Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1 to v3 namespace rbac shim #1853

Merged
merged 1 commit into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions galaxy_ng/app/access_control/access_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from galaxy_ng.app.api.v1.models import LegacyNamespace
from galaxy_ng.app.api.v1.models import LegacyRole
from galaxy_ng.app.constants import COMMUNITY_DOMAINS
from galaxy_ng.app.utils.rbac import get_v3_namespace_owners

from galaxy_ng.app.access_control.statements import PULP_VIEWSETS

Expand Down Expand Up @@ -770,8 +771,14 @@ def is_namespace_owner(self, request, viewset, action):
if namespace is None and github_user and user.username == github_user:
return True

# allow owners to do things in the namespace
if namespace and user.username in [x.username for x in namespace.owners.all()]:
# v1 namespace rbac is controlled via their v3 namespace
v3_namespace = namespace.namespace
if not v3_namespace:
return False

# use the helper to get the list of owners
owners = get_v3_namespace_owners(v3_namespace)
if owners and user in owners:
return True

return False
23 changes: 23 additions & 0 deletions galaxy_ng/app/api/v1/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from galaxy_ng.app.models.auth import User
from galaxy_ng.app.api.v1.models import LegacyNamespace
from galaxy_ng.app.api.v1.models import LegacyRole
from galaxy_ng.app.utils.rbac import get_v3_namespace_owners


class LegacyNamespaceFilter(filterset.FilterSet):

keywords = filters.CharFilter(method='keywords_filter')
owner = filters.CharFilter(method='owner_filter')

sort = filters.OrderingFilter(
fields=(
Expand All @@ -31,6 +33,23 @@ def keywords_filter(self, queryset, name, value):

return queryset

def owner_filter(self, queryset, name, value):
# find the owner on the linked v3 namespace

# FIXME - this is terribly slow
pks = []
for ns1 in LegacyNamespace.objects.all():
if not ns1.namespace:
continue
ns3 = ns1.namespace
owners = get_v3_namespace_owners(ns3)
if value in [x.username for x in owners]:
pks.append(ns1.id)

queryset = queryset.filter(id__in=pks)

return queryset


class LegacyUserFilter(filterset.FilterSet):

Expand Down Expand Up @@ -61,6 +80,7 @@ class LegacyRoleFilter(filterset.FilterSet):
tag = filters.CharFilter(method='tags_filter')
autocomplete = filters.CharFilter(method='autocomplete_filter')
owner__username = filters.CharFilter(method='owner__username_filter')
namespace = filters.CharFilter(method='namespace_filter')

sort = filters.OrderingFilter(
fields=(
Expand All @@ -76,6 +96,9 @@ class Meta:
def github_user_filter(self, queryset, name, value):
return queryset.filter(namespace__name=value)

def namespace_filter(self, queryset, name, value):
return queryset.filter(namespace__name__iexact=value)

def owner__username_filter(self, queryset, name, value):
"""
The cli uses this filter to find a role by the namespace.
Expand Down
6 changes: 6 additions & 0 deletions galaxy_ng/app/api/v1/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class LegacyNamespace(models.Model):
editable=True
)

def __repr__(self):
return f'<LegacyNamespace: {self.name}>'


class LegacyRole(models.Model):
"""
Expand Down Expand Up @@ -148,6 +151,9 @@ class LegacyRole(models.Model):
default=dict
)

def __repr__(self):
return f'<LegacyRole: {self.namespace.name}.{self.name}>'


class LegacyRoleDownloadCount(models.Model):
legacyrole = models.OneToOneField(
Expand Down
25 changes: 23 additions & 2 deletions galaxy_ng/app/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from pulpcore.plugin.util import get_url

from galaxy_ng.app.models.auth import User
from galaxy_ng.app.models.namespace import Namespace
from galaxy_ng.app.utils.rbac import get_v3_namespace_owners
from galaxy_ng.app.api.v1.models import LegacyNamespace
from galaxy_ng.app.api.v1.models import LegacyRole
from galaxy_ng.app.api.v1.models import LegacyRoleDownloadCount
Expand Down Expand Up @@ -49,8 +51,11 @@ def get_date_joined(self, obj):
return obj.created

def get_summary_fields(self, obj):
owners = obj.owners.all()
owners = [{'id': x.id, 'username': x.username} for x in owners]

owners = []
if obj.namespace:
owner_objects = get_v3_namespace_owners(obj.namespace)
owners = [{'id': x.id, 'username': x.username} for x in owner_objects]

# link the v1 namespace to the v3 namespace so that users
# don't need to query the database to figure it out.
Expand Down Expand Up @@ -82,6 +87,22 @@ def get_id(self, obj):
return obj.id


class LegacyNamespaceProviderSerializer(serializers.ModelSerializer):

pulp_href = serializers.SerializerMethodField()

class Meta:
model = Namespace
fields = [
'id',
'name',
'pulp_href'
]

def get_pulp_href(self, obj):
return get_url(obj)


class LegacyUserSerializer(serializers.ModelSerializer):

summary_fields = serializers.SerializerMethodField()
Expand Down
69 changes: 40 additions & 29 deletions galaxy_ng/app/api/v1/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import datetime
import logging
import os
import subprocess
import tempfile

from django.db import transaction
Expand All @@ -13,6 +14,7 @@
from galaxy_ng.app.utils.galaxy import upstream_role_iterator
from galaxy_ng.app.utils.git import get_tag_commit_hash
from galaxy_ng.app.utils.git import get_tag_commit_date
from galaxy_ng.app.utils.legacy import process_namespace

from galaxy_ng.app.api.v1.models import LegacyNamespace
from galaxy_ng.app.api.v1.models import LegacyRole
Expand Down Expand Up @@ -60,27 +62,39 @@ def legacy_role_import(
if not github_reference:
github_reference = None

if LegacyNamespace.objects.filter(name=github_user).count() == 0:
logger.debug(f'CREATE NEW NAMESPACE {github_user}')
namespace, _ = LegacyNamespace.objects.get_or_create(name=github_user)
# this shouldn't happen but just in case ...
if request_username and not User.objects.filter(username=request_username).exists():
raise Exception(f'Username {request_username} does not exist in galaxy')

# set the owner to this request user ...
user = User.objects.filter(username=request_username).first()
namespace.owners.add(user)
# the user should have a legacy and v3 namespace if they logged in ...
namespace = LegacyNamespace.objects.filter(name=github_user).first()
if not namespace:
raise Exception(f'No legacy namespace exists for {github_user}')

else:
logger.debug(f'USE EXISTING NAMESPACE {github_user}')
namespace = LegacyNamespace.objects.filter(name=github_user).first()
# we have to have a v3 namespace because of the rbac based ownership ...
v3_namespace = namespace.namespace
if not v3_namespace:
raise Exception(f'No v3 namespace exists for {github_user}')

with tempfile.TemporaryDirectory() as tmp_path:
# galaxy-importer requires importing legacy roles from the role's parent directory.
os.chdir(tmp_path)

# galaxy-importer wants the role's directory to be the name of the role.
checkout_path = os.path.join(tmp_path, github_repo)

clone_url = f'https://github.com/{github_user}/{github_repo}'
gitrepo = Repo.clone_from(clone_url, checkout_path, multi_options=["--recurse-submodules"])

# pygit didn't have an obvious way to prevent interactive clones ...
pid = subprocess.run(
f'GIT_TERMINAL_PROMPT=0 git clone --recurse-submodules {clone_url} {checkout_path}',
shell=True,
)
if pid.returncode != 0:
raise Exception(f'git clone for {clone_url} failed')

# bind the checkout to a pygit object
gitrepo = Repo(checkout_path)

github_commit = None
github_commit_date = None

Expand Down Expand Up @@ -126,7 +140,7 @@ def legacy_role_import(
if github_reference in old_versions:
msg = (
f'{namespace.name}.{role_name} {github_reference}'
+ 'has already been imported'
+ ' has already been imported'
)
raise Exception(msg)

Expand Down Expand Up @@ -186,7 +200,8 @@ def legacy_sync_from_upstream(
github_user=None,
role_name=None,
role_version=None,
limit=None
limit=None,
start_page=None,
):
"""
Sync legacy roles from a remote v1 api.
Expand Down Expand Up @@ -214,8 +229,6 @@ def legacy_sync_from_upstream(

logger.debug('SYNC INDEX EXISTING NAMESPACES')
nsmap = {}
for ns in LegacyNamespace.objects.all():
nsmap[ns.name] = ns

# allow the user to specify how many roles to sync
if limit is not None:
Expand All @@ -228,12 +241,23 @@ def legacy_sync_from_upstream(
'baseurl': baseurl,
'github_user': github_user,
'role_name': role_name,
'limit': limit
'limit': limit,
'start_page': start_page,
}
for ns_data, rdata, rversions in upstream_role_iterator(**iterator_kwargs):

# processing a namespace should make owners and set rbac as needed ...
if ns_data['name'] not in nsmap:
namespace, v3_namespace = process_namespace(ns_data['name'], ns_data)
nsmap[ns_data['name']] = (namespace, v3_namespace)
else:
namespace, v3_namespace = nsmap[ns_data['name']]

ruser = rdata.get('github_user')
rname = rdata.get('name')

logger.info(f'POPULATE {ruser}.{rname}')

rkey = (ruser, rname)
remote_id = rdata['id']
role_versions = rversions[:]
Expand All @@ -260,19 +284,6 @@ def legacy_sync_from_upstream(
role_type = rdata.get('role_type', 'ANS')
role_download_count = rdata.get('download_count', 0)

if ruser not in nsmap:
logger.debug(f'SYNC NAMESPACE GET_OR_CREATE {ruser}')
namespace, _ = LegacyNamespace.objects.get_or_create(name=ruser)

# if the ns has owners, create them and set them
for owner_info in ns_data['summary_fields']['owners']:
user, _ = User.objects.get_or_create(username=owner_info['username'])
namespace.owners.add(user)

nsmap[ruser] = namespace
else:
namespace = nsmap[ruser]

if rkey not in rmap:
logger.debug(f'SYNC create initial role for {rkey}')
this_role, _ = LegacyRole.objects.get_or_create(
Expand Down
8 changes: 7 additions & 1 deletion galaxy_ng/app/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
LegacyRolesSyncViewSet,
LegacyNamespacesViewSet,
LegacyNamespaceOwnersViewSet,
LegacyNamespaceProvidersViewSet,
LegacyUsersViewSet
)

Expand Down Expand Up @@ -86,7 +87,7 @@
),
path(
'namespaces/',
LegacyNamespacesViewSet.as_view({"get": "list"}),
LegacyNamespacesViewSet.as_view({"get": "list", "post": "create"}),
name='legacy_namespace-list'
),
path(
Expand All @@ -99,6 +100,11 @@
LegacyNamespaceOwnersViewSet.as_view({"get": "list", "put": "update"}),
name='legacy_namespace_owners-list'
),
path(
'namespaces/<int:pk>/providers/',
LegacyNamespaceProvidersViewSet.as_view({"get": "list", "put": "update", "post": "update"}),
name='legacy_namespace_providers-list'
),

path('', LegacyRootView.as_view(), name='legacy-root')
]
2 changes: 2 additions & 0 deletions galaxy_ng/app/api/v1/viewsets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .namespaces import (
LegacyNamespacesViewSet,
LegacyNamespaceOwnersViewSet,
LegacyNamespaceProvidersViewSet,
)

from .users import (
Expand All @@ -23,6 +24,7 @@
__all__ = (
LegacyNamespacesViewSet,
LegacyNamespaceOwnersViewSet,
LegacyNamespaceProvidersViewSet,
LegacyUsersViewSet,
LegacyRolesViewSet,
LegacyRolesSyncViewSet,
Expand Down
Loading
Loading