Repository: genialis/resolwe

Issue:
500 error returned when permission does not exist
We should return 400 Bad request response.

Failing unit test: https://github.com/genialis/resolwe/pull/309


Discussion:

Resolving patch:
diff --git a/resolwe/permissions/utils.py b/resolwe/permissions/utils.py
--- a/resolwe/permissions/utils.py
+++ b/resolwe/permissions/utils.py
@@ -14,6 +14,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import AnonymousUser, Group
 from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
 
 from guardian.models import GroupObjectPermission, UserObjectPermission
 from guardian.shortcuts import assign_perm, remove_perm
@@ -101,6 +102,35 @@ def update_permission(obj, data):
     content_type = ContentType.objects.get_for_model(obj)
     full_permissions = list(zip(*obj._meta.permissions))[0]  # pylint: disable=protected-access
 
+    def apply_perm(perm_func, perms, entity):
+        """Apply permissions using given ``perm_func``.
+
+        ``perm_func`` is intended to be ``assign_perms`` or
+        ``remove_perms`` shortcut function from ``django-guardian``, but
+        can be any function that accepts permission codename,
+        user/group and object parameters (in this order).
+
+        If given permission does not exist, ``exceptions.ParseError`` is
+        raised.
+
+        "ALL" passed as ``perms`` parameter, will call ``perm_function``
+        with ``full_permissions`` list.
+
+        :param func perm_func: Permissions function to be applied
+        :param list params: list of params to be allpied
+        :param entity: user or group to be passed to ``perm_func``
+        :type entity: `~django.contrib.auth.models.User` or
+            `~django.contrib.auth.models.Group`
+
+        """
+        if perms == u'ALL':
+            perms = full_permissions
+        for perm in perms:
+            perm_codename = '{}_{}'.format(perm.lower(), content_type)
+            if perm_codename not in full_permissions:
+                raise exceptions.ParseError("Unknown permission: {}".format(perm))
+            perm_func(perm_codename, entity, obj)
+
     def set_permissions(entity_type, perm_type):
         """Set object permissions."""
         perm_func = assign_perm if perm_type == 'add' else remove_perm
@@ -110,28 +140,22 @@ def set_permissions(entity_type, perm_type):
             entity = fetch_fn(entity_id)
             if entity:
                 perms = data[entity_type][perm_type][entity_id]
-                if perms == u'ALL':
-                    perms = full_permissions
-                for perm in perms:
-                    perm_func('{}_{}'.format(perm.lower(), content_type), entity, obj)
-
-    set_permissions('users', 'add')
-    set_permissions('users', 'remove')
-    set_permissions('groups', 'add')
-    set_permissions('groups', 'remove')
+                apply_perm(perm_func, perms, entity)
 
     def set_public_permissions(perm_type):
         """Set public permissions."""
         perm_func = assign_perm if perm_type == 'add' else remove_perm
         user = AnonymousUser()
         perms = data.get('public', {}).get(perm_type, [])
-        if perms == u'ALL':
-            perms = full_permissions
-        for perm in perms:
-            perm_func('{}_{}'.format(perm.lower(), content_type), user, obj)
-
-    set_public_permissions('add')
-    set_public_permissions('remove')
+        apply_perm(perm_func, perms, user)
+
+    with transaction.atomic():
+        set_permissions('users', 'add')
+        set_permissions('users', 'remove')
+        set_permissions('groups', 'add')
+        set_permissions('groups', 'remove')
+        set_public_permissions('add')
+        set_public_permissions('remove')
 
 
 def assign_contributor_permissions(obj):


Test patch:
diff --git a/resolwe/permissions/tests/test_permissions.py b/resolwe/permissions/tests/test_permissions.py
--- a/resolwe/permissions/tests/test_permissions.py
+++ b/resolwe/permissions/tests/test_permissions.py
@@ -8,7 +8,7 @@
 from django.contrib.contenttypes.models import ContentType
 
 from guardian.models import GroupObjectPermission, UserObjectPermission
-from guardian.shortcuts import assign_perm
+from guardian.shortcuts import assign_perm, get_perms
 from rest_framework import exceptions, status
 
 from resolwe.flow.models import Collection, Data, Entity, Process
@@ -223,10 +223,10 @@ def test_share_content(self):
         resp = self._detail_permissions(self.collection.pk, data, self.user1)
         self.assertEqual(resp.status_code, status.HTTP_200_OK)
         self.assertEqual(UserObjectPermission.objects.filter(user=self.user2).count(), 3)
-        self.assertTrue(UserObjectPermission.objects.filter(user=self.user2, object_pk=data_1.pk).exists())
-        self.assertFalse(UserObjectPermission.objects.filter(user=self.user2, object_pk=data_2.pk).exists())
-        self.assertTrue(UserObjectPermission.objects.filter(user=self.user2, object_pk=entity_1.pk).exists())
-        self.assertFalse(UserObjectPermission.objects.filter(user=self.user2, object_pk=entity_2.pk).exists())
+        self.assertEqual(len(get_perms(self.user2, data_1)), 1)
+        self.assertEqual(len(get_perms(self.user2, data_2)), 0)
+        self.assertEqual(len(get_perms(self.user2, entity_1)), 1)
+        self.assertEqual(len(get_perms(self.user2, entity_2)), 0)
 
         data = {'users': {'remove': {self.user2.pk: ['view']}}}
         resp = self._detail_permissions(self.collection.pk, data, self.user1)
@@ -238,6 +238,29 @@ def test_share_content(self):
         self.assertEqual(resp.status_code, status.HTTP_200_OK)
         self.assertEqual(UserObjectPermission.objects.filter(user=self.user2).count(), 0)
 
+    def test_nonexisting_permission(self):
+        assign_perm("view_collection", self.owner, self.collection)
+        assign_perm("share_collection", self.owner, self.collection)
+
+        # Add one valid permission to make sure that no permission is applied if any of them is unknown.
+        data = {'users': {'add': {self.user1.pk: ['view'], self.user2.pk: ['view', 'foo']}}}
+        resp = self._detail_permissions(self.collection.pk, data, self.owner)
+        self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(resp.data['detail'], 'Unknown permission: foo')
+        self.assertEqual(UserObjectPermission.objects.filter(user=self.user1).count(), 0)
+        self.assertEqual(UserObjectPermission.objects.filter(user=self.user2).count(), 0)
+
+        assign_perm("view_collection", self.user1, self.collection)
+        assign_perm("view_collection", self.user2, self.collection)
+
+        # Add one valid permission to make sure that no permission is applied if any of them is unknown.
+        data = {'users': {'remove': {self.user1.pk: ['view'], self.user2.pk: ['view', 'foo']}}}
+        resp = self._detail_permissions(self.collection.pk, data, self.owner)
+        self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(resp.data['detail'], 'Unknown permission: foo')
+        self.assertEqual(UserObjectPermission.objects.filter(user=self.user1).count(), 1)
+        self.assertEqual(UserObjectPermission.objects.filter(user=self.user2).count(), 1)
+
 
 class PermissionsUtilitiesTest(TestCase):