diff --git a/README.md b/README.md
index 3d00f1d600bc28ecd04a195c797f6da67adcadd0..4fb7d06dafb5a99fa94672914f455f126802a301 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,10 @@
 ```python
 LOGIN_URL = urls.reverse_lazy("sso-dev")
 ```
-- If you want to debug `ssoauth` or need a fully functional SSO during development for some other reason, an example is below. For additional info see production setup chapter and `ssoauth.app_settings.defaults`. If you also want a working SLO during development you will need SSL for your localhost, `nginx` will be your best friend.
+- If you want to debug `ssoauth` or need a fully functional SSO during development for some other reason, an example is below. For additional info see the production setup section and `ssoauth.app_settings.defaults`. If you also want a working SLO during development you will need SSL for your localhost, `nginx` will be your best friend.
+
+#### Advanced development setup
+If you are not sure whether you need it: you don't need it. 
 
 ```python
 """ settings/dev.py """
@@ -64,7 +67,11 @@ After logging out locally, user will be redirected to one of the following (with
     - `LOGOUT_REDIRECT_URL = urls.reverse_lazy("sso-logged-out-locally")`
   - Default: redirect to the view that asks users what to do next. Don't forget to override template `ssoauth/logged_out_locally.html`
 
-**SLO**: SLO/SLS is disabled by default. If you want to try your luck with you should add to your project settings `SP_SLS_ENABLED = True`
+#### Advanced Logging Out and SLO
+
+If unsure, ignore this section.
+
+**SLO**: SLO/SLS is disabled by default. If you want to try your luck with SLO you should add to your project settings `SP_SLS_ENABLED = True`
 
 Currently only IdP-initiated SLO is supported by this app. The only supported binding type is HTTP-Redirect due to the limitations of the underlying library used.
 
@@ -73,13 +80,22 @@ If you have `nginx` serving pages to users, you might need to configure `x-frame
 
 
 #### Groups and Permissions
-To receive groups over SSO you need a group mapping (and of course a properly configured IdP). You can manage group mapping with `group_mapping` management command:
-    
-    group_mapping add myproject_superusers "CN=MyProjectSuperusers,OU=Foo,OU=Bar,DC=fh-h,DC=de"
 
-To generate a working mapping for `hshinfo` groups, use `ssoauth_group_mapping` management command in `syncds` (you can find one on the `sync` server).
+Users receive groups using SSO. For this to work, you need:
+  - some groups in your django project (see `django.contrib.auth` groups)
+  - groups with exactly the same names provided by the IDP
+    - create a group in the IDM
+    - make sure IDM provides it to the IDP
+    - make sure IDP provides it to your SP
+  - you might want to predefine some groups in the project settings (see `ssoauth` default config for details)
+    - these groups will be created automatically (when migrating) and will receive the specified permissions
+    - e.g. you probably want a superuser group, see the example below
 
-*Groups are not mapped automatically. The reason is that automatic mapping can pose security risks. Imagine auto-mapping that expects group with name "Superusers"; an intruder could create new group with this name under any path they own and/or create an alias/reference and receive superuser permissions in your project.* 
+```python
+PREDEFINED_GROUPS = {
+    "my_project_superusers": [ssoauth.SUPERUSER_PERM_CODENAME],
+}
+```
 
 
 #### Production Settings
@@ -125,9 +141,8 @@ openssl req -newkey rsa:16384 -x509 -days 3650 -nodes -out sp.pem -keyout sp.key
     - create a new `<MetadataProvider .../>` element, your best guess is to copy an existing line that belongs to some existing Django project
     - `id` should be unique
     - `metadataFile` should point on your new metadata
-   - edit `./conf/attribute-filter.xml`
-     - add a new `<Rule .../>` element inside of `<AttributeFilterPolicy id="releaseToDjango">`
-     - `value` (looks like URI) should be the `entityID` of your SP (you find it in your meta)
-  - `systemctl restart tomcat8` (you now have some time to grab you a coffee)
-  - ensure the IdP works after restarting!
-
+    - edit `./conf/attribute-filter.xml`
+      - use your head
+  - `systemctl restart tomcat8`
+  - make sure that IDP works
+    - if IDP does not work, rollback your changes and restart the IDP again
diff --git a/ssoauth/__init__.py b/ssoauth/__init__.py
index 76976b99993e528e2d4ff241dba366c61c897ba5..8c3c79bd083a32ddd059ce859af316df4cde1d32 100644
--- a/ssoauth/__init__.py
+++ b/ssoauth/__init__.py
@@ -1,10 +1,11 @@
 import logging
 
-from . import checks  # As for Django 1.11 it still doesn't auto-import checks >.<
-assert checks  # must be imported
-
-
 logger = logging.getLogger("ssoauth")
 
 default_app_config = "ssoauth.apps.SSOAuthConfig"
 
+
+# permission codenames for builtin django features
+STAFF_PERM_CODENAME = "staff"
+SUPERUSER_PERM_CODENAME = "superuser"
+
diff --git a/ssoauth/app_settings/defaults.py b/ssoauth/app_settings/defaults.py
index 5cf1fadbd31fe306754135c5950c6faaf9b7d8b9..f6dd7f2fd9e243bb01fcf73b089b8ce589baba42 100644
--- a/ssoauth/app_settings/defaults.py
+++ b/ssoauth/app_settings/defaults.py
@@ -31,7 +31,7 @@ SSO_REQUIRED_OUTSIDE_MANAGE_PY = True  # enabled to ensure that production (that
 SP_SLS_ENABLED = False  # single log out creates too many problems, so it is disabled for now
 SP_SLS_X_FRAME_OPTIONS = None  # in case you encounter problems with SLS view not allowed inside of an iframe, e.g. "ALLOW-FROM idp-test.it.hs-hannover.de idp.hs-hannover.de"
 
-GROUP_RESOLVER = "ssoauth.auth_utils.groups_from_raw_saml2_values"  # in case you want to override how groups are resolved for users
+GROUP_RESOLVER = "ssoauth.auth_utils.groups_from_group_names"  # in case you want to override how groups are resolved for users
 
 LOGIN_PERM_CODENAME = None  # None or str; value "can_log_in" will require this permission for users to log in
 
@@ -47,8 +47,8 @@ PREDEFINED_GROUPS = {
     # Give your local groups the same name as the AuthGroup they will be mapped to
     # (e.g. your local group for students will be named IDM_Studierende)
     #
-    # Example:
-    # {"IDM_Studierende": ["perm_codename", "another_perm_codename"]}
+    # {"example_group": ["perm_codename", "another_perm_codename"]}
+    # {"superusers": [ssoauth.SUPERUSER_PERM_CODENAME]}
 }
 
 
@@ -70,12 +70,6 @@ SP_METADATA_LIFETIME_DAYS = 365 * 20
 
 PROJECT_NAME = os.environ.get('DJANGO_SETTINGS_MODULE').split('.')[0]
 
-# 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
 
 # the block below defines from which SAML2 attributes this SP receives user data
diff --git a/ssoauth/apps.py b/ssoauth/apps.py
index 12c3ed40ed56c39f492b6f84fc82eed9efc13752..891a8cc0b74e67af3dc387c0e4da53813534415b 100644
--- a/ssoauth/apps.py
+++ b/ssoauth/apps.py
@@ -5,12 +5,15 @@ from django.db.models.signals import post_migrate
 from . import app_settings
 from . import logger
 from . import sso_utils
+from . import checks  # As for Django 1.11 it still doesn't auto-import checks >.<
 
 
 class SSOAuthConfig(AppConfig):
     name = "ssoauth"
 
     def ready(self, *args, **kwargs):
+        if not checks:
+            raise RuntimeError("Checks are not imported.")
         super().ready(*args, **kwargs)
         # OneLogin settings stuff
         try:
@@ -26,8 +29,8 @@ class SSOAuthConfig(AppConfig):
 
     @staticmethod
     def post_migrate_callback(*args, **kwargs):
-        # compatibility groups and permissions
-        management.call_command("create_compat_groups")
-        # predefined groups and permissions
+        # first, let django create its own permissions
         create_permissions(*args, **kwargs)  # calling create_permissions() before using the permissions
-        management.call_command("create_custom_groups")
+        # custom and compatibility groups and permissions
+        management.call_command("ssoauth_setup_groups_and_perms")
+
diff --git a/ssoauth/auth_utils.py b/ssoauth/auth_utils.py
index 5a501cc776d75f1956d6a33e7e05bc2ab36a45ea..4e785f9e404f52397ef4e3e9edf5c838e131a2a0 100644
--- a/ssoauth/auth_utils.py
+++ b/ssoauth/auth_utils.py
@@ -1,14 +1,12 @@
 from django.db import transaction
-from django.db.models import Q
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import Group
 from uuid import UUID
 from . import models
 from . import logger
 from . import app_settings
-import functools
+from . import SUPERUSER_PERM_CODENAME, STAFF_PERM_CODENAME
 import importlib
-import re
 
 
 def _validate_username(username):
@@ -118,35 +116,18 @@ def set_user_groups(user, raw_values):
         assert set(user.groups.all()) == groups
         logger.info("Groups for {user} were updated.".format(user=user))
     # done
-    logger.info("User {user} is member of: {groups}".format(user=user, groups=set(str(g) for g in groups)))
+    logger.info("User {user} is member of: {groups}".format(user=user, groups=[str(g) for g in groups]))
 
 
-def groups_from_raw_saml2_values(user, raw_values):
-    """ This default group resolver returns list of Group objects based on DN list """
-    # using Q to create ignore-case DN lookup since DS is case insensitive
-
-    def from_dn_list(dn_list):
-        logger.warning("Receiving groups from DN list is deprecated. Reconfigure the project to receive group names instead.")
-        q_list = [Q(sso_mapping__dn__iexact=dn) for dn in dn_list]
-        if q_list:
-            q_combined = functools.reduce(lambda q1, q2: q1 | q2, q_list)
-            groups = set(Group.objects.filter(q_combined))
-        else:
-            groups = set()
-        return groups
-
-    def from_group_name_list(group_names):
-        groups = set(Group.objects.filter(name__in=group_names))
-        group_names_resolved = {g.name for g in groups}
-        group_names_redundant = set(group_names) - group_names_resolved
-        if group_names_redundant:
-            logger.warning("Received redundant group names (clean up the configs): {0}".format(group_names_redundant))
-        return groups
-
-    if raw_values and bool(re.match('^\w+=\w+', str(raw_values[0]))):  # if looks like DN
-        return from_dn_list(raw_values)
-    else:
-        return from_group_name_list(raw_values)
+def groups_from_group_names(user, saml2_raw_values):
+    """ This default group resolver returns list of Group objects based on group names as raw_values """
+    groups = set(Group.objects.filter(name__in=saml2_raw_values))
+    # perform some sanity checks
+    group_names_redundant = set(saml2_raw_values) - {g.name for g in groups}
+    if group_names_redundant:
+        logger.warning("Received redundant group names (clean up the configs): {0}".format(group_names_redundant))
+    # done
+    return groups
 
 
 def cleanup_direct_permissions(user):
@@ -155,41 +136,41 @@ def cleanup_direct_permissions(user):
         user.user_permissions.clear()
 
 
-def set_user_compat_flags(user, check_login_perm=True):
+def get_superuser_groups():
+    """ :return: QuerySet with all groups that have superuser permission """
+    return Group.objects.filter(permissions__codename=SUPERUSER_PERM_CODENAME)
+
+
+def update_user_compat_flags(user, check_login_perm=True):
 
     def get_full_perm_name(codename):
         return "{app}.{codename}".format(
             app=get_user_model()._meta.app_label,
             codename=codename,
         )
-
     # defaults are restrictive
     user.is_staff = False
     user.is_superuser = False
-    # is_active must be initially set to True, otherwise has_perm() will not function
+    # is_active must be initially set to True, otherwise has_perm() will not function (e.g. for is_staff)
     user.is_active = True
-    # is_superuser
-    try:
-        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_superuser = True
-    except Group.DoesNotExist as e:
-        logger.error("Could not fetch the superuser group. Forgot to migrate?")
+    # is_superuser (carefully resolving using groups, not using has_perm because checks is_superuser internally)
+    user_groups = user.groups.all()
+    su_groups = get_superuser_groups()
+    if bool(set(user_groups) & set(su_groups)):
+        user.is_superuser = True
     # is_staff
-    if user.has_perm(get_full_perm_name(app_settings.STAFF_PERM_CODENAME)):
-        logger.info("User {user} is staff (has admin access).".format(**locals()))
+    if user.has_perm(get_full_perm_name(STAFF_PERM_CODENAME)):
+        logger.info("User {0} is staff (has admin access).".format(user))
         user.is_staff = True
     # is_active (actually represents the log in permission)
     if app_settings.LOGIN_PERM_CODENAME and check_login_perm:
         if user.has_perm(get_full_perm_name(app_settings.LOGIN_PERM_CODENAME)):
-            logger.info("User {user} is active.".format(**locals()))
+            logger.info("User {0} is active.".format(user))
             user.is_active = True
         else:
-            logger.info("User {user} is inactive (does not have LOGIN_PERM_CODENAME).".format(**locals()))
+            logger.info("User {0} is inactive - does not have login permission {1}.".format(user, app_settings.LOGIN_PERM_CODENAME))
             user.is_active = False
     else:
         user.is_active = True  # without LOGIN_PERM_CODENAME every SSO user is considered active
     # done
     user.save()
-
diff --git a/ssoauth/checks.py b/ssoauth/checks.py
index 93c054f0e8869c5bbac75575adeda99f6cfaff30..846eab937d7ea4e032b19f6ff241ed96b2eb52d2 100644
--- a/ssoauth/checks.py
+++ b/ssoauth/checks.py
@@ -1,9 +1,11 @@
 from django.core.checks import Tags, Error, Warning, register
 from django.contrib.auth import get_user_model
 from django.db.utils import OperationalError, ProgrammingError
+from django.apps import apps
 from django import conf
 from django import urls
 from . import app_settings
+from . import SUPERUSER_PERM_CODENAME
 
 
 def _get_abstract_user():
@@ -35,6 +37,29 @@ def no_passwords_stored(app_configs, **kwargs):
     return errors
 
 
+@register(Tags.security)
+@_ignore_db_errors
+def not_too_many_superuser_groups(app_configs, **kwargs):
+    errors = list()
+    group_model = apps.get_model("auth", "Group")
+    su_groups = group_model.objects.filter(permissions__codename=SUPERUSER_PERM_CODENAME)
+    if len(su_groups) > 1:
+        errors.append(Error("There are too many superuser groups: {0}".format(su_groups)))
+        for g in su_groups:
+            g.permissions.clear()
+            errors.append(Error("Purged permissions of group {0}".format(g), obj=group_model))
+    return errors
+
+
+@register(Tags.compatibility)
+@_ignore_db_errors
+def deprecated_su_group_name(app_configs, **kwargs):
+    errors = list()
+    if hasattr(conf.settings, "SSOAUTH_SUPERUSER_GROUP_NAME") or hasattr(conf.settings, "SUPERUSER_GROUP_NAME"):
+        errors.append(Warning("[SSOAUTH_]SUPERUSER_GROUP_NAME is deprecated and will be ignored. Create your own superuser group using [SSOAUTH_]PREDEFINED_GROUPS.", obj=conf.settings))
+    return errors
+
+
 @register(Tags.compatibility)
 def compatible_user_model(app_configs, **kwargs):
     errors = list()
diff --git a/ssoauth/management/commands/create_compat_groups.py b/ssoauth/management/commands/create_compat_groups.py
deleted file mode 100644
index ce7cd0fe5785dda9295e31fade60a854ba807f43..0000000000000000000000000000000000000000
--- a/ssoauth/management/commands/create_compat_groups.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group, Permission
-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 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.SUPERUSER_GROUP_NAME)
-            self.ensure_permission_exists(app_settings.STAFF_PERM_CODENAME)
-            if app_settings.LOGIN_PERM_CODENAME:
-                self.ensure_permission_exists(app_settings.LOGIN_PERM_CODENAME)
-        except Exception as e:
-            raise CommandError("Could not ensure that compatibility groups and permissions exist. {0}".format(str(e)))
-
-    @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).
-        """
-        group, created = Group.objects.get_or_create(name=group_name)
-        if created:
-            logger.info("Created group '{}'".format(group))
-        for permission_name in permission_names:
-            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/management/commands/create_custom_groups.py b/ssoauth/management/commands/create_custom_groups.py
deleted file mode 100644
index 28eb0f6dd3b38e6233f0266e934f2a6b33a51e76..0000000000000000000000000000000000000000
--- a/ssoauth/management/commands/create_custom_groups.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from django.core.management.base import BaseCommand, CommandError
-from django.apps import apps
-from django.contrib.auth import get_user_model
-
-from ... import app_settings
-from ... import logger
-
-
-def setup_groups():
-    """
-    Creates groups and permissions as specified in your project settings.
-    """
-    # grab the required models
-    User = get_user_model()
-    Group = apps.get_model("auth", "Group")
-    ContentType = apps.get_model("contenttypes", "ContentType")
-    Permission = apps.get_model("auth", "Permission")
-
-    for group_name, permission_names in app_settings.PREDEFINED_GROUPS.items():
-        group, created_group = Group.objects.get_or_create(name=group_name)
-        if created_group:
-            logger.info("Created group \"{}\"".format(group_name))
-        for perm_name in permission_names:
-            try:
-                perm = Permission.objects.get(
-                    codename=perm_name,
-                    content_type_id=ContentType.objects.get_for_model(User).id
-                )
-            except Permission.DoesNotExist:
-                perm = Permission.objects.create(
-                    codename=perm_name,
-                    name=perm_name,
-                    content_type_id=ContentType.objects.get_for_model(User).id
-                )
-                logger.info("Created permission \"{}\"".format(perm_name))
-            if perm not in group.permissions.all():
-                group.permissions.add(perm)
-                logger.info("Added permission \"{}\" to group \"{}\"".format(perm_name, group_name))
-
-
-class Command(BaseCommand):
-    help = "Creates groups and permissions, predefined by user in project settings."
-    requires_migrations_checks = True
-    requires_system_checks = True
-
-    def handle(self, *args, **options):
-        try:
-            setup_groups()
-        except Exception as e:
-            raise CommandError("Could not ensure that compatibility groups and permissions exist. {0}".format(str(e)))
-
diff --git a/ssoauth/management/commands/group_mapping.py b/ssoauth/management/commands/group_mapping.py
deleted file mode 100644
index 9a85ddeffbf258df72dab794b46c87e36e23c2cf..0000000000000000000000000000000000000000
--- a/ssoauth/management/commands/group_mapping.py
+++ /dev/null
@@ -1,119 +0,0 @@
-from django.core.management.base import BaseCommand, CommandError
-from django.contrib.auth.models import Group, Permission
-from ... models import GroupMapping
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.auth import get_user_model
-from ... import app_settings
-from ... import logger
-
-
-class Command(BaseCommand):
-    help = "Modify group mapping. You can also do it from your project admin."
-    requires_migrations_checks = True
-    requires_system_checks = True
-
-    def add_arguments(self, parser):
-        parser.add_argument(
-            "action",
-            action="store",
-            choices=["list", "add", "del", "?"],
-            help="action",
-        )
-        parser.add_argument(
-            "name",
-            action="store",
-            default=None,
-            nargs="?",
-            help="local group name (in this project)",
-        )
-        parser.add_argument(
-            "dn",
-            action="store",
-            default=None,
-            nargs="?",
-            help="group DN in a directory as returned by Shibboleth",
-        )
-
-    def handle(self, *args, **options):
-        action, name, dn = options["action"], options["name"], options["dn"]
-        if action == "list":
-            self.print_mapping()
-        elif action == "add":
-            if not name or not dn:
-                return logger.error("Need group name and DN.")
-            self.add_mapping(name, dn)
-            self.print_mapping()
-        elif action == "del":
-            if not name:
-                return logger.error("Need group name.")
-            self.delete_mapping(name)
-        else:
-            self.print_help(str(), str())
-
-    def print_mapping(self):
-        groups = Group.objects.all()
-        mapped = groups.filter(sso_mapping__isnull=False)
-        unmapped = groups.filter(sso_mapping__isnull=True)
-        logger.info("There are {g} groups ({m} mapped, {u} unmapped):".format(g=len(groups), m=len(mapped), u=len(unmapped)))
-        for group in groups.order_by("-sso_mapping", "name"):
-            name = group.name
-            try:
-                dn = group.sso_mapping.dn
-            except GroupMapping.DoesNotExist:
-                dn = None
-            logger.info("    {name:<50} {dn}".format(**locals()))
-
-    def delete_mapping(self, group_name):
-        mapping = self._get_mapping_for(group_name)
-        if mapping:
-            logger.info("Deleting {mapping}.".format(**locals()))
-            mapping.delete()
-        else:
-            logger.error("There is no mapping for {group_name}.".format(**locals()))
-
-    def add_mapping(self, group_name, dn):
-        # group
-        try:
-            group = Group.objects.get(name__iexact=group_name)
-            logger.info("Existing group {group} will be used.".format(**locals()))
-        except Group.DoesNotExist:
-            logger.info("Group {group_name} will be created.".format(**locals()))
-            group = Group.objects.create(name=group_name)
-        # mapping
-        existing = self._get_mapping_for(group_name)
-        if existing:
-            logger.warning("Removing existing mapping {existing}".format(**locals()))
-            existing.delete()
-        new = GroupMapping.objects.create(group=group, dn=dn)
-        logger.info("Created {new}.".format(**locals()))
-
-    def _get_mapping_for(self, group_name):
-        try:
-            return GroupMapping.objects.get(group__name__iexact=group_name)
-        except GroupMapping.DoesNotExist:
-            return None
-
-
-    ## seems like the following code can create groups and permissions as we had it
-    ## in the old There hshauth, based on the project settings
-    #
-    # @staticmethod
-    # def ensure_group_exists(group_name, permission_names=list()):
-    #     """
-    #     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).
-    #     """
-    #     group, created = Group.objects.get_or_create(name=group_name)
-    #     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))
-    #         if permission not in group.permissions.all():
-    #             group.permissions.add(permission)
-    #             logger.info("Added permission '{1}' to group '{0}'".format(group, permission.codename))
diff --git a/ssoauth/management/commands/ssoauth_setup_groups_and_perms.py b/ssoauth/management/commands/ssoauth_setup_groups_and_perms.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c74434921979b88733a19428f9ba693b6b8d214
--- /dev/null
+++ b/ssoauth/management/commands/ssoauth_setup_groups_and_perms.py
@@ -0,0 +1,62 @@
+from django.core.management.base import BaseCommand, CommandError
+from django.contrib.auth import get_user_model
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.auth.models import Group, Permission
+from ... import app_settings
+from ... import logger
+from ... import SUPERUSER_PERM_CODENAME, STAFF_PERM_CODENAME
+
+
+def get_user_content_type():
+    return ContentType.objects.get_for_model(get_user_model())
+
+
+def get_or_create_permission(codename, create_with_name=None):
+    """
+    :return: permission object
+    Ensures the permissions exists. Creates it if needed.
+    """
+    try:
+        perm = Permission.objects.get(codename=codename)
+    except Permission.DoesNotExist:
+        perm = Permission.objects.create(
+            codename=codename,
+            name=create_with_name or codename,
+            content_type=get_user_content_type()
+        )
+        logger.info("Created permission: {0}".format(perm))
+    return perm
+
+
+def get_or_create_group(name):
+    group, created = Group.objects.get_or_create(name=name)
+    if created:
+        logger.info("Created group: {0}".format(group))
+    return group
+
+
+def setup_predefined_groups():
+    for group_name, perm_codename in app_settings.PREDEFINED_GROUPS.items():
+        group = get_or_create_group(group_name)
+        for perm_codename in perm_codename:
+            perm = get_or_create_permission(perm_codename)
+            if perm not in group.permissions.all():
+                group.permissions.add(perm)
+                logger.info("Added permission {0} to group {1}".format(perm, group))
+
+
+class Command(BaseCommand):
+    help = "Set up groups and permissions (both predefined and compatibility). Runs automatically after migrations."
+    requires_migrations_checks = True
+    requires_system_checks = True
+
+    def handle(self, *args, **options):
+        try:
+            # compat permissions
+            for codename in [SUPERUSER_PERM_CODENAME, STAFF_PERM_CODENAME]:
+                get_or_create_permission(codename, "{0} (SSO)".format(codename))
+            # predefined groups
+            setup_predefined_groups()
+        except Exception as e:
+            raise CommandError("Could not set up groups and permissions: {0}".format(e))
+
diff --git a/ssoauth/migrations/0002_auto_20190306_1823.py b/ssoauth/migrations/0002_auto_20190306_1823.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e589ea41add06b2859c3bf6b9e2ac7f1935eeb3
--- /dev/null
+++ b/ssoauth/migrations/0002_auto_20190306_1823.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.1.7 on 2019-03-06 18:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ssoauth', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='groupmapping',
+            name='group',
+        ),
+        migrations.DeleteModel(
+            name='GroupMapping',
+        ),
+    ]
diff --git a/ssoauth/models.py b/ssoauth/models.py
index 30b0f66400b18958519b547bf09271587f296b62..5bc2b06e2e0fa094eb577d7260365acff31ff8bd 100644
--- a/ssoauth/models.py
+++ b/ssoauth/models.py
@@ -11,12 +11,3 @@ class UserMapping(models.Model):
     def __str__(self):
         return "{user} <-> {uuid}".format(user=self.user, uuid=self.uuid)
 
-
-class GroupMapping(models.Model):
-    group = models.OneToOneField("auth.Group", null=False, on_delete=models.CASCADE, related_name="sso_mapping")
-    dn = models.CharField(max_length=1000, null=False, blank=False)
-    created_on = models.DateField(null=False, auto_now_add=True)
-
-    def __str__(self):
-        return "{group} <-> \"{dn}\"".format(group=self.group, dn=self.dn)
-
diff --git a/ssoauth/views.py b/ssoauth/views.py
index fcab98b46113da5519bb250d1c7befbc87972dbc..d61f74793341e595dd49610fec4f452356751f75 100644
--- a/ssoauth/views.py
+++ b/ssoauth/views.py
@@ -203,7 +203,7 @@ class ACSAuthNView(SAMLMixin, View):
         )
         # other necessary steps
         auth_utils.cleanup_direct_permissions(user=user)  # in case somebody was acting funny
-        auth_utils.set_user_compat_flags(user=user)  # superuser, staff
+        auth_utils.update_user_compat_flags(user=user)  # superuser, staff
         user.backend = app_settings.PRETEND_AUTH_BACKEND
         request.user = user
         if user.is_active:
@@ -350,7 +350,7 @@ class DevView(FormView):
             contrib_auth.logout(self.request)
         # update the compat flags, might be needed when user or their groups change
         if self.request.user.is_authenticated:
-            auth_utils.set_user_compat_flags(self.request.user, False)
+            auth_utils.update_user_compat_flags(self.request.user, False)
         # redirect
         return http.HttpResponseRedirect(self.next_url or urls.reverse("sso-dev"))