From 8ec9f3fd6671669a7b9743e8e096c46059be52ef Mon Sep 17 00:00:00 2001
From: Art Lukyanchyk <artiom.lukyanchyk@hs-hannover.de>
Date: Tue, 30 Jan 2018 18:50:49 +0100
Subject: [PATCH] Make the staff permission/flag work in realistic situations

---
 ssoauth/app_settings/defaults.py              | 10 +++--
 ssoauth/auth_utils.py                         | 24 +++++++-----
 .../commands/create_compat_groups.py          | 39 +++++++++++++------
 ssoauth/templates/ssoauth/dev.html            |  6 ++-
 ssoauth/views.py                              |  2 +-
 5 files changed, 54 insertions(+), 27 deletions(-)

diff --git a/ssoauth/app_settings/defaults.py b/ssoauth/app_settings/defaults.py
index 4629eec..9972386 100644
--- a/ssoauth/app_settings/defaults.py
+++ b/ssoauth/app_settings/defaults.py
@@ -1,5 +1,4 @@
 import os
-from django import urls
 from django.conf import settings as django_settings
 
 
@@ -54,7 +53,7 @@ GROUPS = getattr(django_settings, "LOCAL_GROUPS", {
 Settings you might want to change on development (don't change them for production):
 """
 
-SP_FORCE_ENTITY_ID = None  # do NOT set for production, set to some unique string on development
+SP_FORCE_ENTITY_ID = None  # do NOT set for production, set to some unique string on development if SSO is enabled
 
 
 """
@@ -68,8 +67,11 @@ SP_METADATA_LIFETIME_DAYS = 365 * 20
 
 PROJECT_NAME = os.environ.get('DJANGO_SETTINGS_MODULE').split('.')[0]
 
-SU_GROUP_NAME = "{0}_superusers".format(PROJECT_NAME)
-STAFF_GROUP_NAME = "{0}_staff".format(PROJECT_NAME)
+# superusers is a group to have the best possible overview of them
+SUPERUSER_GROUP_NAME = "{0}_superusers".format(PROJECT_NAME)
+
+# staff is a permission because a group would normally combine staff and permissions over some models
+STAFF_PERM_CODENAME = "staff"
 
 PRETEND_AUTH_BACKEND = django_settings.AUTHENTICATION_BACKENDS[0]  # pretend to be this backend; django does not expect that it is possible to log in without an authentication backend
 
diff --git a/ssoauth/auth_utils.py b/ssoauth/auth_utils.py
index 3e6a9c4..7354615 100644
--- a/ssoauth/auth_utils.py
+++ b/ssoauth/auth_utils.py
@@ -137,20 +137,26 @@ def cleanup_direct_permissions(user):
 
 
 def set_user_compat_flags(user, user_is_active=True):
-    user.is_active = user_is_active
+    user.is_active = user_is_active  # cannot do much with it, by default activates all users that came from SSO
+    # defaults are restrictive
     user.is_staff = False
     user.is_superuser = False
+    # superuser
     try:
-        group_su = Group.objects.get(name=app_settings.SU_GROUP_NAME)
-        group_staff = Group.objects.get(name=app_settings.STAFF_GROUP_NAME)
+        group_su = Group.objects.get(name=app_settings.SUPERUSER_GROUP_NAME)
         if group_su in user.groups.all():
             logger.info("User {user} is superuser".format(**locals()))
-            user.is_staff = True
             user.is_superuser = True
-        if group_staff in user.groups.all():
-            logger.info("User {user} is staff (has admin access)".format(**locals()))
-            user.is_staff = True
-    except Group.DoesNotExist:
-        logger.error("No compat groups. Migrate or run management command create_compat_groups.")
+    except Group.DoesNotExist as e:
+        logger.error("Could not fetch the superuser group. Forgot to migrate?")
+    # staff
+    full_staff_perm_name = "{app}.{codename}".format(
+        app=get_user_model()._meta.app_label,
+        codename=app_settings.STAFF_PERM_CODENAME,
+    )
+    if user.has_perm(full_staff_perm_name):
+        logger.info("User {user} is staff (has admin access)".format(**locals()))
+        user.is_staff = True
+    # done
     user.save()
 
diff --git a/ssoauth/management/commands/create_compat_groups.py b/ssoauth/management/commands/create_compat_groups.py
index 19e5975..65d3d0f 100644
--- a/ssoauth/management/commands/create_compat_groups.py
+++ b/ssoauth/management/commands/create_compat_groups.py
@@ -1,26 +1,27 @@
 from django.core.management.base import BaseCommand, CommandError
 from django.contrib.auth.models import Group, Permission
-from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth import get_user_model
+from django.contrib.contenttypes.models import ContentType
 from ... import app_settings
 from ... import logger
 
 
 class Command(BaseCommand):
-    help = "Creates groups required for django.contrib.auth compatibility."
+    help = "Creates groups and permissions for django.contrib.auth compatibility."
     requires_migrations_checks = True
     requires_system_checks = True
 
     def handle(self, *args, **options):
         try:
-            self.ensure_group_exists(app_settings.SU_GROUP_NAME)
-            self.ensure_group_exists(app_settings.STAFF_GROUP_NAME)
+            self.ensure_group_exists(app_settings.SUPERUSER_GROUP_NAME)
+            self.ensure_permission_exists(app_settings.STAFF_PERM_CODENAME)
         except Exception as e:
             raise CommandError("Could not ensure that compatibility groups and permissions exist. {0}".format(str(e)))
 
-    @staticmethod
-    def ensure_group_exists(group_name, permission_names=list()):
+    @classmethod
+    def ensure_group_exists(cls, group_name, permission_names=list()):
         """
+        :return: group object
         Ensures the group with the given permissions exists.
         Creates the group and/or permissions if needed.
         Adds permissions to the group if needed (but never removes any).
@@ -29,12 +30,26 @@ class Command(BaseCommand):
         if created:
             logger.info("Created group '{}'".format(group))
         for permission_name in permission_names:
-            permission, created = Permission.objects.get_or_create(
-                codename=permission_name,
-                content_type_id=ContentType.objects.get_for_model(get_user_model()).id,
-            )
-            if created:
-                logger.info("Created permission '{}'".format(permission.codename))
+            permission = cls.ensure_permission_exists(permission_name)
             if permission not in group.permissions.all():
                 group.permissions.add(permission)
                 logger.info("Added permission '{1}' to group '{0}'".format(group, permission.codename))
+        return group
+
+    @staticmethod
+    def ensure_permission_exists(codename):
+        """
+        :return: permission object
+        Ensures the permissions exists. Creates it if needed.
+        """
+        user_content_type = ContentType.objects.get_for_model(get_user_model())
+        permission, created = Permission.objects.get_or_create(
+            codename=codename,
+            content_type_id=user_content_type.pk,
+        )
+        if created:
+            permission.name = "{0} (ssoauth)".format(codename)
+            permission.save()
+            logger.info("Created permission '{}'".format(permission))
+        return permission
+
diff --git a/ssoauth/templates/ssoauth/dev.html b/ssoauth/templates/ssoauth/dev.html
index 3d3ddb8..2602c2d 100644
--- a/ssoauth/templates/ssoauth/dev.html
+++ b/ssoauth/templates/ssoauth/dev.html
@@ -52,7 +52,11 @@
                 </div>
             </form>
             {% if next_url %}<p class="message">You will be redirected to: <strong>{{ next_url }}</strong></p>{% endif %}
-            {% if user.is_superuser %}<p class="message">You are a superuser</p>{% endif %}
+            {% if user.is_superuser %}
+                <p class="message">You are a superuser</p>
+            {% elif user.is_staff %}
+                <p class="message">You are a staff member</p>
+            {% endif %}
         </section>
 
         {% for table_name, table_contents in tables %}
diff --git a/ssoauth/views.py b/ssoauth/views.py
index 48f22f5..58df4eb 100644
--- a/ssoauth/views.py
+++ b/ssoauth/views.py
@@ -338,7 +338,7 @@ class DevView(FormView):
             else:
                 logger.warning("Too anonymous to join groups.")
         elif local_logout:
-            logger.info("Logging out")
+            logger.info("Logging out {u}".format(u=self.request.user))
             contrib_auth.logout(self.request)
         # update the compat flags, might be needed when user or their groups change
         if self.request.user.is_authenticated:
-- 
GitLab