Skip to content

Commit

Permalink
Remove django guardian (ansible#1057)
Browse files Browse the repository at this point in the history
Removing django-guardian and migrating to pulp RBAC roles. 
Issue: AAH-1093

Co-authored-by: Brian McLaughlin <[email protected]>
  • Loading branch information
newswangerd and bmclaughlin authored Apr 7, 2022
1 parent daa07d0 commit d84dee6
Show file tree
Hide file tree
Showing 23 changed files with 183 additions and 188 deletions.
1 change: 1 addition & 0 deletions CHANGES/1093.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removing django-guardian and migrating to RBAC Roles
39 changes: 17 additions & 22 deletions galaxy_ng/app/access_control/fields.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,46 @@
from django.contrib.auth.models import Permission
from django.db.models import Q
from django.utils.translation import gettext_lazy as _

from guardian.shortcuts import get_perms_for_model

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from pulpcore.plugin.models.role import Role

from pulpcore.plugin.util import get_perms_for_model

from galaxy_ng.app.models import auth as auth_models


class GroupPermissionField(serializers.Field):
def _validate_group(self, group_data):
if 'object_permissions' not in group_data:
if 'object_roles' not in group_data:
raise ValidationError(detail={
'groups': _('object_permissions field is required')})
'groups': _('object_roles field is required')})

if 'id' not in group_data and 'name' not in group_data:
raise ValidationError(detail={
'groups': _('id or name field is required')})

perms = group_data['object_permissions']
roles = group_data['object_roles']

if not isinstance(perms, list):
if not isinstance(roles, list):
raise ValidationError(detail={
'groups': _('object_permissions must be a list of strings')})
'groups': _('object_roles must be a list of strings')})

# validate that the permissions exist
for perm in perms:
if '.' in perm:
app_label, codename = perm.split('.', maxsplit=1)
filter_q = Q(content_type__app_label=app_label) & Q(codename=codename)
else:
filter_q = Q(codename=perm)

for role in roles:
# TODO(newswangerd): Figure out how to make this one SQL query instead of
# performing N queries for each permission
if not Permission.objects.filter(filter_q).exists():
if not Role.objects.filter(name=role).exists():
raise ValidationError(detail={
'groups': _('Permission {} does not exist').format(perm)})
'groups': _('Role {} does not exist').format(role)})

def to_representation(self, value):
rep = []
for group in value:
rep.append({
'id': group.id,
'name': group.name,
'object_permissions': value[group]
'object_roles': value[group]
})
return rep

Expand All @@ -65,7 +59,10 @@ def to_internal_value(self, data):
group_filter[field] = group_data[field]
try:
group = auth_models.Group.objects.get(**group_filter)
internal[group] = group_data['object_permissions']
if 'object_permissions' in group_data:
internal[group] = group_data['object_permissions']
if 'object_roles' in group_data:
internal[group] = group_data['object_roles']
except auth_models.Group.DoesNotExist:
raise ValidationError(detail={
'groups': _("Group name=%s, id=%s does not exist") % (
Expand All @@ -84,8 +81,6 @@ def to_representation(self, obj):
return []
user = request.user

# guardian's get_perms(user, obj) method only returns user permissions,
# not all permissions a user has.
my_perms = []
for perm in get_perms_for_model(type(obj)).all():
codename = "{}.{}".format(perm.content_type.app_label, perm.codename)
Expand Down
31 changes: 24 additions & 7 deletions galaxy_ng/app/access_control/mixins.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
from django.conf import settings
from django.db import transaction
from guardian.shortcuts import get_groups_with_perms, assign_perm, remove_perm
from django.core.exceptions import BadRequest
from django.utils.translation import gettext_lazy as _

from rest_framework.exceptions import ValidationError

from pulpcore.plugin.util import (
assign_role,
remove_role,
get_groups_with_perms_attached_roles
)

from django_lifecycle import hook


Expand All @@ -9,28 +19,35 @@ class GroupModelPermissionsMixin:

@property
def groups(self):
return get_groups_with_perms(self, attach_perms=True)
return get_groups_with_perms_attached_roles(self)

@groups.setter
def groups(self, groups):
self._set_groups(groups)

@transaction.atomic
def _set_groups(self, groups):
# guardian doesn't allow adding permissions to objects that haven't been
# Can't add permissions to objects that haven't been
# saved. When creating new objects, save group data to _groups where it
# can be picked up by the post save hook.
if self._state.adding:
self._groups = groups
else:
current_groups = get_groups_with_perms(self, attach_perms=True)
current_groups = get_groups_with_perms_attached_roles(self)
for group in current_groups:
for perm in current_groups[group]:
remove_perm(perm, group, self)
remove_role(perm, group, self)

for group in groups:
for perm in groups[group]:
assign_perm(perm, group, self)
for role in groups[group]:
try:
assign_role(role, group, self)
except BadRequest:
raise ValidationError(
detail={'groups': _('Role {role} does not exist or does not '
'have any permissions related to this object.'
).format(role=role)}
)

@hook('after_save')
def set_object_groups(self):
Expand Down
16 changes: 16 additions & 0 deletions galaxy_ng/app/access_control/statements/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
},
"NamespaceViewSet": {
"LOCKED_ROLES": {
"galaxy.content_admin": [
"ansible.modify_ansible_repo_content",
],
"galaxy.namespace_owner": [
"galaxy.add_namespace",
"galaxy.change_namespace",
Expand All @@ -36,6 +39,18 @@
"galaxy.upload_to_namespace",
"ansible.delete_collection",
],
"galaxy.group_admin": [
"galaxy.view_group",
"galaxy.delete_group",
"galaxy.add_group",
"galaxy.change_group",
],
"galaxy.user_admin": [
"galaxy.view_user",
"galaxy.delete_user",
"galaxy.add_user",
"galaxy.change_user",
],
},
},
"SyncListViewSet": {
Expand All @@ -45,6 +60,7 @@
"galaxy.change_synclist",
"galaxy.delete_synclist",
"galaxy.view_synclist",
"ansible.change_collectionremote",
],
}
},
Expand Down
2 changes: 1 addition & 1 deletion galaxy_ng/app/api/ui/serializers/execution_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field

from guardian.shortcuts import get_users_with_perms
from pulpcore.plugin.util import get_users_with_perms

from pulp_container.app import models as container_models
from pulp_container.app import serializers as container_serializers
Expand Down
4 changes: 2 additions & 2 deletions galaxy_ng/app/api/ui/viewsets/distribution.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import mixins
from pulp_ansible.app import models as pulp_models
from guardian.shortcuts import get_objects_for_user
from pulpcore.plugin.util import get_objects_for_user

from galaxy_ng.app.access_control import access_policy
from galaxy_ng.app.api.ui import serializers, versioning
Expand Down Expand Up @@ -31,7 +31,7 @@ def get_queryset(self):
'galaxy.change_synclist',
any_perm=True,
accept_global_perms=False,
klass=models.SyncList
qs=models.SyncList.objects.all()
)

# TODO: find a better way query this data
Expand Down
4 changes: 2 additions & 2 deletions galaxy_ng/app/api/ui/viewsets/execution_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django_filters import filters
from django_filters.rest_framework import DjangoFilterBackend, filterset
from drf_spectacular.utils import extend_schema
from guardian.shortcuts import get_objects_for_user
from pulpcore.plugin.util import get_objects_for_user
from pulp_container.app import models as container_models
from pulpcore.plugin import models as core_models
from pulpcore.plugin.serializers import AsyncOperationResponseSerializer
Expand Down Expand Up @@ -47,7 +47,7 @@ class Meta:
def has_permissions(self, queryset, name, value):
perms = self.request.query_params.getlist(name)
namespaces = get_objects_for_user(
self.request.user, perms, klass=container_models.ContainerNamespace)
self.request.user, perms, qs=container_models.ContainerNamespace.objects.all())
return self.queryset.filter(namespace__in=namespaces)


Expand Down
4 changes: 2 additions & 2 deletions galaxy_ng/app/api/ui/viewsets/my_namespace.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from galaxy_ng.app import models
from guardian.shortcuts import get_objects_for_user
from pulpcore.plugin.util import get_objects_for_user

from .namespace import NamespaceViewSet

Expand All @@ -10,5 +10,5 @@ def get_queryset(self):
self.request.user,
('galaxy.change_namespace', 'galaxy.upload_to_namespace'),
any_perm=True,
klass=models.Namespace
qs=models.Namespace.objects.all()
)
7 changes: 3 additions & 4 deletions galaxy_ng/app/api/ui/viewsets/my_synclist.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from django.shortcuts import get_object_or_404
from guardian.shortcuts import get_objects_for_user
from pulpcore.plugin.util import get_objects_for_user

from rest_framework.decorators import action

Expand Down Expand Up @@ -30,9 +30,8 @@ def get_queryset(self):
return get_objects_for_user(
self.request.user,
"galaxy.change_synclist",
any_perm=True,
accept_global_perms=False,
klass=models.SyncList,
# any_perm=True,
qs=models.SyncList.objects.all(),
)

@action(detail=True, methods=["post"])
Expand Down
11 changes: 5 additions & 6 deletions galaxy_ng/app/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

from django.conf import settings
from django.db import transaction
from guardian import shortcuts

from pulpcore.plugin.util import get_objects_for_group

from pulp_ansible.app.models import AnsibleDistribution, AnsibleRepository
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
Expand Down Expand Up @@ -79,11 +81,9 @@ def _ensure_group(self, account_scope, account):
def _ensure_synclists(self, group):
with transaction.atomic():
# check for existing synclists
perms = ['galaxy.view_synclist']

synclists_owned_by_group = \
shortcuts.get_objects_for_group(group, perms, klass=SyncList,
any_perm=False, accept_global_perms=True)
get_objects_for_group(group, 'galaxy.view_synclist', SyncList.objects.all())
if synclists_owned_by_group:
return synclists_owned_by_group

Expand All @@ -105,8 +105,7 @@ def _ensure_synclists(self, group):
},
)

default_synclist.groups = {group: ['galaxy.view_synclist', 'galaxy.add_synclist',
'galaxy.delete_synclist', 'galaxy.change_synclist']}
default_synclist.groups = {group: ['galaxy.synclist_owner']}
default_synclist.save()
return default_synclist

Expand Down
44 changes: 17 additions & 27 deletions galaxy_ng/app/management/commands/maintain-pe-group.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.core.management import BaseCommand
from guardian.shortcuts import assign_perm

from pulpcore.plugin.util import assign_role

from galaxy_ng.app.models.auth import Group

Expand All @@ -9,43 +10,32 @@
class Command(BaseCommand):
"""
This command creates or updates a partner engineering group
with a standard set of permissions. Intended to be used for
settings.GALAXY_DEPLOYMENT_MODE==insights.
with a standard set of permissions via Galaxy locked roles.
Intended to be used for settings.GALAXY_DEPLOYMENT_MODE==insights.
$ django-admin maintain-pe-group
"""

help = "Creates/updates partner engineering group with permissions"

def handle(self, *args, **options):
pe_group, created = Group.objects.get_or_create(name=PE_GROUP_NAME)
if created:
pe_group, group_created = Group.objects.get_or_create(name=PE_GROUP_NAME)
if group_created:
self.stdout.write(f"Created group '{PE_GROUP_NAME}'")
else:
self.stdout.write(f"Group '{PE_GROUP_NAME}' already exists")

pe_perms = [
# groups
"galaxy.view_group",
"galaxy.delete_group",
"galaxy.add_group",
"galaxy.change_group",
# users
"galaxy.view_user",
"galaxy.delete_user",
"galaxy.add_user",
"galaxy.change_user",
# collections
"ansible.modify_ansible_repo_content",
"ansible.delete_collection",
# namespaces
"galaxy.add_namespace",
"galaxy.change_namespace",
"galaxy.upload_to_namespace",
"galaxy.delete_namespace",
pe_roles = [
'galaxy.group_admin',
'galaxy.user_admin',
'galaxy.collection_admin',
'galaxy.namespace_owner',
'galaxy.content_admin',
]

for perm in pe_perms:
assign_perm(perm, pe_group)
for role in pe_roles:
assign_role(rolename=role, entity=pe_group)

self.stdout.write(f"Permissions assigned to '{PE_GROUP_NAME}'")
self.stdout.write(
f"Roles assigned to '{PE_GROUP_NAME}'"
)
4 changes: 3 additions & 1 deletion galaxy_ng/app/models/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from django.contrib.auth import models as auth_models

from pulpcore.plugin.models import Group as PulpGroup

log = logging.getLogger(__name__)

__all__ = (
Expand Down Expand Up @@ -36,7 +38,7 @@ def _make_name(scope, name):
return f"{scope}:{name}"


class Group(auth_models.Group):
class Group(PulpGroup):
objects = GroupManager()

class Meta:
Expand Down
9 changes: 9 additions & 0 deletions galaxy_ng/app/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ class ContentRedirectContentGuardViewSet(
):
queryset = models.ContentRedirectContentGuard.objects.all()
endpoint_name = "contentgaurd"


class AuthViewSet(
pulp_viewsets.NamedModelViewSet,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
):
queryset = models.auth.Group.objects.all()
endpoint_name = "auth"
Loading

0 comments on commit d84dee6

Please sign in to comment.