Repository: learningequality/kolibri

Issue:
Khang was right - you *can* delete yourself as a superuser

reported by @jonboiser 

This maroons you if you don't have another superuser to reinstate you. (refs #1985)

* go to facility > users
* select your own name (as superuser) > delete yourself
* get logged out
* try to log back in




Discussion:
Hrm, odd. This is specifically banned in the permissions, and has tests to check this case also.
We should add the same check for facility admins to super users also, then, just as an initial fix.
Front end fix was handled in #2298. 

Resolving patch:
diff --git a/kolibri/auth/models.py b/kolibri/auth/models.py
--- a/kolibri/auth/models.py
+++ b/kolibri/auth/models.py
@@ -612,7 +612,7 @@ def can_read(self, obj):
     def can_update(self, obj):
         # Superusers cannot update their own permissions, because they only thing they can do is make themselves
         # not super, we all saw what happened in Superman 2, no red kryptonite here!
-        if self.is_superuser and obj is not self.devicepermissions:
+        if self.is_superuser and obj != self.devicepermissions:
             return True
         # a FacilityUser's permissions are determined through the object's permission class
         if _has_permissions_class(obj):
@@ -622,11 +622,11 @@ def can_update(self, obj):
 
     def can_delete(self, obj):
         # Users cannot delete themselves
-        if self is obj:
+        if self == obj:
             return False
         # Superusers cannot update their own permissions, because they only thing they can do is make themselves
         # not super, we all saw what happened in Superman 2, no red kryptonite here!
-        if self.is_superuser and obj is not self.devicepermissions:
+        if self.is_superuser and obj != self.devicepermissions:
             return True
         # a FacilityUser's permissions are determined through the object's permission class
         if _has_permissions_class(obj):
diff --git a/kolibri/core/device/api.py b/kolibri/core/device/api.py
--- a/kolibri/core/device/api.py
+++ b/kolibri/core/device/api.py
@@ -10,7 +10,7 @@
 class DevicePermissionsViewSet(viewsets.ModelViewSet):
     queryset = DevicePermissions.objects.all()
     serializer_class = DevicePermissionsSerializer
-    permissions_classes = (KolibriAuthPermissions,)
+    permission_classes = (KolibriAuthPermissions,)
     filter_backends = (KolibriAuthPermissionsFilter,)
 
 
diff --git a/kolibri/core/device/permissions.py b/kolibri/core/device/permissions.py
--- a/kolibri/core/device/permissions.py
+++ b/kolibri/core/device/permissions.py
@@ -17,11 +17,11 @@ def user_can_create_object(self, user, obj):
 
     def user_can_update_object(self, user, obj):
         # Superuser cannot commit superuser-suicide
-        return user.is_superuser and obj.user is not user
+        return user.is_superuser and obj.user != user
 
     def user_can_delete_object(self, user, obj):
         # Superuser cannot commit superuser-suicide
-        return user.is_superuser and obj.user is not user
+        return user.is_superuser and obj.user != user
 
 
 class NotProvisionedCanPost(BasePermission):


Test patch:
diff --git a/kolibri/auth/test/test_api.py b/kolibri/auth/test/test_api.py
--- a/kolibri/auth/test/test_api.py
+++ b/kolibri/auth/test/test_api.py
@@ -261,6 +261,24 @@ def test_user_update_password(self):
         self.assertTrue(response)
 
 
+class UserDeleteTestCase(APITestCase):
+
+    def setUp(self):
+        provision_device()
+        self.facility = FacilityFactory.create()
+        self.superuser = create_superuser(self.facility)
+        self.user = FacilityUserFactory.create(facility=self.facility)
+        self.client.login(username=self.superuser.username, password=DUMMY_PASSWORD, facility=self.facility)
+
+    def test_user_delete(self):
+        response = self.client.delete(reverse('facilityuser-detail', kwargs={'pk': self.user.pk}), format="json")
+        self.assertEqual(response.status_code, 204)
+
+    def test_superuser_delete_self(self):
+        response = self.client.delete(reverse('facilityuser-detail', kwargs={'pk': self.superuser.pk}), format="json")
+        self.assertEqual(response.status_code, 403)
+
+
 class LoginLogoutTestCase(APITestCase):
 
     def setUp(self):
diff --git a/kolibri/auth/test/test_permissions.py b/kolibri/auth/test/test_permissions.py
--- a/kolibri/auth/test/test_permissions.py
+++ b/kolibri/auth/test/test_permissions.py
@@ -510,6 +510,10 @@ def test_superuser_can_do_anything_to_a_facility_user(self):
 
         self.assertSetEqual(set(FacilityUser.objects.all()), set(self.superuser.filter_readable(FacilityUser.objects.all())))
 
+    def test_superuser_cannot_delete_self(self):
+        """ superuser can't delete themselves """
+        self.assertFalse(self.superuser.can_delete(self.superuser))
+
 
 class SuperuserPermissionsTestCase(TestCase):
     """
diff --git a/kolibri/core/device/test/test_api.py b/kolibri/core/device/test/test_api.py
--- a/kolibri/core/device/test/test_api.py
+++ b/kolibri/core/device/test/test_api.py
@@ -1,5 +1,6 @@
 from kolibri.auth.constants.role_kinds import ADMIN
-from kolibri.auth.test.helpers import provision_device
+from kolibri.auth.test.test_api import FacilityFactory, FacilityUserFactory
+from kolibri.auth.test.helpers import create_superuser, provision_device
 from kolibri.auth.models import Facility, FacilityDataset, FacilityUser, Role
 from kolibri.core.device.models import DevicePermissions
 
@@ -8,6 +9,8 @@
 from rest_framework import status
 from rest_framework.test import APITestCase
 
+DUMMY_PASSWORD = "password"
+
 class DeviceProvisionTestCase(APITestCase):
 
     superuser_data = {"username": "superuser", "password": "password"}
@@ -99,3 +102,24 @@ def test_dataset_set_created(self):
         self.assertEqual(FacilityDataset.objects.get().learner_can_sign_up, self.dataset_data["learner_can_sign_up"])
         self.assertEqual(FacilityDataset.objects.get().learner_can_delete_account, self.dataset_data["learner_can_delete_account"])
         self.assertEqual(FacilityDataset.objects.get().learner_can_login_with_no_password, self.dataset_data["learner_can_login_with_no_password"])
+
+
+class DevicePermissionsTestCase(APITestCase):
+
+    def setUp(self):
+        provision_device()
+        self.facility = FacilityFactory.create()
+        self.superuser = create_superuser(self.facility)
+        self.user = FacilityUserFactory.create(facility=self.facility)
+        self.client.login(username=self.superuser.username, password=DUMMY_PASSWORD, facility=self.facility)
+
+    def test_superuser_delete_own_permissions(self):
+        response = self.client.delete(reverse('devicepermissions-detail', kwargs={'pk': self.superuser.devicepermissions.pk}), format="json")
+        self.assertEqual(response.status_code, 403)
+
+    def test_superuser_update_own_permissions(self):
+        response = self.client.patch(reverse('devicepermissions-detail',
+                                     kwargs={'pk': self.superuser.devicepermissions.pk}),
+                                     {'is_superuser': False},
+                                     format="json")
+        self.assertEqual(response.status_code, 403)