From 6b8c3afb65cdf015a7ac22d3a695e685a1aeeab5 Mon Sep 17 00:00:00 2001 From: Paul Marillonnet Date: Fri, 12 Jan 2018 11:27:12 +0100 Subject: [PATCH] add role-creation API (#20706) --- src/authentic2/api_views.py | 65 ++++++++++++++++++ tests/test_api.py | 156 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py index 95fb99ee..db691f00 100644 --- a/src/authentic2/api_views.py +++ b/src/authentic2/api_views.py @@ -444,6 +444,43 @@ class BaseUserSerializer(serializers.ModelSerializer): exclude = ('date_joined', 'user_permissions', 'groups', 'last_login') +class RoleSerializer(serializers.ModelSerializer): + ou = serializers.SlugRelatedField( + many=False, + required=False, + default=get_default_ou, + queryset=get_ou_model().objects.all(), + slug_field='slug') + + def check_perm(self, perm, ou=None): + self.context['view'].check_perm(perm, ou) + + def create(self, validated_data): + self.check_perm('a2_rbac.add_role', validated_data.get('ou')) + return super(RoleSerializer, self).create(validated_data) + + def update(self, instance, validated_data): + self.check_perm('a2_rbac.change_role', validated_data.get('ou')) + # The OU shouldn't be changed: + if instance.ou != validated_data.get('ou'): + validated_data['ou'] = instance.ou + super(RoleSerializer, self).update(instance, validated_data) + return instance + + def partial_update(self, instance, validated_data): + self.check_perm('a2_rbac.change_role', validated_data.get('ou')) + # The OU shouldn't be changed: + if validated_data.get('ou') and instance.ou != validated_data['ou']: + validated_data['ou'] = instance.ou + super(RoleSerializer, self).partial_update(instance, validated_data) + return instance + + class Meta: + model = get_role_model() + fields = ('uuid', 'name', 'slug', 'description', 'ou',) + extra_kwargs = {'uuid': {'read_only': True}} + + class UsersFilter(FilterSet): class Meta: model = get_user_model() @@ -576,6 +613,33 @@ class UsersAPI(HookMixin, ExceptionHandlerMixin, ModelViewSet): return Response({'result': 1}) +class RolesAPI(ExceptionHandlerMixin, ModelViewSet): + permission_classes = (permissions.IsAuthenticated,) + serializer_class = RoleSerializer + lookup_field = 'slug' + queryset = get_role_model().objects.all() + + def check_perm(self, perm, ou=None): + if ou: + if not self.request.user.has_ou_perm(perm, ou): + raise PermissionDenied(u'User %s does not have permission %s in %s' % (user, perm, ou)) + else: + if not self.request.user.has_perm(perm): + raise PermissionDenied(u'User %s do not have permission %s' % (user, perm)) + + def retrieve(self, request, *args, **kwargs): + self.check_perm('a2_rbac.view_role', None) + return super(RolesAPI, self).retrieve(request, *args, **kwargs) + + def list(self, request, *args, **kwargs): + self.check_perm('a2_rbac.view_role', None) + return super(RolesAPI, self).list(request, *args, **kwargs) + + def perform_destroy(self, instance): + self.check_perm('a2_rbac.delete_role', instance.ou) + super(RolesAPI, self).perform_destroy(instance) + + class RoleMembershipsAPI(ExceptionHandlerMixin, APIView): permission_classes = (permissions.IsAuthenticated,) @@ -620,6 +684,7 @@ class OrganizationalUnitAPI(ExceptionHandlerMixin, ModelViewSet): router = SimpleRouter() router.register(r'users', UsersAPI, base_name='a2-api-users') router.register(r'ous', OrganizationalUnitAPI, base_name='a2-api-ous') +router.register(r'roles', RolesAPI, base_name='a2-api-roles') class CheckPasswordSerializer(serializers.Serializer): diff --git a/tests/test_api.py b/tests/test_api.py index 96085590..d8790ca5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -668,3 +668,159 @@ def test_users_email(app, ou1, admin, user_ou1, mailoutbox): assert mail.to[0] == new_email assert 'http://testserver/accounts/change-email/verify/' in mail.body + + +def test_api_delete_role(app, superuser, simple_role): + app.authorization = ('Basic', (superuser.username, superuser.username)) + Role = get_role_model() + + app.delete('/api/roles/simple-role/') + assert not len(Role.objects.filter(slug='simple-role')) + + +def test_api_delete_role_unauthorized(app, simple_user, simple_role): + app.authorization = ('Basic', (simple_user.username, simple_user.username)) + Role = get_role_model() + + app.delete('/api/roles/simple-role/', status=403) + assert len(Role.objects.filter(slug='simple-role')) + + +def test_api_patch_role(app, superuser, simple_role): + app.authorization = ('Basic', (superuser.username, superuser.username)) + Role = get_role_model() + + role_data = { + 'slug': 'updated-role', + } + resp = app.patch_json('/api/roles/simple-role/', params=role_data) + assert not len(Role.objects.filter(slug='simple-role')) + assert len(Role.objects.filter(slug='updated-role')) + + # The role API won't change the organizational unit attribute: + role = Role.objects.get(slug='updated-role') + assert role.ou.slug == get_default_ou().slug + assert role.ou.slug != 'ou1' + + +def test_api_patch_role_unauthorized(app, simple_user, simple_role): + app.authorization = ('Basic', (simple_user.username, simple_user.username)) + Role = get_role_model() + + role_data = { + 'slug': 'updated-role', + } + resp = app.patch_json('/api/roles/simple-role/', params=role_data, status=403) + assert len(Role.objects.filter(slug='simple-role')) + assert not len(Role.objects.filter(slug='updated-role')) + + +def test_api_put_role(app, superuser, simple_role, ou1): + app.authorization = ('Basic', (superuser.username, superuser.username)) + Role = get_role_model() + + role_data = { + 'name': 'updated-role', + 'slug': 'updated-role', + 'ou': 'ou1' + } + resp = app.put_json('/api/roles/simple-role/', params=role_data) + assert not len(Role.objects.filter(slug='simple-role')) + assert len(Role.objects.filter(slug='updated-role')) + + # The role API won't change the organizational unit attribute: + role = Role.objects.get(slug='updated-role') + assert role.ou.slug == get_default_ou().slug + assert role.ou.slug != 'ou1' + + +def test_api_put_role_unauthorized(app, simple_user, simple_role, ou1): + app.authorization = ('Basic', (simple_user.username, simple_user.username)) + Role = get_role_model() + + role_data = { + 'name': 'updated-role', + 'slug': 'updated-role', + 'ou': 'ou1' + } + resp = app.put_json('/api/roles/simple-role/', params=role_data, status=403) + assert len(Role.objects.filter(slug='simple-role')) + assert not len(Role.objects.filter(slug='updated-role')) + + +def test_api_post_role(app, superuser, ou1): + app.authorization = ('Basic', (superuser.username, superuser.username)) + + role_data = { + 'slug': 'coffee-manager', + 'name': 'Coffee Manager', + 'ou': 'ou1' + } + resp = app.post_json('/api/roles/', params=role_data) + assert isinstance(resp.json, dict) + Role = get_role_model() + + # Check attribute values against the server's response: + for key, value in role_data.items(): + assert key in resp.json.keys() + assert value in resp.json.values() + + # Check attributes values against the DB: + posted_role = Role.objects.get(slug='coffee-manager') + assert posted_role.slug == role_data['slug'] + assert posted_role.name == role_data['name'] + assert posted_role.ou.slug == 'ou1' + + +def test_api_post_role_no_ou(app, superuser): + app.authorization = ('Basic', (superuser.username, superuser.username)) + Role = get_role_model() + + role_data = { + 'slug': 'tea-manager', + 'name': 'Tea Manager', + } + resp = app.post_json('/api/roles/', params=role_data) + new_role = Role.objects.get(slug='tea-manager') + default_ou = get_default_ou() + assert new_role.ou == default_ou + + +def test_api_post_role_unauthorized(app, simple_user, ou1): + app.authorization = ('Basic', (simple_user.username, simple_user.username)) + Role = get_role_model() + + role_data = { + 'slug': 'mocca-manager', + 'name': 'Mocca Manager', + 'ou': 'ou1' + } + + resp = app.post_json('/api/roles/', params=role_data, status=403) + assert not len(Role.objects.filter(slug='mocca-manager')) + + +def test_api_get_role_description(app, superuser, role_random): + app.authorization = ('Basic', (superuser.username, superuser.username)) + resp = app.get('/api/roles/rando/') + + assert resp.json['slug'] == 'rando' + assert resp.json['ou'] == 'ou_rando' + + +def test_api_get_role_not_found(app, superuser): + app.authorization = ('Basic', (superuser.username, superuser.username)) + resp = app.get('/api/roles/thisisnotavalidroleslug/', status=404) + + +def test_api_get_role_list(app, superuser, simple_role, role_random): + app.authorization = ('Basic', (superuser.username, superuser.username)) + resp = app.get('/api/roles/') + + role_fields = ['slug', 'uuid', 'name', 'ou'] + + assert len(resp.json['results']) + + for role_dict in resp.json['results']: + for field in role_fields: + assert field in role_dict -- 2.11.0