diff --git a/aai/apps.py b/aai/apps.py
index bd5f7be7e18346b0cac7535a603ae6e39b5434dd..01fcb37054d5d5c6aa89db03a1f7fc1ed8d0f863 100644
--- a/aai/apps.py
+++ b/aai/apps.py
@@ -4,6 +4,3 @@ from django.apps import AppConfig
 class AaiConfig(AppConfig):
     default_auto_field = "django.db.models.BigAutoField"
     name = "aai"
-
-    def ready(self):
-        import aai.management.commands.assignperms
diff --git a/aai/backend.py b/aai/backend.py
index 40928fb54afb42f1ab1ee6f04381562803e0e370..fc247c7afb89a4bde9b0b23c1b2dbd0f4faf66d8 100644
--- a/aai/backend.py
+++ b/aai/backend.py
@@ -21,7 +21,7 @@ class CustomAuthBackend(ModelBackend):
             )
             raise AuthenticationFailed("Username or password is missing")
         try:
-            user = UserModel._default_manager.get_by_natural_key(username)
+            user: User = UserModel._default_manager.get_by_natural_key(username)
         except UserModel.DoesNotExist:
             # Run the default password hasher once to reduce the timing
             # difference between an existing and a nonexistent user.
diff --git a/aai/management/__init__.py b/aai/management/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/aai/management/commands/__init__.py b/aai/management/commands/__init__.py
deleted file mode 100644
index 87f235c0666d98aff19c03802b176f78bbf740f4..0000000000000000000000000000000000000000
--- a/aai/management/commands/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = "aai.apps.AaiConfig"
diff --git a/aai/management/commands/assignperms.py b/aai/management/commands/assignperms.py
deleted file mode 100644
index 35c77c423c5c450f5462788b4b6ab473dfa3474e..0000000000000000000000000000000000000000
--- a/aai/management/commands/assignperms.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from typing import Any
-import sys
-
-from django.core.management.base import BaseCommand
-from django.contrib.auth.models import Permission, Group
-from django.db.models.signals import post_migrate
-from django.dispatch import receiver
-
-from aai.models import UserGroup, Perms
-
-migrated_apps = set()
-required_migrations = {"user", "aai", "django.contrib.auth"}
-DONE = False
-
-
-# Due to unit tests pipeline this has to be temporarly automatic
-@receiver(post_migrate)
-def assign_perms_after_migration(sender, **kwargs):
-    global DONE, migrated_apps, required_migrations
-    if DONE:
-        return
-
-    migrated_apps.add(sender.name)
-    if required_migrations.issubset(migrated_apps):
-        assign_perms()
-        DONE = True
-
-
-def assign_perms():
-    # Initialize groups
-    trainee, _ = Group.objects.get_or_create(name=UserGroup.TRAINEE)
-    instructor, _ = Group.objects.get_or_create(name=UserGroup.INSTRUCTOR)
-    _, _ = Group.objects.get_or_create(name=UserGroup.ADMIN)
-
-    #### trainee permissions assignemnt ####
-    trainee_perm_names = [
-        Perms.view_exercise.codename,
-        Perms.use_tool.codename,
-        Perms.send_email.codename,
-        Perms.view_trainee_info.codename,
-        Perms.view_email_info.codename,
-        Perms.view_email.codename,
-        Perms.manipulate_file.codename,
-    ]
-    trainee_perms = Permission.objects.filter(codename__in=trainee_perm_names)
-    trainee.permissions.add(*trainee_perms)
-    instructor.permissions.add(*trainee_perms)
-    trainee.save()
-
-    #### instructor permissions assignment ####
-    instructor_perm_names = [
-        Perms.update_exercise.codename,
-        Perms.update_definition.codename,
-        Perms.view_definition.codename,
-        Perms.view_category.codename,
-        Perms.view_milestone.codename,
-        Perms.send_injectselection.codename,
-        Perms.view_extendtool.codename,
-        Perms.view_injectselection.codename,
-        Perms.view_analytics.codename,
-        Perms.update_userassignment.codename,
-        Perms.view_user.codename,
-        Perms.update_user.codename,
-    ]
-    instructor_perms = Permission.objects.filter(
-        codename__in=instructor_perm_names
-    )
-    instructor.permissions.add(*instructor_perms)
-    instructor.save()
-
-    sys.stderr.write("Predefined groups and permissions have been set in DB\n")
-
-
-class Command(BaseCommand):
-    help = "Populate database with groups and assigns permissions"
-
-    def handle(self, *args: Any, **options: Any):
-        assign_perms()
diff --git a/aai/migrations/0004_delete_perms.py b/aai/migrations/0004_delete_perms.py
new file mode 100644
index 0000000000000000000000000000000000000000..176e09fe9ab7018bd85e9f15d7d98ccc73473fb9
--- /dev/null
+++ b/aai/migrations/0004_delete_perms.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.22 on 2024-07-10 15:36
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('aai', '0003_alter_perms_options'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='Perms',
+        ),
+    ]
diff --git a/aai/models.py b/aai/models.py
deleted file mode 100644
index 3225491129fb39134fda2506f18f4559dc74cf5b..0000000000000000000000000000000000000000
--- a/aai/models.py
+++ /dev/null
@@ -1,118 +0,0 @@
-from typing import Optional
-from enum import Enum
-
-from django.db import models
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.auth.models import Group
-
-
-class UserGroup(str, Enum):
-    TRAINEE = "trainee"
-    INSTRUCTOR = "instructor"
-    ADMIN = "admin"
-
-    @staticmethod
-    def from_str(group: str) -> Optional["UserGroup"]:
-        lowered_group = group.lower()
-        if lowered_group == "trainee" or lowered_group == "t":
-            return UserGroup.TRAINEE
-        elif lowered_group == "instructor" or lowered_group == "i":
-            return UserGroup.INSTRUCTOR
-        elif lowered_group == "admin" or lowered_group == "a":
-            return UserGroup.ADMIN
-        return None
-
-    def __eq__(self, other):
-        if isinstance(other, Group):
-            return self.value == other.name
-        return super().__eq__(other)
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __hash__(self) -> int:
-        return super().__hash__()
-
-
-class NameHandler:
-    full_name: str
-    codename: str
-
-    def __init__(self, full_name):
-        self.full_name = full_name
-        self.codename = self.full_name.split(".")[1]
-
-    def __get__(self, instance, owner):
-        return self
-
-
-class Perms(models.Model):
-    update_exercise = NameHandler("aai.update_exercise")
-    view_exercise = NameHandler("aai.view_exercise")
-    update_definition = NameHandler("aai.update_definition")
-    view_definition = NameHandler("aai.view_definition")
-    view_category = NameHandler("aai.view_category")
-    view_milestone = NameHandler("aai.view_milestone")
-    use_tool = NameHandler("aai.use_tool")
-    send_injectselection = NameHandler("aai.send_injectselection")
-    send_email = NameHandler("aai.send_email")
-    view_trainee_info = NameHandler("aai.view_trainee_info")
-    view_extendtool = NameHandler("aai.view_extendtool")
-    view_injectselection = NameHandler("aai.view_injectselection")
-    view_email_info = NameHandler("aai.view_email_info")
-    view_email = NameHandler("aai.view_email")
-    view_analytics = NameHandler("aai.view_analytics")
-    update_userassignment = NameHandler("aai.update_userassignment")
-    view_user = NameHandler("aai.view_user")
-    manipulate_file = NameHandler("aai.manipulate_file")
-    update_user = NameHandler("aai.update_user")
-    delete_user = NameHandler("aai.delete_user")  # only for admin
-    export_import = NameHandler("aai.export_import")
-
-    class Meta:
-        default_permissions = ()
-        permissions = [
-            (
-                "update_exercise",
-                "Can access instructor tools for exercise manipulation",
-            ),
-            ("view_exercise", "Can view exercise and their info"),
-            ("update_definition", "Can add/delete/change definition"),
-            ("view_definition", "Can view definition"),
-            ("view_category", "Can view inject categories"),
-            ("view_milestone", "Can view milestones"),
-            ("use_tool", "Can use tool of the exercise"),
-            ("send_injectselection", "Can pick and send inject selection"),
-            (
-                "send_email",
-                "Can send email and execute email related operations",
-            ),
-            (
-                "view_trainee_info",
-                "Can view exercise info intedned to trainees (roles, tools...)",
-            ),
-            ("view_extendtool", "Can view extend tool (with responses)"),
-            ("view_injectselection", "Can view inject selecion options"),
-            (
-                "view_email_info",
-                "Can view info related to emails (contacts, addresses...)",
-            ),
-            ("view_email", "Can view email bodies and threads"),
-            ("view_analytics", "Can view data needed for analytics dashboard"),
-            (
-                "update_userassignment",
-                "Can (un)assign user to exercise or team",
-            ),
-            ("view_user", "Can view users in database"),
-            (
-                "manipulate_file",
-                "Can upload and download files during exercise",
-            ),
-            ("update_user", "Can add/remove/change user"),
-            ("delete_user", "Can delete user"),
-            ("export_import", "Can export and import database"),
-        ]
-
-    @classmethod
-    def content_type(cls):
-        return ContentType.objects.get_for_model(Perms)
diff --git a/aai/tests/aai_utils_tests.py b/aai/tests/aai_utils_tests.py
index 8a48c4e8a2d345ab214102ce1e0c3bad730e4d23..ee012b71a15a793196d24157f3dcf46909ccc5d7 100644
--- a/aai/tests/aai_utils_tests.py
+++ b/aai/tests/aai_utils_tests.py
@@ -7,7 +7,6 @@ from rest_framework.exceptions import PermissionDenied, AuthenticationFailed
 from django.test import TestCase, override_settings
 from rest_framework.test import APIRequestFactory
 
-from aai.models import Perms
 from aai.utils import protected, extra_protected, Check
 from common_lib.test_utils import (
     internal_create_exercise,
@@ -88,7 +87,6 @@ class AaiUtilsTests(TestCase):
 
         cls.factory = APIRequestFactory()
         cls.assignUsers()
-        cls.assignPerms()
         cls.createData()
 
     @classmethod
@@ -108,15 +106,6 @@ class AaiUtilsTests(TestCase):
         # Assign definition to the instructor
         cls.instructor.definitions.add(cls.definition)
 
-    @classmethod
-    def assignPerms(cls):
-        p = Permission.objects.get(codename=Perms.view_exercise.codename)
-        cls.trainee1.group.permissions.add(p)
-        cls.instructor.group.permissions.add(p)
-
-        p = Permission.objects.get(codename=Perms.update_exercise.codename)
-        cls.instructor.group.permissions.add(p)
-
     @classmethod
     def createData(cls):
         # create action log
@@ -170,9 +159,7 @@ class AaiUtilsTests(TestCase):
         return 0
 
     def test_protected_admin(self):
-        decorated = protected("aai.non_existent")(
-            self.dummy
-        )  # admin has access
+        decorated = protected(User.AuthGroup.ADMIN)(self.dummy)
         request = self.factory.request()
 
         request.user = self.trainee1
@@ -187,7 +174,7 @@ class AaiUtilsTests(TestCase):
         self.assertEqual(decorated(request), 0)
 
     def test_protected_instructor(self):
-        decorated = protected(Perms.update_exercise.full_name)(self.dummy)
+        decorated = protected(User.AuthGroup.INSTRUCTOR)(self.dummy)
         request = self.factory.request()
 
         request.user = self.trainee1
@@ -201,7 +188,7 @@ class AaiUtilsTests(TestCase):
         self.assertEqual(decorated(request), 0)
 
     def test_protected_trainee(self):
-        decorated = protected(Perms.view_exercise.full_name)(self.dummy)
+        decorated = protected(User.AuthGroup.TRAINEE)(self.dummy)
         request = self.factory.request()
 
         request.user = self.trainee1
@@ -214,7 +201,7 @@ class AaiUtilsTests(TestCase):
         self.assertEqual(decorated(request), 0)
 
     def test_protected_anonoymous(self):
-        decorated = protected(Perms.view_exercise.full_name)(self.dummy)
+        decorated = protected(User.AuthGroup.TRAINEE)(self.dummy)
         request = self.factory.request()
 
         request.user = AnonymousUser()
diff --git a/aai/utils.py b/aai/utils.py
index 1ae550be47ce464535c648fa4766a88c70af5b5a..c9b7286f8ba85c57e885934a6a0f9592baa252a2 100644
--- a/aai/utils.py
+++ b/aai/utils.py
@@ -7,7 +7,6 @@ from graphene import ResolveInfo
 from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
 from rest_framework.request import HttpRequest, Request
 
-from aai.models import UserGroup
 from common_lib.logger import logger
 from common_lib.utils import get_model, InputObject
 from exercise.graphql_inputs import CreateExerciseInput
@@ -168,7 +167,7 @@ def _check_thread(user: User, thread_id) -> bool:
 
 
 def _check_visible_only(user: User, visible_only: bool) -> bool:
-    return visible_only or user.group != UserGroup.TRAINEE
+    return visible_only or user.group != User.AuthGroup.TRAINEE
 
 
 CHECK_MAPPING: Dict[Check, Callable] = {
@@ -191,13 +190,13 @@ def logged_in(func):
     return wrapper
 
 
-def protected(required_permission: str):
+def protected(required_group: User.AuthGroup):
     """
-    Decorator that checks whether user of request has required permission.
+    Decorator that checks whether user of request has required authorization group.
     If not, PermissionDenied exception is raised.
 
     Args:
-        required_permission: name of the permission in form "app_label.codename"
+        required_group: minimal required authorization group to access endpoint
     """
 
     def decorator(func):
@@ -218,7 +217,7 @@ def protected(required_permission: str):
             if user.is_anonymous:
                 raise AuthenticationFailed("Authentication failed")
 
-            if user.has_perm(required_permission):
+            if user.group >= required_group:
                 return func(*args, **kwargs)
             raise PermissionDenied("Permission denied")
 
diff --git a/common_lib/schema_types.py b/common_lib/schema_types.py
index fa0626986c40f3b824075dcb4fa5776a4d926893..1068392c062842b4b18f90c091f4f52779b40869 100644
--- a/common_lib/schema_types.py
+++ b/common_lib/schema_types.py
@@ -3,7 +3,6 @@ from django.db import models
 from graphene_django import DjangoObjectType
 from django.conf import settings
 
-from aai.models import UserGroup
 from exercise.models import (
     Exercise,
     Team,
@@ -45,7 +44,7 @@ from running_exercise.models import (
     LogType,
     QuestionnaireAnswer,
 )
-from user.models import User, Tag, Group
+from user.models import User, Tag
 
 
 class RestrictedUser(DjangoObjectType):
@@ -110,7 +109,7 @@ class ExerciseType(DjangoObjectType):
     def resolve_user_set(self, info):
         if settings.NOAUTH:
             pass
-        elif info.context.user.group == UserGroup.TRAINEE:
+        elif info.context.user.group == User.AuthGroup.TRAINEE:
             return User.objects.none()
         return self.user_set.all()
 
@@ -124,7 +123,7 @@ class TeamType(DjangoObjectType):
     def resolve_user_set(self, info):
         if settings.NOAUTH:
             pass
-        elif info.context.user.group == UserGroup.TRAINEE:
+        elif info.context.user.group == User.AuthGroup.TRAINEE:
             return User.objects.none()
         return self.user_set.all()
 
@@ -181,7 +180,7 @@ class QuestionType(DjangoObjectType):
         if settings.NOAUTH:
             return self.correct
 
-        if user.group == UserGroup.TRAINEE:
+        if user.group == User.AuthGroup.TRAINEE:
             return 0
         return self.correct
 
@@ -329,7 +328,7 @@ class UserType(DjangoObjectType):
         return self.teams.all()
 
     def resolve_group(self, info):
-        return self.group
+        return User.AuthGroup(self.group).name.lower()
 
 
 class TagType(DjangoObjectType):
@@ -338,12 +337,6 @@ class TagType(DjangoObjectType):
         exclude_fields = ("user_set",)
 
 
-class GroupType(DjangoObjectType):
-    class Meta:
-        model = Group
-        exclude_fields = ("user_set",)
-
-
 class ExerciseEventTypeEnum(models.TextChoices):
     CREATE = "create"
     MODIFY = "modify"
@@ -381,7 +374,7 @@ class QuestionnaireAnswerType(DjangoObjectType):
         if user.is_anonymous:
             return self.is_correct
 
-        if user.group == UserGroup.TRAINEE:
+        if user.group == User.AuthGroup.TRAINEE:
             return None
         return self.is_correct
 
diff --git a/exercise/schema/mutation.py b/exercise/schema/mutation.py
index 7d1e16dcd8384e6ee36a29b3e47498d67ea5be84..ba2f8e2cc5e5740fe38b7c842700ed8858776d78 100644
--- a/exercise/schema/mutation.py
+++ b/exercise/schema/mutation.py
@@ -6,10 +6,9 @@ from common_lib.schema_types import ExerciseType
 from exercise.graphql_inputs import CreateExerciseInput
 from exercise.lib.exercise_manager import ExerciseManager
 from exercise_definition.lib.definition_manager import DefinitionManager
-from aai.models import Perms
 from aai.utils import protected, extra_protected, input_object_protected, Check
 from user.schema.validators import validate_instructor_assigning
-from user.models import InstructorOfExercise
+from user.models import InstructorOfExercise, User
 
 
 class CreateExerciseMutation(graphene.Mutation):
@@ -21,7 +20,7 @@ class CreateExerciseMutation(graphene.Mutation):
         )
 
     @classmethod
-    @protected(Perms.update_exercise.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @input_object_protected("create_exercise_input")
     def mutate(
         cls,
@@ -50,7 +49,7 @@ class DeleteExerciseMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_exercise.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(cls, root, info, exercise_id: str):
         ExerciseManager.delete_exercise(int(exercise_id))
@@ -64,7 +63,7 @@ class DeleteDefinitionMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.DEFINITION_ID)
     def mutate(cls, root, info, definition_id: str):
         DefinitionManager.delete_definition(int(definition_id))
diff --git a/exercise/schema/query.py b/exercise/schema/query.py
index 2331a46c247293fa7705b94a8d9c4e229cfd33d3..64bc2b78c46248f4ffac8bfc76deed36ca8af528 100644
--- a/exercise/schema/query.py
+++ b/exercise/schema/query.py
@@ -20,7 +20,7 @@ from exercise_definition.models import (
     FileInfo,
     Channel,
 )
-from aai.models import Perms, UserGroup
+from user.models import User
 from aai.utils import protected, extra_protected, Check
 
 
@@ -94,7 +94,7 @@ class Query(graphene.ObjectType):
         description="Retrieve all channels for an exercise",
     )
 
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def resolve_exercises(
         self,
         info,
@@ -108,11 +108,11 @@ class Query(graphene.ObjectType):
         if settings.NOAUTH:
             exercises = Exercise.objects.all()
         else:
-            if user.group == UserGroup.TRAINEE:
+            if user.group == User.AuthGroup.TRAINEE:
                 exercises = Exercise.objects.filter(teams__users__user=user)
-            elif user.group == UserGroup.INSTRUCTOR:
+            elif user.group == User.AuthGroup.INSTRUCTOR:
                 exercises = user.exercises.all()
-            elif user.group == UserGroup.ADMIN:
+            elif user.group == User.AuthGroup.ADMIN:
                 exercises = Exercise.objects.all()
             else:
                 return Exercise.objects.none()
@@ -131,31 +131,30 @@ class Query(graphene.ObjectType):
 
         return exercises
 
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_exercise_id(self, info, exercise_id: str) -> Optional[Exercise]:
         return Exercise.objects.filter(id=exercise_id).first()
 
-    @protected(Perms.view_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def resolve_definitions(self, info) -> List[Definition]:
         if settings.NOAUTH:
             return Definition.objects.all()
 
         user = info.context.user
         # User dependant data resolving
-        if user.group == UserGroup.INSTRUCTOR:
+        if user.group == User.AuthGroup.INSTRUCTOR:
             return Definition.objects.filter(maintainers__user=user)
-        elif user.group == UserGroup.ADMIN:
+        elif user.group == User.AuthGroup.ADMIN:
             return Definition.objects.all()
-        else:  # UserGroup.TRAINEE and unknown groups can't see definitions
-            return []
+        return []
 
-    @protected(Perms.view_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.DEFINITION_ID)
     def resolve_definition(self, info, definition_id: str) -> Definition:
         return get_model(Definition, id=definition_id)
 
-    @protected(Perms.view_category.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_auto_injects(self, info, exercise_id: str) -> List[Inject]:
         exercise = get_model(
@@ -167,7 +166,7 @@ class Query(graphene.ObjectType):
             definition_id=exercise.definition.id, auto=True
         )
 
-    @protected(Perms.view_milestone.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_milestones(self, info, exercise_id: str) -> List[Milestone]:
         exercise = get_model(
@@ -177,20 +176,20 @@ class Query(graphene.ObjectType):
 
         return Milestone.objects.filter(definition_id=exercise.definition.id)
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def resolve_file_info(self, info, file_info_id: str) -> FileInfo:
         return get_model(FileInfo, id=file_info_id)
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_team_uploaded_files(self, info, team_id: str) -> List[FileInfo]:
         return FileInfo.objects.filter(team__team_id=team_id)
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def resolve_channel(self, info, channel_id: str) -> Channel:
         return get_model(Channel, id=channel_id)
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_exercise_channels(
         self, info, exercise_id: str
diff --git a/exercise/subscription.py b/exercise/subscription.py
index 433a42d04e39493c48e5fd389c3f0eb022dd8c57..9f7c91c016b2c7c2fb21175ab65a99e04b69fb67 100644
--- a/exercise/subscription.py
+++ b/exercise/subscription.py
@@ -3,7 +3,7 @@ import graphene
 from common_lib.schema_types import ExerciseEventTypeEnum
 
 from common_lib.schema_types import ExerciseType
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected
 
 NOTIFICATION_QUEUE_LIMIT = 64
@@ -16,7 +16,7 @@ class ExercisesSubscription(channels_graphql_ws.Subscription):
     event_type = graphene.Field(graphene.Enum.from_enum(ExerciseEventTypeEnum))
 
     @staticmethod
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def subscribe(root, info):
         return ["all"]
 
diff --git a/exercise/views.py b/exercise/views.py
index f16dda9594a46ca747a9c9bdb480e35339393189..a2f6354a4c792a5ed7e0b05c744d14da74a01075 100644
--- a/exercise/views.py
+++ b/exercise/views.py
@@ -6,7 +6,7 @@ from rest_framework import parsers
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected, extra_protected, Check
 from common_lib.exceptions import ApiException
 from exercise.lib.export_import import export_database, import_database
@@ -14,7 +14,7 @@ from exercise.lib.log_manager import LogManager
 
 
 class RetrieveExerciseLogsView(APIView):
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def get(self, request, *args, **kwargs):
         """
@@ -44,7 +44,7 @@ class BackendVersionView(APIView):
 
 
 class ExportImportView(APIView):
-    @protected(Perms.export_import.full_name)
+    @protected(User.AuthGroup.ADMIN)
     def get(self, request, *args, **kwargs):
         """
         Get the exported file
@@ -53,7 +53,7 @@ class ExportImportView(APIView):
 
     parser_classes = [parsers.MultiPartParser]
 
-    @protected(Perms.export_import.full_name)
+    @protected(User.AuthGroup.ADMIN)
     def post(self, request, *args, **kwargs):
         """
         Import data into the database based on the imported zip file
diff --git a/exercise_definition/views.py b/exercise_definition/views.py
index 32aaed2970b2d2d445e6384b5d3b7a6b1802d716..a230998f5fbf25b888ae9844da3db4582ee688bd 100644
--- a/exercise_definition/views.py
+++ b/exercise_definition/views.py
@@ -6,19 +6,18 @@ from rest_framework.request import Request
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
-from aai.models import Perms
 from aai.utils import protected
 from common_lib.exceptions import ApiException
 from common_lib.logger import logger, log_user_msg
 from exercise_definition.lib import DefinitionUploader, DefinitionParser
 from exercise_definition.lib.definition_validator import DefinitionValidator
-from user.models import DefinitionAccess
+from user.models import DefinitionAccess, User
 
 
 class UploadDefinitionView(APIView):
     parser_classes = [parsers.MultiPartParser]
 
-    @protected(Perms.update_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def post(self, request: Request, *args, **kwargs):
         """Upload an exercise definition file and check it for correctness."""
         uploaded_file = request.FILES.get("file")
@@ -52,7 +51,7 @@ class UploadDefinitionView(APIView):
 class ValidateDefinition(APIView):
     parser_classes = [parsers.MultiPartParser]
 
-    @protected(Perms.update_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def post(self, request: Request, *args, **kwargs):
         """Validate the uploaded definition"""
         uploaded_file = request.FILES.get("file")
diff --git a/running_exercise/schema/mutation.py b/running_exercise/schema/mutation.py
index 72dd953c3059d44ea6137d2d641ffda50df56e50..a9681e4bda633d4f5203994650f310c30838a72b 100644
--- a/running_exercise/schema/mutation.py
+++ b/running_exercise/schema/mutation.py
@@ -3,7 +3,7 @@ from typing import List
 import graphene
 from django.conf import settings
 
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected, extra_protected, input_object_protected, Check
 from common_lib.logger import logger
 from common_lib.schema_types import ExerciseType, EmailThreadType
@@ -34,7 +34,7 @@ class UseToolMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.use_tool.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @input_object_protected("use_tool_input")
     def mutate(
         cls,
@@ -56,7 +56,7 @@ class SelectTeamInjectOptionMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.send_injectselection.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @input_object_protected("select_team_inject_input")
     def mutate(
         cls, root, info, select_team_inject_input: SelectTeamInjectInput
@@ -81,7 +81,7 @@ class CreateThreadMutation(graphene.Mutation):
     thread = graphene.Field(EmailThreadType)
 
     @classmethod
-    @protected(Perms.send_email.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(
         cls,
@@ -105,7 +105,7 @@ class SendEmailMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.send_email.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @input_object_protected("send_email_input")
     def mutate(
         cls,
@@ -126,7 +126,7 @@ class MoveExerciseTimeMutation(graphene.Mutation):
     exercise = graphene.Field(ExerciseType)
 
     @classmethod
-    @protected(Perms.update_exercise.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(cls, root, info, exercise_id: str, time_diff: int):
         exercise = ExerciseLoop.move_time(int(exercise_id), time_diff)
@@ -140,7 +140,7 @@ class StartExerciseMutation(graphene.Mutation):
     exercise = graphene.Field(ExerciseType)
 
     @classmethod
-    @protected(Perms.update_exercise.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(cls, root, info, exercise_id: str):
         exercise = ExerciseLoop.start(int(exercise_id))
@@ -154,7 +154,7 @@ class StopExerciseMutation(graphene.Mutation):
     exercise = graphene.Field(ExerciseType)
 
     @classmethod
-    @protected(Perms.update_exercise.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(cls, root, info, exercise_id: str):
         exercise = ExerciseLoop.stop(int(exercise_id))
@@ -174,7 +174,7 @@ class ModifyMilestoneMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_exercise.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def mutate(cls, root, info, team_id: str, milestone: str, activate: bool):
         instructor_modify_milestone(
@@ -192,9 +192,8 @@ class AnswerQuestionnaireMutation(graphene.Mutation):
 
     operation_done = graphene.Boolean()
 
-    # using send email because we do not have a "trainee action" permission
     @classmethod
-    @protected(Perms.send_email.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def mutate(cls, root, info, quest_input: QuestionnaireInput):
         QuestionnaireHandler.answer_questionnaire(quest_input)
         return AnswerQuestionnaireMutation(operation_done=True)
diff --git a/running_exercise/schema/query.py b/running_exercise/schema/query.py
index c7560e567edbb56d945ae0bbd095480ac997665f..503fb66e488e82b465521ceaca69395201125892 100644
--- a/running_exercise/schema/query.py
+++ b/running_exercise/schema/query.py
@@ -3,7 +3,7 @@ from typing import List
 import graphene
 from django.db.models import QuerySet
 
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected, extra_protected, Check
 from common_lib.exceptions import RunningExerciseOperationException
 from common_lib.schema_types import (
@@ -206,12 +206,12 @@ class Query(graphene.ObjectType):
         description="Retrieve the learning objectives for the specific team",
     )
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_team(self, info, team_id: str) -> Team:
         return get_model(Team, id=int(team_id))
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def resolve_team_roles(self, info) -> List[str]:
         # TODO: remove dependency on "get_running_exercise", prefer exercise_id parameter
         running_exercise = get_running_exercise()
@@ -219,7 +219,7 @@ class Query(graphene.ObjectType):
             raise RunningExerciseOperationException("No exercise is running.")
         return [team.role for team in running_exercise.teams.all()]
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_team_tools(self, info, team_id: str) -> List[Tool]:
         team = get_model(Team, id=int(team_id))
@@ -230,7 +230,7 @@ class Query(graphene.ObjectType):
             if has_role(team.role, tool.roles.split(" "))
         ]
 
-    @protected(Perms.view_extendtool.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def resolve_extended_team_tools(self, info, team_id: str) -> List[Tool]:
         team = get_model(Team, id=int(team_id))
@@ -241,12 +241,12 @@ class Query(graphene.ObjectType):
             if has_role(team.role, tool.roles.split(" "))
         ]
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.LOG_ID)
     def resolve_action_log(self, info, log_id: str) -> ActionLog:
         return get_model(ActionLog, id=int(log_id))
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_team_action_logs(
         self, info, team_id: str
@@ -258,7 +258,7 @@ class Query(graphene.ObjectType):
     ) -> QuerySet[ActionLog]:
         return ActionLog.objects.filter(team_id=team_id, channel_id=channel_id)
 
-    @protected(Perms.view_milestone.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     @extra_protected(Check.VISIBLE_ONLY)
     def resolve_team_milestones(
@@ -266,76 +266,76 @@ class Query(graphene.ObjectType):
     ) -> List[MilestoneState]:
         return get_milestone_states(int(team_id), visible_only)
 
-    @protected(Perms.view_injectselection.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def resolve_team_inject_selections(
         self, info, team_id: str
     ) -> QuerySet[InjectSelection]:
         return InjectSelection.objects.filter(team_id=team_id)
 
-    @protected(Perms.view_email_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.VISIBLE_ONLY)
     def resolve_email_contacts(
         self, info, visible_only: bool = True
     ) -> QuerySet[EmailParticipant]:
         return EmailClient.get_contacts(visible_only)
 
-    @protected(Perms.view_email_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def resolve_email_contact(
         self, info, participant_id: str
     ) -> EmailParticipant:
         return get_model(EmailParticipant, id=int(participant_id))
 
-    @protected(Perms.view_email.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_email_threads(
         self, info, team_id: str
     ) -> QuerySet[EmailThread]:
         return EmailClient.get_threads(int(team_id))
 
-    @protected(Perms.view_email.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.THREAD_ID)
     def resolve_email_thread(self, info, thread_id: str) -> EmailThread:
         return get_model(EmailThread, id=int(thread_id))
 
-    @protected(Perms.view_email_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.THREAD_ID)
     def resolve_email_addresses(self, info, thread_id: str) -> List[str]:
         return EmailClient.get_instructor_email_addresses(int(thread_id))
 
-    @protected(Perms.view_email_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_validate_email_address(
         self, info, exercise_id: str, address: str
     ) -> bool:
         return EmailClient.validate_email_address(int(exercise_id), address)
 
-    @protected(Perms.view_email_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_team_email_participant(
         self, info, team_id: str
     ) -> EmailParticipant:
         return EmailClient.get_team_participant(int(team_id))
 
-    @protected(Perms.view_injectselection.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_email_templates(
         self, info, exercise_id: str, email_addresses: List[str]
     ) -> List[EmailTemplate]:
         return EmailClient.get_email_templates(exercise_id, email_addresses)
 
-    @protected(Perms.view_injectselection.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.THREAD_ID)
     def resolve_thread_templates(
         self, info, thread_id: int
     ) -> List[EmailTemplate]:
         return EmailClient.get_thread_templates(int(thread_id))
 
-    @protected(Perms.view_injectselection.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def resolve_thread_template(self, info, template_id: str) -> EmailTemplate:
         return get_model(EmailTemplate, id=int(template_id))
 
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     def resolve_exercise_time_left(self, info) -> int:
         # TODO: remove dependency on "get_running_exercise", prefer exercise_id parameter
         running_exercise = get_running_exercise()
@@ -348,7 +348,7 @@ class Query(graphene.ObjectType):
             )
         return max(0, exercise_duration_s - int(running_exercise.elapsed_s))
 
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_exercise_config(self, info, exercise_id: str) -> GrapheneConfig:
         exercise = get_model(Exercise, id=int(exercise_id))
@@ -364,12 +364,12 @@ class Query(graphene.ObjectType):
             custom_email_suffix=config.custom_email_suffix,
         )
 
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_exercise_loop_running(self, info, exercise_id: str) -> bool:
         return ExerciseLoop.is_running(int(exercise_id))
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_analytics_milestones(
         self, info, exercise_id: str
@@ -378,7 +378,7 @@ class Query(graphene.ObjectType):
             team_state__exercise_id=int(exercise_id)
         )
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_analytics_action_logs(
         self, info, exercise_id: str
@@ -387,7 +387,7 @@ class Query(graphene.ObjectType):
 
         return ActionLog.objects.filter(team__in=exercise.teams.all())
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_analytics_email_threads(
         self, info, exercise_id: str
@@ -396,14 +396,14 @@ class Query(graphene.ObjectType):
             exercise_id=exercise_id
         ).prefetch_related("emails")
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_exercise_tools(self, info, exercise_id: str) -> List[Tool]:
         exercise = get_model(Exercise, id=int(exercise_id))
 
         return exercise.definition.tools.all()
 
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def resolve_questionnaire_state(
         self, info, team_id: str, questionnaire_id: str
@@ -414,14 +414,14 @@ class Query(graphene.ObjectType):
             questionnaire_id=questionnaire_id,
         )
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def resolve_team_questionnaires(
         self, info, team_id: str
     ) -> List[TeamQuestionnaireState]:
         return TeamQuestionnaireState.objects.filter(team_id=team_id)
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def resolve_exercise_questionnaires(
         self, info, exercise_id: str
@@ -430,7 +430,7 @@ class Query(graphene.ObjectType):
             team__exercise_id=exercise_id
         )
 
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def resolve_team_learning_objectives(
         self, info, team_id: str
     ) -> List[TeamLearningObjective]:
diff --git a/running_exercise/subscription.py b/running_exercise/subscription.py
index d439ced28ecde08ab6f70e52dbf19b19abb5c678..5e4a93badd9b18b9373f2345c589d75206312662 100644
--- a/running_exercise/subscription.py
+++ b/running_exercise/subscription.py
@@ -1,7 +1,7 @@
 import channels_graphql_ws
 import graphene
 
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected, extra_protected, Check
 from common_lib.schema_types import (
     ActionLogType,
@@ -25,7 +25,7 @@ class ExerciseLoopRunningSubscription(channels_graphql_ws.Subscription):
         exercise_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.view_exercise.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.EXERCISE_ID)
     def subscribe(root, info, exercise_id: str):
         get_model(Exercise, id=int(exercise_id))
@@ -45,7 +45,7 @@ class ActionLogsSubscription(channels_graphql_ws.Subscription):
         team_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def subscribe(root, info, team_id: str):
         get_model(Team, id=int(team_id))
@@ -69,7 +69,7 @@ class MilestonesSubscription(channels_graphql_ws.Subscription):
         )
 
     @staticmethod
-    @protected(Perms.view_milestone.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     @extra_protected(Check.VISIBLE_ONLY)
     def subscribe(root, info, team_id: str, visible_only: bool = True):
@@ -89,7 +89,7 @@ class InjectSelectionsSubscription(channels_graphql_ws.Subscription):
         team_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.send_injectselection.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def subscribe(root, info, team_id: str):
         get_model(Team, id=int(team_id))
@@ -108,7 +108,7 @@ class EmailThreadSubscription(channels_graphql_ws.Subscription):
         team_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.view_email.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def subscribe(root, info, team_id: str):
         get_model(Team, id=int(team_id))
@@ -127,7 +127,7 @@ class AnalyticsMilestonesSubscription(channels_graphql_ws.Subscription):
         exercise_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def subscribe(root, info, exercise_id: str):
         get_model(Exercise, id=int(exercise_id))
@@ -146,7 +146,7 @@ class AnalyticsActionLogsSubscription(channels_graphql_ws.Subscription):
         exercise_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def subscribe(root, info, exercise_id: str):
         get_model(Exercise, id=int(exercise_id))
@@ -165,7 +165,7 @@ class AnalyticsEmailThreadSubscription(channels_graphql_ws.Subscription):
         exercise_id = graphene.ID()
 
     @staticmethod
-    @protected(Perms.view_analytics.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def subscribe(root, info, exercise_id: str):
         get_model(Exercise, id=int(exercise_id))
@@ -186,7 +186,7 @@ class TeamQuestionnaireStateSubscription(channels_graphql_ws.Subscription):
         team_id = graphene.ID(required=True)
 
     @staticmethod
-    @protected(Perms.view_trainee_info.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def subscribe(root, info, team_id: str):
         get_model(Team, id=int(team_id))
diff --git a/running_exercise/views.py b/running_exercise/views.py
index db8da9218cc5deb0073e14f299e16246507fe82a..230321bfca72757c05810ad7927201d6cfbb0167 100644
--- a/running_exercise/views.py
+++ b/running_exercise/views.py
@@ -3,7 +3,7 @@ from rest_framework import parsers
 from rest_framework.response import Response
 from rest_framework.views import APIView
 
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected, extra_protected, Check
 from common_lib.exceptions import ApiException
 from running_exercise.lib.file_handler import (
@@ -13,7 +13,7 @@ from running_exercise.lib.file_handler import (
 
 
 class GetFileView(APIView):
-    @protected(Perms.manipulate_file.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def get(self, request, *args, **kwargs):
         """
@@ -40,7 +40,7 @@ class GetFileView(APIView):
 class UploadFileView(APIView):
     parser_classes = [parsers.MultiPartParser]
 
-    @protected(Perms.manipulate_file.full_name)
+    @protected(User.AuthGroup.TRAINEE)
     @extra_protected(Check.TEAM_ID)
     def post(self, request, *args, **kwargs):
         """Upload a file as a team."""
diff --git a/user/lib/user_uploader.py b/user/lib/user_uploader.py
index 83406ab08f02da7bd9e717bae0f8c412e4a2d5a1..d4df050f4af3440d065b7b302e28b9cc806877b7 100644
--- a/user/lib/user_uploader.py
+++ b/user/lib/user_uploader.py
@@ -14,7 +14,6 @@ from user.lib.user_validator import (
     ValidatedUserData,
 )
 from user.models import User, Tag, UserTag
-from aai.models import UserGroup
 from user.email.email_sender import send_credentials
 
 
@@ -23,17 +22,17 @@ Password = str
 CreatedUser = Tuple[User, Password]
 
 
-def _group_to_create_func(group: UserGroup) -> Callable:
-    if group == UserGroup.TRAINEE:
+def _group_to_create_func(group: User.AuthGroup) -> Callable:
+    if group == User.AuthGroup.TRAINEE:
         return User.objects.create_user
 
-    elif group == UserGroup.INSTRUCTOR:
+    if group == User.AuthGroup.INSTRUCTOR:
         return User.objects.create_staffuser
 
-    elif group == UserGroup.ADMIN:
+    if group == User.AuthGroup.ADMIN:
         return User.objects.create_superuser
 
-    raise ValueError(f"Unexpected UserGroup ({group}) in _group_to_create_func")
+    raise ValueError(f"Unexpected Authorization Group ({group}).")
 
 
 def _create_and_tag_user(
diff --git a/user/lib/user_validator.py b/user/lib/user_validator.py
index c219d58b029a58e7f0014a7c1f7cede06a0ad76b..a9bfacbf8838d31d6b1033bf005541c9340c9cf6 100644
--- a/user/lib/user_validator.py
+++ b/user/lib/user_validator.py
@@ -5,7 +5,6 @@ from enum import Enum
 from rest_framework.response import Response
 from rest_framework import status
 
-from aai.models import UserGroup
 from user.models import User, EMAIL_REGEX
 
 
@@ -36,7 +35,7 @@ class ValidatedUserData:
     def __init__(
         self,
         username: str,
-        group: UserGroup,
+        group: User.AuthGroup,
         tags: Optional[str] = None,
         first_name: Optional[str] = None,
         last_name: Optional[str] = None,
@@ -142,14 +141,14 @@ class UserValidator:
             # For convenience of users "Empty" is taken as TRAINEE and thus valid
             return True
 
-        if UserGroup.from_str(group) is None:
+        if User.AuthGroup.from_str(group) is None:
             self.result_handler.add_error(
                 f"User with username:{username} has definined invalid role: {group}."
             )
             create = False
 
         elif (
-            UserGroup.from_str(group) == UserGroup.ADMIN
+            User.AuthGroup.from_str(group) == User.AuthGroup.ADMIN
             and not self.admin_initiator
         ):
             self.result_handler.add_error(
@@ -178,9 +177,9 @@ class UserValidator:
                 ResultAction.ERROR,
             ):
                 typed_group = (
-                    UserGroup.from_str(user_data.group)
+                    User.AuthGroup.from_str(user_data.group)
                     if user_data.group is not None and user_data.group != ""
-                    else UserGroup.TRAINEE
+                    else User.AuthGroup.TRAINEE
                 )
                 if typed_group is None:
                     # validation was success -> can't be None
diff --git a/user/management/commands/initadmins.py b/user/management/commands/initadmins.py
index 2ee1d629332e98068b6ef3c9e5e64ed8660c5217..669019c02ecfa5edf9f5465cf1ef050096103410 100644
--- a/user/management/commands/initadmins.py
+++ b/user/management/commands/initadmins.py
@@ -9,7 +9,6 @@ from user.models import EMAIL_REGEX, User
 from user.lib.user_uploader import _create_and_tag_user
 from user.lib.user_validator import ValidatedUserData
 from user.email.email_sender import send_credentials
-from aai.models import UserGroup
 
 
 def _validate_admins(admins: str) -> List[ValidatedUserData]:
@@ -28,7 +27,7 @@ def _validate_admins(admins: str) -> List[ValidatedUserData]:
             )
         else:
             valid_users.append(
-                ValidatedUserData(username=email, group=UserGroup.ADMIN)
+                ValidatedUserData(username=email, group=User.AuthGroup.ADMIN)
             )
     return valid_users
 
diff --git a/user/migrations/0005_alter_user_group.py b/user/migrations/0005_alter_user_group.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa8611350d251b6f79c92497310d0b2247340331
--- /dev/null
+++ b/user/migrations/0005_alter_user_group.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.22 on 2024-07-10 15:36
+
+from django.db import migrations, models
+import user.models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('user', '0004_auto_20240625_1246'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='group',
+            field=models.IntegerField(choices=[(1, 'trainee'), (3, 'instructor'), (5, 'admin')], default=user.models.User.AuthGroup['TRAINEE']),
+        ),
+    ]
diff --git a/user/models.py b/user/models.py
index b502e6e62f8893af978660a58e531936adad0961..335df7f3a4a9cde22eb4cc11c0d9c4344185ed81 100644
--- a/user/models.py
+++ b/user/models.py
@@ -1,14 +1,13 @@
 import re
 import uuid
+from enum import IntEnum
+from typing import Optional
 
 from django.contrib.auth.hashers import make_password
-from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, Group
+from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
 from django.db import models, transaction
-from django.db.models.signals import post_save, m2m_changed
-from django.dispatch import receiver
 from django.utils import timezone
 
-from aai.models import UserGroup
 from exercise.models import Team, Exercise, Definition
 
 EMAIL_REGEX = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b"
@@ -18,7 +17,7 @@ PERM_CACHE = "_perm_cache"
 class UserManager(BaseUserManager):
     use_in_migrations = True
 
-    def _create_user(self, username, password, group=None, **extra_fields):
+    def _create_user(self, username, password, group, **extra_fields):
         """
         Create and save a user with the given username and password.
         """
@@ -32,14 +31,7 @@ class UserManager(BaseUserManager):
         username = self.normalize_email(username)
         user: User = self.model(username=username, **extra_fields)
         user.password = make_password(password)
-        user.save(
-            using=self._db
-        )  # user must have assigned id when adding group
-
-        if group is not None:
-            group, _ = Group.objects.get_or_create(name=group)
-            user.group = group
-            user.save()
+        user.group = group
 
         if (first_name := extra_fields.get("first_name", None)) is not None:
             user.first_name = first_name
@@ -47,27 +39,28 @@ class UserManager(BaseUserManager):
         if (last_name := extra_fields.get("last_name", None)) is not None:
             user.last_name = last_name
 
+        user.save(using=self._db)
         return user
 
     def create_superuser(self, username, password, **extra_fields):
         extra_fields.setdefault("is_staff", True)
         extra_fields.setdefault("is_superuser", True)
         return self._create_user(
-            username, password, UserGroup.ADMIN, **extra_fields
+            username, password, User.AuthGroup.ADMIN, **extra_fields
         )
 
     def create_staffuser(self, username, password, **extra_fields):
         extra_fields.setdefault("is_staff", True)
         extra_fields.setdefault("is_superuser", False)
         return self._create_user(
-            username, password, UserGroup.INSTRUCTOR, **extra_fields
+            username, password, User.AuthGroup.INSTRUCTOR, **extra_fields
         )
 
     def create_user(self, username, password, **extra_fields):
         extra_fields.setdefault("is_staff", False)
         extra_fields.setdefault("is_superuser", False)
         return self._create_user(
-            username, password, UserGroup.TRAINEE, **extra_fields
+            username, password, User.AuthGroup.TRAINEE, **extra_fields
         )
 
     def get_by_natural_key(self, username):
@@ -109,6 +102,26 @@ class UserTagManager(models.Manager):
 
 
 class User(AbstractBaseUser):
+    class AuthGroup(IntEnum):
+        TRAINEE = 1
+        INSTRUCTOR = 3
+        ADMIN = 5
+
+        @staticmethod
+        def from_str(group: str) -> Optional["User.AuthGroup"]:
+            lowered_group = group.lower()
+            if lowered_group == "trainee" or lowered_group == "t":
+                return User.AuthGroup.TRAINEE
+            elif lowered_group == "instructor" or lowered_group == "i":
+                return User.AuthGroup.INSTRUCTOR
+            elif lowered_group == "admin" or lowered_group == "a":
+                return User.AuthGroup.ADMIN
+            return None
+
+        @classmethod
+        def choices(cls):
+            return [(group.value, group.name.lower()) for group in cls]
+
     id = models.UUIDField(
         primary_key=True,
         default=uuid.uuid4,
@@ -119,7 +132,9 @@ class User(AbstractBaseUser):
     first_name = models.TextField(blank=True, null=True)
     last_name = models.TextField(blank=True, null=True)
     date_joined = models.DateTimeField(default=timezone.now)
-    group = models.ForeignKey(Group, on_delete=models.SET_NULL, null=True)
+    group = models.IntegerField(
+        choices=AuthGroup.choices(), default=AuthGroup.TRAINEE
+    )
     is_staff = models.BooleanField(default=False)
     is_superuser = models.BooleanField(default=False)
     is_active = models.BooleanField(default=True)
@@ -149,36 +164,6 @@ class User(AbstractBaseUser):
     def __str__(self):
         return self.get_username()
 
-    def has_perm(self, perm):
-        if self.is_superuser:
-            return True
-
-        if not self.is_active or self.is_anonymous or self.group is None:
-            return False
-
-        if not hasattr(self, PERM_CACHE):
-            perms = self.group.permissions.all().values_list(
-                "content_type__app_label", "codename"
-            )
-            setattr(
-                self,
-                PERM_CACHE,
-                {f"{ct}.{name}" for ct, name in perms},
-            )
-        return perm in getattr(self, PERM_CACHE)
-
-    @classmethod
-    def invalidate_cache(cls):
-        for user in cls.objects.all():
-            if hasattr(user, PERM_CACHE):
-                delattr(user, PERM_CACHE)
-
-
-@receiver(post_save, sender=Group)
-@receiver(m2m_changed, sender=Group.permissions.through)
-def _user_perm_cache_invalidator(signal, instance, **kwargs):
-    User.invalidate_cache()
-
 
 class Tag(models.Model):
     name = models.CharField(max_length=20, blank=False, null=False)
diff --git a/user/schema/mutation.py b/user/schema/mutation.py
index 66224f49ac2a7851dbd596c9b9d728ced71f6b6d..3f186ffab666766dafb6976772403485f6a96220 100644
--- a/user/schema/mutation.py
+++ b/user/schema/mutation.py
@@ -4,7 +4,6 @@ import graphene
 from django.utils.crypto import get_random_string
 from django.conf import settings
 
-from aai.models import Perms
 from aai.utils import protected, extra_protected, Check
 from common_lib.logger import logger, log_user_msg
 from common_lib.schema_types import UserType
@@ -40,7 +39,7 @@ class AssignUsersToTeamMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_userassignment.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def mutate(
         cls, root, info, user_ids: List[str], team_id: str
@@ -73,7 +72,7 @@ class RemoveUsersFromTeamMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_userassignment.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.TEAM_ID)
     def mutate(
         cls, root, info, user_ids: List[str], team_id: str
@@ -104,7 +103,7 @@ class AssignInstructorsToExercise(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_userassignment.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(
         cls, root, info, user_ids: List[str], exercise_id: str
@@ -137,7 +136,7 @@ class RemoveInstructorsFromExerciseMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_userassignment.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.EXERCISE_ID)
     def mutate(
         cls, root, info, user_ids: List[str], exercise_id: str
@@ -170,7 +169,7 @@ class AddDefinitionAccessMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.DEFINITION_ID)
     def mutate(
         cls, root, info, user_ids: List[str], definition_id: str
@@ -203,7 +202,7 @@ class RemoveDefinitionAccessMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_definition.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     @extra_protected(Check.DEFINITION_ID)
     def mutate(
         cls, root, info, user_ids: List[str], definition_id: str
@@ -228,7 +227,7 @@ class ChangeUserDataMutation(graphene.Mutation):
         change_user_input = graphene.Argument(ChangeUserInput, required=True)
 
     @classmethod
-    @protected(Perms.update_user.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def mutate(
         cls, root, info, change_user_input: ChangeUserInput
     ) -> graphene.Mutation:
@@ -254,7 +253,7 @@ class RegenerateCredentialsMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.update_user.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def mutate(cls, root, info, user_ids: List[str]) -> graphene.Mutation:
         users = User.objects.filter(id__in=user_ids, is_active=True)
         regenerated_users = []
@@ -290,7 +289,7 @@ class DeleteUsersMutation(graphene.Mutation):
     operation_done = graphene.Boolean()
 
     @classmethod
-    @protected(Perms.delete_user.full_name)
+    @protected(User.AuthGroup.ADMIN)
     def mutate(cls, root, info, user_ids: List[str]) -> graphene.Mutation:
         users = User.objects.filter(id__in=user_ids)
         if not settings.NOAUTH or not info.context.user.is_anonymous:
diff --git a/user/schema/query.py b/user/schema/query.py
index 8d186dc28241295e73cb0c051c39a1b4fb313df9..4cdffcc62e83bbb6676b94a75ed6621f6251c651 100644
--- a/user/schema/query.py
+++ b/user/schema/query.py
@@ -1,11 +1,9 @@
 from typing import Optional, List
 
 import graphene
-from django.contrib.auth.models import Group
 
-from aai.models import Perms
-from aai.utils import protected
-from common_lib.schema_types import UserType, TagType, GroupType
+from aai.utils import protected, logged_in
+from common_lib.schema_types import UserType, TagType
 from common_lib.utils import get_model
 from user.graphql_inputs import FilterUsersInput
 from user.models import User, Tag
@@ -26,7 +24,7 @@ class Query(graphene.ObjectType):
         TagType, description="Retrieve all tags (for filtering)"
     )
     groups = graphene.List(
-        GroupType, description="Retrieve all groups (for filtering)"
+        graphene.String, description="Retrieve all groups (for filtering)"
     )
 
     def resolve_who_am_i(self, info) -> Optional[User]:
@@ -34,7 +32,7 @@ class Query(graphene.ObjectType):
             return None
         return info.context.user
 
-    @protected(Perms.view_user.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def resolve_users(
         self, info, filter_users_input: Optional[FilterUsersInput] = None
     ) -> List[User]:
@@ -75,14 +73,14 @@ class Query(graphene.ObjectType):
             users = users[: filter_users_input.limit]
         return users
 
-    @protected(Perms.view_user.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def resolve_user(self, info, user_id: str) -> User:
         return get_model(User, id=user_id)
 
-    @protected(Perms.view_user.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def resolve_tags(self, info) -> List[Tag]:
         return Tag.objects.all()
 
-    # TODO: for now leave open to anyone
-    def resolve_groups(self, info) -> List[Group]:
-        return Group.objects.all()
+    @logged_in
+    def resolve_groups(self, info) -> List[str]:
+        return [name for _, name in User.AuthGroup.choices()]
diff --git a/user/schema/validators.py b/user/schema/validators.py
index c73fbbe7816f9b6a104eb7c576fbafd93c4b4e6f..b66d92adbf6495ecc1ebf912f8bdeb463000b452 100644
--- a/user/schema/validators.py
+++ b/user/schema/validators.py
@@ -4,11 +4,9 @@ from django.db.models import Model
 from django.conf import settings
 from rest_framework.exceptions import PermissionDenied
 from django.db.models import QuerySet
-from django.contrib.auth.models import Group
 
 from user.models import UserInTeam, User, InstructorOfExercise, DefinitionAccess
 from exercise.models import Team, Exercise, Definition
-from aai.models import UserGroup
 from user.graphql_inputs import ChangeUserInput
 
 ModelType = TypeVar("ModelType", bound=Model)
@@ -27,12 +25,12 @@ def _check_nonexistent_users(user_ids: List[str]) -> List[User]:
     raise ValueError(f"Users with ids:{invalid_ids} don't exist")
 
 
-def _check_group(users: List[User], required_groups: List[UserGroup]):
+def _check_group(users: List[User], required_groups: List[User.AuthGroup]):
+    # checks AuthGroup with exact match (not hierarchical)
     invalid_group_users = [
         user
         for user in users
-        if user.group is None
-        or not set([user.group.name]).intersection(set(required_groups))
+        if not set([user.group]).intersection(set(required_groups))
     ]
     if not invalid_group_users:
         return
@@ -40,7 +38,7 @@ def _check_group(users: List[User], required_groups: List[UserGroup]):
     msg_format_users = __create_users_msg_format(invalid_group_users)
     raise ValueError(
         f"Users {msg_format_users} don't have one of the groups:{required_groups}. "
-        f"If you want to assign them, set them one of the metioned group."
+        f"If you want to assign them, set them one of the mentioned group."
     )
 
 
@@ -72,13 +70,16 @@ def _validate_group_change(
 ):
     if settings.NOAUTH:
         return
-    if new_group == UserGroup.ADMIN and requester.group != UserGroup.ADMIN:
+    if (
+        new_group == User.AuthGroup.ADMIN
+        and requester.group != User.AuthGroup.ADMIN
+    ):
         raise PermissionDenied(
             "Permission denied - Only admin can change user to admin group"
         )
     if (
-        requester.group != UserGroup.ADMIN
-        and changing_user.group == UserGroup.ADMIN
+        requester.group != User.AuthGroup.ADMIN
+        and changing_user.group == User.AuthGroup.ADMIN
     ):
         raise PermissionDenied(
             "Permission denied - Only admin can change user from admin group"
@@ -99,8 +100,8 @@ def _validate_active_change(
             "Permission denied - Can't change own active status"
         )
     if (
-        requester.group != UserGroup.ADMIN
-        and changing_user.group == UserGroup.ADMIN
+        requester.group != User.AuthGroup.ADMIN
+        and changing_user.group == User.AuthGroup.ADMIN
     ):
         raise PermissionDenied(
             "Permission denied - Only admin can change active status of admin user"
@@ -124,7 +125,7 @@ def validate_team_assigning(user_ids: List[str], team: Team) -> List[User]:
             f"to a team of the same exercise"
         )
 
-    _check_group(users, [UserGroup.TRAINEE])
+    _check_group(users, [User.AuthGroup.TRAINEE])
     return users
 
 
@@ -156,7 +157,7 @@ def validate_instructor_assigning(
             f"Users {msg_format_users} were already assigned to exercise (id:{exercise.id})"
         )
 
-    _check_group(users, [UserGroup.INSTRUCTOR, UserGroup.ADMIN])
+    _check_group(users, [User.AuthGroup.INSTRUCTOR, User.AuthGroup.ADMIN])
     return users
 
 
@@ -176,7 +177,7 @@ def validate_instructor_removing(
 
 def validate_definition_access_assinging(user_ids: List[str]) -> List[User]:
     users = _check_nonexistent_users(user_ids)
-    _check_group(users, [UserGroup.INSTRUCTOR, UserGroup.ADMIN])
+    _check_group(users, [User.AuthGroup.INSTRUCTOR, User.AuthGroup.ADMIN])
     return users
 
 
@@ -198,8 +199,8 @@ def validate_credentials_regeneration(users: QuerySet[User], requester: User):
     if settings.NOAUTH:
         return
     if (
-        requester.group != UserGroup.ADMIN
-        and users.filter(group__name=UserGroup.ADMIN).exists()
+        requester.group != User.AuthGroup.ADMIN
+        and users.filter(group=User.AuthGroup.ADMIN).exists()
     ):
         raise PermissionDenied(
             "Permission denied - Only admin can re-generate credentials for admin users"
@@ -219,7 +220,7 @@ def execute_change_userdata(
         _validate_group_change(
             requester, changing_user, change_user_input.group
         )
-        changing_user.group = Group.objects.get(name=change_user_input.group)
+        changing_user.group = User.AuthGroup.from_str(change_user_input.group)
         if change_user_input.group == "admin":
             changing_user.is_superuser = True
             changing_user.is_staff = True
diff --git a/user/tests/csv_onboarding_tests.py b/user/tests/csv_onboarding_tests.py
index 9992248fd2bd7eb5706ba34bbe32790de35baccc..148517516db9cacc859556f7ae49b9b8ca9f97e8 100644
--- a/user/tests/csv_onboarding_tests.py
+++ b/user/tests/csv_onboarding_tests.py
@@ -6,7 +6,6 @@ from django.test import TestCase
 from rest_framework.response import Response
 
 from user.lib.user_uploader import UserUploader, TAG_DELIMITER
-from aai.models import UserGroup
 from user.models import User, Tag
 
 
@@ -41,7 +40,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.ADMIN
+            uploaded_file, User.AuthGroup.ADMIN
         )
         self.assertEqual(
             response.data["status"], "ok", msg=response.data["detail"]
@@ -65,7 +64,7 @@ class UserTests(TestCase):
         User.objects.create_user(username="b@uni.mail.com", password="b")
 
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.ADMIN
+            uploaded_file, User.AuthGroup.ADMIN
         )
         self.assertEqual(response.data["status"], "ok")
         self.assertIn(
@@ -81,7 +80,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.ADMIN
+            uploaded_file, User.AuthGroup.ADMIN
         )
         self.assertEqual(response.data["status"], "ok")
         self.assertIn(
@@ -97,7 +96,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.ADMIN
+            uploaded_file, User.AuthGroup.ADMIN
         )
         self.assertEqual(response.data["status"], "error")
         self.assertIn(
@@ -112,7 +111,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.ADMIN
+            uploaded_file, User.AuthGroup.ADMIN
         )
         self.assertEqual(response.data["status"], "error")
         self.assertIn(
@@ -127,7 +126,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.ADMIN
+            uploaded_file, User.AuthGroup.ADMIN
         )
         self.assertEqual(response.data["status"], "error")
         self.assertIn(
@@ -159,7 +158,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.INSTRUCTOR
+            uploaded_file, User.AuthGroup.INSTRUCTOR
         )
 
         self.assertEqual(
@@ -189,7 +188,7 @@ class UserTests(TestCase):
 
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.INSTRUCTOR
+            uploaded_file, User.AuthGroup.INSTRUCTOR
         )
         self.assertEqual(
             response.data["status"], "ok", msg=response.data["detail"]
@@ -221,7 +220,7 @@ class UserTests(TestCase):
 
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.INSTRUCTOR
+            uploaded_file, User.AuthGroup.INSTRUCTOR
         )
         self.assertEqual(
             response.data["status"], "ok", msg=response.data["detail"]
@@ -252,7 +251,7 @@ class UserTests(TestCase):
         ]
         uploaded_file = self.create_file(data)
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.INSTRUCTOR
+            uploaded_file, User.AuthGroup.INSTRUCTOR
         )
         self.assertEqual(
             response.data["status"], "ok", msg=response.data["detail"]
@@ -267,7 +266,7 @@ class UserTests(TestCase):
 
         with self.assertRaises(ValueError):
             UserUploader.validate_and_upload(
-                uploaded_file, UserGroup.INSTRUCTOR
+                uploaded_file, User.AuthGroup.INSTRUCTOR
             )
 
     def test_empty_username(self):
@@ -276,7 +275,7 @@ class UserTests(TestCase):
 
         with self.assertRaises(ValueError):
             UserUploader.validate_and_upload(
-                uploaded_file, UserGroup.INSTRUCTOR
+                uploaded_file, User.AuthGroup.INSTRUCTOR
             )
 
     def test_multiple_separators(self):
@@ -284,7 +283,7 @@ class UserTests(TestCase):
         uploaded_file = self.create_file(data)
 
         response: Response = UserUploader.validate_and_upload(
-            uploaded_file, UserGroup.INSTRUCTOR
+            uploaded_file, User.AuthGroup.INSTRUCTOR
         )
         self.assertEqual(
             response.data["status"], "error", msg=response.data["detail"]
diff --git a/user/tests/mutation_validators_tests.py b/user/tests/mutation_validators_tests.py
index f4dac319bc058eab5318fcba079de324626badb2..0c6d470b816bd263a5bffd4195104744bf8f2b01 100644
--- a/user/tests/mutation_validators_tests.py
+++ b/user/tests/mutation_validators_tests.py
@@ -6,7 +6,6 @@ from django.test import TestCase, override_settings
 
 from django.conf import settings
 from user.models import User, UserInTeam, InstructorOfExercise
-from aai.models import UserGroup
 from exercise.models import Exercise, Team
 from user.schema.validators import (
     _check_nonexistent_users,
@@ -74,15 +73,15 @@ class UserMutationValidatorsTest(TestCase):
             get_model(Team, id="10")
 
     def test_group_checker(self):
-        self.assertIsNone(_check_group([self.user1], [UserGroup.TRAINEE]))
+        self.assertIsNone(_check_group([self.user1], [User.AuthGroup.TRAINEE]))
         with self.assertRaises(ValueError):
-            _check_group([self.user1], [UserGroup.INSTRUCTOR])
+            _check_group([self.user1], [User.AuthGroup.INSTRUCTOR])
 
         self.assertIsNone(
-            _check_group([self.instructor], [UserGroup.INSTRUCTOR])
+            _check_group([self.instructor], [User.AuthGroup.INSTRUCTOR])
         )
         with self.assertRaises(ValueError):
-            _check_group([self.instructor], [UserGroup.ADMIN])
+            _check_group([self.instructor], [User.AuthGroup.ADMIN])
 
     def test_presence_checker(self):
         UserInTeam.objects.create(user=self.user1, team=self.team)
diff --git a/user/views.py b/user/views.py
index 93fc560cded495df7bbc06f45faf7530b0542296..4af5ca88b045e7920c7b9622edb9fa9ce3d41369 100644
--- a/user/views.py
+++ b/user/views.py
@@ -3,7 +3,7 @@ from rest_framework import parsers
 from rest_framework.request import Request
 from rest_framework.views import APIView
 
-from aai.models import Perms
+from user.models import User
 from aai.utils import protected
 from common_lib.exceptions import ApiException
 from common_lib.logger import logger, log_user_msg
@@ -13,7 +13,7 @@ from user.lib import UserUploader
 class UploadUserFile(APIView):
     parser_classes = [parsers.MultiPartParser]
 
-    @protected(Perms.update_user.full_name)
+    @protected(User.AuthGroup.INSTRUCTOR)
     def post(self, request: Request, *args, **kwargs):
         """
         Uploads a new users file and checks for its correctness.