From b4932ab5a9de113e4e35f8b38eadc374247d3f58 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 26 Apr 2013 17:32:19 -0400 Subject: [PATCH] Partial support for permission editablity through REST. More TBA. --- lib/main/models/__init__.py | 37 +++++++++++++++++++++++++--- lib/main/serializers.py | 27 +++++++++++++++++++- lib/main/tests/projects.py | 49 ++++++++++++++++++++++++++++++++++--- lib/main/views.py | 39 +++++++++++++++++++++++++++++ lib/urls.py | 9 +++++-- 5 files changed, 151 insertions(+), 10 deletions(-) diff --git a/lib/main/models/__init__.py b/lib/main/models/__init__.py index 3eefe3e745..41bbe0c76d 100644 --- a/lib/main/models/__init__.py +++ b/lib/main/models/__init__.py @@ -168,15 +168,15 @@ class PrimordialModel(models.Model): @classmethod def can_user_administrate(cls, user, obj, data): # FIXME: do we want a seperate method to override put? This is kind of general purpose - raise exceptions.NotImplementedError() + raise Exception("can_user_administrate needs to be implemented in model subclass") @classmethod def can_user_delete(cls, user, obj): - raise exceptions.NotImplementedError() + raise Exception("can_user_delete needs to be implemented in model subclass") @classmethod def can_user_read(cls, user, obj): - raise exceptions.NotImplementedError() + raise Exception("can_user_read needs to be implemented in model subclass") @classmethod def can_user_add(cls, user, data): @@ -805,6 +805,37 @@ class Permission(CommonModelNameNotUnique): self.permission_type )) + def get_absolute_url(self): + import lib.urls + return reverse(lib.urls.views_PermissionsDetail, args=(self.pk,)) + + @classmethod + def can_user_administrate(cls, user, obj, data): + if user.is_superuser: + return True + # a permission can be administrated by a super + # or if a user permission, that an admin of a user's organization + # or if a team permission, an admin of that team's organization + if obj.user and obj.user.organizations.filter(admins__in = [user]).count() > 0: + return True + if obj.team and obj.team.organization.admins.filter(user=user).count() > 0: + return True + return False + + @classmethod + def can_user_read(cls, user, obj): + # a permission can be seen by the assigned user or team + # or anyone who can administrate that permission + if obj.user and obj.user == user: + return True + if obj.team and obj.team.users.filter(pk = user.pk).count() > 0: + return True + return cls.can_user_administrate(user, obj, None) + + @classmethod + def can_user_delete(cls, user, obj): + return cls.can_user_administrate(user, obj, None) + # TODO: other job types (later) class JobTemplate(CommonModel): diff --git a/lib/main/serializers.py b/lib/main/serializers.py index 738a50822d..0c52fb440c 100644 --- a/lib/main/serializers.py +++ b/lib/main/serializers.py @@ -169,18 +169,42 @@ class TeamSerializer(BaseSerializer): users = reverse(lib.urls.views_TeamsUsersList, args=(obj.pk,)), credentials = reverse(lib.urls.views_TeamsCredentialsList, args=(obj.pk,)), organization = reverse(lib.urls.views_OrganizationsDetail, args=(obj.organization.pk,)), + permissions = reverse(lib.urls.views_TeamsPermissionsList, args=(obj.pk,)), ) if obj.created_by: res['created_by'] = reverse(lib.urls.views_UsersDetail, args=(obj.created_by.pk,)) return res +class PermissionSerializer(BaseSerializer): + + url = serializers.CharField(source='get_absolute_url', read_only=True) + related = serializers.SerializerMethodField('get_related') + + class Meta: + model = Permission + fields = ( 'url', 'id', 'user', 'team', 'name', 'description', 'creation_date', + 'project', 'inventory', 'permission_type' ) + + def get_related(self, obj): + res = dict() + if obj.user: + res['user'] = reverse(lib.urls.views_UsersDetail, args=(obj.user.pk,)) + if obj.team: + res['team'] = reverse(lib.urls.views_TeamsDetail, args=(obj.team.pk,)) + if self.project: + res['project'] = reverse(lib.urls.views_ProjectsDetail, args=(obj.project.pk,)) + if self.inventory: + res['inventory'] = reverse(lib.urls.views_InventoryDetail, args=(obj.inventory.pk,)) + if self.created_by: + res['created_by'] = reverse(lib.urls.views_UsersDetail, args=(obj.created_by.pk,)) + class CredentialSerializer(BaseSerializer): # add the URL and related resources url = serializers.CharField(source='get_absolute_url', read_only=True) related = serializers.SerializerMethodField('get_related') - # FIXME: may want to make soem of these filtered based on user accessing + # FIXME: may want to make some of these filtered based on user accessing class Meta: model = Credential fields = ( @@ -228,6 +252,7 @@ class UserSerializer(BaseSerializer): admin_of_organizations = reverse(lib.urls.views_UsersAdminOrganizationsList, args=(obj.pk,)), projects = reverse(lib.urls.views_UsersProjectsList, args=(obj.pk,)), credentials = reverse(lib.urls.views_UsersCredentialsList, args=(obj.pk,)), + permissions = reverse(lib.urls.views_UsersPermissionsList, args=(obj.pk,)), ) def get_absolute_url_override(self, obj): diff --git a/lib/main/tests/projects.py b/lib/main/tests/projects.py index 5d876e42e2..4e012a1f9e 100644 --- a/lib/main/tests/projects.py +++ b/lib/main/tests/projects.py @@ -386,15 +386,56 @@ class ProjectsTest(BaseTest): # ===================================================================== # PERMISSIONS + + user = self.other_django_user + team = Team.objects.get(pk=1) + organization = Organization.objects.get(pk=1) + inventory = Inventory.objects.create( + name = 'test inventory', + organization = organization, + created_by = self.super_django_user + ) + project = Project.objects.get(pk=1) + # can add permissions to a user + + user_permission = dict( + name='user can deploy a certain project to a certain inventory', + # user=user.pk, # no need to specify, this will be automatically filled in + inventory=inventory.pk, + project=project.pk, + permission_type=PERM_INVENTORY_DEPLOY + ) + team_permission = dict( + name='team can deploy a certain project to a certain inventory', + # team=team.pk, # no need to specify, this will be automatically filled in + inventory=inventory.pk, + project=project.pk, + permission_type=PERM_INVENTORY_DEPLOY + ) + + url = '/api/v1/users/%s/permissions/' % user.pk + self.post(url, user_permission, expect=201, auth=self.get_super_credentials()) + # can add permissions on a team + url = '/api/v1/teams/%s/permissions/' % team.pk + self.post(url, team_permission, expect=201, auth=self.get_super_credentials()) + # can list permissions on a user + url = '/api/v1/users/%s/permissions/' % user.pk + # can list permissions on a team + url = '/api/v1/teams/%s/permissions/' % team.pk + # can edit a permission - # can remove credentials from a user - # can remove credentials from a team - - + + # can remove permissions from a user + # do need to disassociate, just delete it + + # can remove permissions from a team + # do need to disassociate, just delete it + + diff --git a/lib/main/views.py b/lib/main/views.py index 61c59f1228..a154d306be 100644 --- a/lib/main/views.py +++ b/lib/main/views.py @@ -266,6 +266,23 @@ class TeamsUsersList(BaseSubList): return base raise PermissionDenied() +class TeamsPermissionsList(BaseSubList): + + model = Permission + serializer_class = PermissionSerializer + permission_classes = (CustomRbac,) + parent_model = Team + relationship = 'permissions' + postable = True + filter_fields = ('name',) + inject_primary_key_on_post_as = 'team' + + def _get_queryset(self): + team = Team.objects.get(pk=self.kwargs['pk']) + if not Team.can_user_administrate(self.request.user, team, None): + raise PermissionDenied() + return Permission.objects.filter(team = team) + class TeamsProjectsList(BaseSubList): @@ -423,6 +440,23 @@ class UsersTeamsList(BaseSubList): raise PermissionDenied() return Team.objects.filter(users__in = [ user ]) +class UsersPermissionsList(BaseSubList): + + model = Permission + serializer_class = PermissionSerializer + permission_classes = (CustomRbac,) + parent_model = User + relationship = 'permissions' + postable = True + filter_fields = ('name',) + inject_primary_key_on_post_as = 'user' + + def _get_queryset(self): + user = User.objects.get(pk=self.kwargs['pk']) + if not UserHelper.can_user_administrate(self.request.user, user, None): + raise PermissionDenied() + return Permission.objects.filter(user=user) + class UsersProjectsList(BaseSubList): model = Project @@ -514,6 +548,11 @@ class CredentialsDetail(BaseDetail): serializer_class = CredentialSerializer permission_classes = (CustomRbac,) +class PermissionsDetail(BaseDetail): + + model = Permission + serializer_class = PermissionSerializer + permission_classes = (CustomRbac,) class InventoryList(BaseList): diff --git a/lib/urls.py b/lib/urls.py index ef3a873b0c..d819eafde6 100644 --- a/lib/urls.py +++ b/lib/urls.py @@ -102,6 +102,10 @@ views_TagsDetail = views.TagsDetail.as_view() # credentials service views_CredentialsDetail = views.CredentialsDetail.as_view() +# permissions +views_UsersPermissionsList = views.UsersPermissionsList.as_view() +views_TeamsPermissionsList = views.TeamsPermissionsList.as_view() +views_PermissionsDetail = views.PermissionsDetail.as_view() urlpatterns = patterns('', @@ -199,8 +203,9 @@ urlpatterns = patterns('', url(r'^api/v1/credentials/(?P[0-9]+)/$', views_CredentialsDetail), # permissions services - # ... users - # ... teams + url(r'^api/v1/users/(?P[0-9]+)/permissions/$', views_UsersPermissionsList), + url(r'^api/v1/teams/(?P[0-9]+)/permissions/$', views_TeamsPermissionsList), + url(r'^api/v1/permissions/(?P[0-9]+)/$', views_PermissionsDetail), )