core: Initial RBAC (#6806)
* rename consent permission Signed-off-by: Jens Langhammer <jens@goauthentik.io> * the user version Signed-off-by: Jens Langhammer <jens@goauthentik.io> t Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial role Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start form Signed-off-by: Jens Langhammer <jens@goauthentik.io> * some minor table refactoring Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix user, add assign Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add roles ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix backend Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add assign API for roles Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start adding toggle buttons Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start view page Signed-off-by: Jens Langhammer <jens@goauthentik.io> * exclude add_ permission for per-object perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * small cleanup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add permission list for roles Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make sidebar update Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix page header not re-rendering? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fixup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add search Signed-off-by: Jens Langhammer <jens@goauthentik.io> * show first category in table groupBy except when its empty Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make model and object PK optional but required together Signed-off-by: Jens Langhammer <jens@goauthentik.io> * allow for setting global perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * exclude non-authentik permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * exclude models which aren't allowed (base models etc) Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ensure all models have verbose_name set, exclude some more internal objects Signed-off-by: Jens Langhammer <jens@goauthentik.io> * lint fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix role perm assign Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add unasign for global perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add meta changes Signed-off-by: Jens Langhammer <jens@goauthentik.io> * clear modal state after submit Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add roles to our group Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix duplicate url names Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make recursive group query more usable Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add name field to role itself and move group creation to signal Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start sync Signed-off-by: Jens Langhammer <jens@goauthentik.io> * move rbac stuff to separate django app Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix lint and such Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix go Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start API changes Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more API tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make admin interface not require superuser for now, improve error handling Signed-off-by: Jens Langhammer <jens@goauthentik.io> * replace some IsAdminUser where applicable Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate flow inspector perms to actual permission Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix license not being a serializermodel Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add permission modal to models without view page Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add additional permissions to assign/unassign permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add action to unassign user permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add permissions tab to remaining view pages Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix flow inspector permission check Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix codecov config? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more API tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ensure viewsets have an order set Signed-off-by: Jens Langhammer <jens@goauthentik.io> * hopefully the last api name change Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make perm modal less confusing Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start user view permission page Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only make delete bulk form expandable if usedBy is set Signed-off-by: Jens Langhammer <jens@goauthentik.io> * expand permission tables Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more things Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add user global permission table Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix lint Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests' url names Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests for assign perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add unassign tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rebuild permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * prevent assigning/unassigning permissions to internal service accounts Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only enable default api browser in debug Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix role object permissions showing duplicate Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix role link on role object permissions table Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix object permission modal having duplicate close buttons Signed-off-by: Jens Langhammer <jens@goauthentik.io> * return error if user has no global perm and no object perms also improve error display on table Signed-off-by: Jens Langhammer <jens@goauthentik.io> * small optimisation Signed-off-by: Jens Langhammer <jens@goauthentik.io> * optimise even more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update locale Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add system permission for non-object permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * allow access to admin interface based on perm Signed-off-by: Jens Langhammer <jens@goauthentik.io> * clean Signed-off-by: Jens Langhammer <jens@goauthentik.io> * don't exclude base models Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		| @ -17,7 +17,6 @@ from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||
| from structlog.stdlib import get_logger | ||||
| from structlog.testing import capture_logs | ||||
|  | ||||
| @ -38,6 +37,7 @@ from authentik.lib.utils.file import ( | ||||
| from authentik.policies.api.exec import PolicyTestResultSerializer | ||||
| from authentik.policies.engine import PolicyEngine | ||||
| from authentik.policies.types import PolicyResult | ||||
| from authentik.rbac.filters import ObjectFilter | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| @ -122,7 +122,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|     def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: | ||||
|         """Custom filter_queryset method which ignores guardian, but still supports sorting""" | ||||
|         for backend in list(self.filter_backends): | ||||
|             if backend == ObjectPermissionsFilter: | ||||
|             if backend == ObjectFilter: | ||||
|                 continue | ||||
|             queryset = backend().filter_queryset(self.request, queryset, self) | ||||
|         return queryset | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
| from json import loads | ||||
| from typing import Optional | ||||
|  | ||||
| from django.db.models.query import QuerySet | ||||
| from django.http import Http404 | ||||
| from django_filters.filters import CharFilter, ModelMultipleChoiceFilter | ||||
| from django_filters.filterset import FilterSet | ||||
| @ -14,12 +13,12 @@ from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||
|  | ||||
| from authentik.api.decorators import permission_required | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import PassiveSerializer, is_dict | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.rbac.api.roles import RoleSerializer | ||||
|  | ||||
|  | ||||
| class GroupMemberSerializer(ModelSerializer): | ||||
| @ -49,6 +48,12 @@ class GroupSerializer(ModelSerializer): | ||||
|     users_obj = ListSerializer( | ||||
|         child=GroupMemberSerializer(), read_only=True, source="users", required=False | ||||
|     ) | ||||
|     roles_obj = ListSerializer( | ||||
|         child=RoleSerializer(), | ||||
|         read_only=True, | ||||
|         source="roles", | ||||
|         required=False, | ||||
|     ) | ||||
|     parent_name = CharField(source="parent.name", read_only=True, allow_null=True) | ||||
|  | ||||
|     num_pk = IntegerField(read_only=True) | ||||
| @ -71,8 +76,10 @@ class GroupSerializer(ModelSerializer): | ||||
|             "parent", | ||||
|             "parent_name", | ||||
|             "users", | ||||
|             "attributes", | ||||
|             "users_obj", | ||||
|             "attributes", | ||||
|             "roles", | ||||
|             "roles_obj", | ||||
|         ] | ||||
|         extra_kwargs = { | ||||
|             "users": { | ||||
| @ -138,19 +145,6 @@ class GroupViewSet(UsedByMixin, ModelViewSet): | ||||
|     filterset_class = GroupFilter | ||||
|     ordering = ["name"] | ||||
|  | ||||
|     def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: | ||||
|         """Custom filter_queryset method which ignores guardian, but still supports sorting""" | ||||
|         for backend in list(self.filter_backends): | ||||
|             if backend == ObjectPermissionsFilter: | ||||
|                 continue | ||||
|             queryset = backend().filter_queryset(self.request, queryset, self) | ||||
|         return queryset | ||||
|  | ||||
|     def filter_queryset(self, queryset): | ||||
|         if self.request.user.has_perm("authentik_core.view_group"): | ||||
|             return self._filter_queryset_for_list(queryset) | ||||
|         return super().filter_queryset(queryset) | ||||
|  | ||||
|     @permission_required(None, ["authentik_core.add_user"]) | ||||
|     @extend_schema( | ||||
|         request=UserAccountSerializer, | ||||
|  | ||||
| @ -119,6 +119,7 @@ class TransactionApplicationResponseSerializer(PassiveSerializer): | ||||
| class TransactionalApplicationView(APIView): | ||||
|     """Create provider and application and attach them in a single transaction""" | ||||
|  | ||||
|     # TODO: Migrate to a more specific permission | ||||
|     permission_classes = [IsAdminUser] | ||||
|  | ||||
|     @extend_schema( | ||||
|  | ||||
| @ -73,6 +73,11 @@ class UsedByMixin: | ||||
|             # but so we only apply them once, have a simple flag for the first object | ||||
|             first_object = True | ||||
|  | ||||
|             # TODO: This will only return the used-by references that the user can see | ||||
|             # Either we have to leak model information here to not make the list | ||||
|             # useless if the user doesn't have all permissions, or we need to double | ||||
|             # query and check if there is a difference between modes the user can see | ||||
|             # and can't see and add a warning | ||||
|             for obj in get_objects_for_user( | ||||
|                 request.user, f"{app}.view_{model_name}", manager | ||||
|             ).all(): | ||||
|  | ||||
| @ -7,7 +7,6 @@ from django.contrib.auth import update_session_auth_hash | ||||
| from django.contrib.sessions.backends.cache import KEY_PREFIX | ||||
| from django.core.cache import cache | ||||
| from django.db.models.functions import ExtractHour | ||||
| from django.db.models.query import QuerySet | ||||
| from django.db.transaction import atomic | ||||
| from django.db.utils import IntegrityError | ||||
| from django.urls import reverse_lazy | ||||
| @ -52,7 +51,6 @@ from rest_framework.serializers import ( | ||||
| ) | ||||
| from rest_framework.validators import UniqueValidator | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.admin.api.metrics import CoordinateSerializer | ||||
| @ -205,6 +203,7 @@ class UserSelfSerializer(ModelSerializer): | ||||
|     groups = SerializerMethodField() | ||||
|     uid = CharField(read_only=True) | ||||
|     settings = SerializerMethodField() | ||||
|     system_permissions = SerializerMethodField() | ||||
|  | ||||
|     @extend_schema_field( | ||||
|         ListSerializer( | ||||
| @ -226,6 +225,14 @@ class UserSelfSerializer(ModelSerializer): | ||||
|         """Get user settings with tenant and group settings applied""" | ||||
|         return user.group_attributes(self._context["request"]).get("settings", {}) | ||||
|  | ||||
|     def get_system_permissions(self, user: User) -> list[str]: | ||||
|         """Get all system permissions assigned to the user""" | ||||
|         return list( | ||||
|             user.user_permissions.filter( | ||||
|                 content_type__app_label="authentik_rbac", content_type__model="systempermission" | ||||
|             ).values_list("codename", flat=True) | ||||
|         ) | ||||
|  | ||||
|     class Meta: | ||||
|         model = User | ||||
|         fields = [ | ||||
| @ -240,6 +247,7 @@ class UserSelfSerializer(ModelSerializer): | ||||
|             "uid", | ||||
|             "settings", | ||||
|             "type", | ||||
|             "system_permissions", | ||||
|         ] | ||||
|         extra_kwargs = { | ||||
|             "is_active": {"read_only": True}, | ||||
| @ -654,19 +662,6 @@ class UserViewSet(UsedByMixin, ModelViewSet): | ||||
|  | ||||
|         return Response(status=204) | ||||
|  | ||||
|     def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet: | ||||
|         """Custom filter_queryset method which ignores guardian, but still supports sorting""" | ||||
|         for backend in list(self.filter_backends): | ||||
|             if backend == ObjectPermissionsFilter: | ||||
|                 continue | ||||
|             queryset = backend().filter_queryset(self.request, queryset, self) | ||||
|         return queryset | ||||
|  | ||||
|     def filter_queryset(self, queryset): | ||||
|         if self.request.user.has_perm("authentik_core.view_user"): | ||||
|             return self._filter_queryset_for_list(queryset) | ||||
|         return super().filter_queryset(queryset) | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: inline_serializer( | ||||
|  | ||||
							
								
								
									
										45
									
								
								authentik/core/migrations/0032_group_roles.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								authentik/core/migrations/0032_group_roles.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| # Generated by Django 4.2.6 on 2023-10-11 13:37 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|     dependencies = [ | ||||
|         ("authentik_core", "0031_alter_user_type"), | ||||
|         ("authentik_rbac", "0001_initial"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterModelOptions( | ||||
|             name="group", | ||||
|             options={"verbose_name": "Group", "verbose_name_plural": "Groups"}, | ||||
|         ), | ||||
|         migrations.AlterModelOptions( | ||||
|             name="token", | ||||
|             options={ | ||||
|                 "permissions": [("view_token_key", "View token's key")], | ||||
|                 "verbose_name": "Token", | ||||
|                 "verbose_name_plural": "Tokens", | ||||
|             }, | ||||
|         ), | ||||
|         migrations.AlterModelOptions( | ||||
|             name="user", | ||||
|             options={ | ||||
|                 "permissions": [ | ||||
|                     ("reset_user_password", "Reset Password"), | ||||
|                     ("impersonate", "Can impersonate other users"), | ||||
|                     ("assign_user_permissions", "Can assign permissions to users"), | ||||
|                     ("unassign_user_permissions", "Can unassign permissions from users"), | ||||
|                 ], | ||||
|                 "verbose_name": "User", | ||||
|                 "verbose_name_plural": "Users", | ||||
|             }, | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="group", | ||||
|             name="roles", | ||||
|             field=models.ManyToManyField( | ||||
|                 blank=True, related_name="ak_groups", to="authentik_rbac.role" | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,7 +1,7 @@ | ||||
| """authentik core models""" | ||||
| from datetime import timedelta | ||||
| from hashlib import sha256 | ||||
| from typing import Any, Optional | ||||
| from typing import Any, Optional, Self | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from deepmerge import always_merger | ||||
| @ -88,6 +88,8 @@ class Group(SerializerModel): | ||||
|         default=False, help_text=_("Users added to this group will be superusers.") | ||||
|     ) | ||||
|  | ||||
|     roles = models.ManyToManyField("authentik_rbac.Role", related_name="ak_groups", blank=True) | ||||
|  | ||||
|     parent = models.ForeignKey( | ||||
|         "Group", | ||||
|         blank=True, | ||||
| @ -115,6 +117,38 @@ class Group(SerializerModel): | ||||
|         """Recursively check if `user` is member of us, or any parent.""" | ||||
|         return user.all_groups().filter(group_uuid=self.group_uuid).exists() | ||||
|  | ||||
|     def children_recursive(self: Self | QuerySet["Group"]) -> QuerySet["Group"]: | ||||
|         """Recursively get all groups that have this as parent or are indirectly related""" | ||||
|         direct_groups = [] | ||||
|         if isinstance(self, QuerySet): | ||||
|             direct_groups = list(x for x in self.all().values_list("pk", flat=True).iterator()) | ||||
|         else: | ||||
|             direct_groups = [self.pk] | ||||
|         if len(direct_groups) < 1: | ||||
|             return Group.objects.none() | ||||
|         query = """ | ||||
|         WITH RECURSIVE parents AS ( | ||||
|             SELECT authentik_core_group.*, 0 AS relative_depth | ||||
|             FROM authentik_core_group | ||||
|             WHERE authentik_core_group.group_uuid = ANY(%s) | ||||
|  | ||||
|             UNION ALL | ||||
|  | ||||
|             SELECT authentik_core_group.*, parents.relative_depth + 1 | ||||
|             FROM authentik_core_group, parents | ||||
|             WHERE ( | ||||
|                 authentik_core_group.group_uuid = parents.parent_id and | ||||
|                 parents.relative_depth < 20 | ||||
|             ) | ||||
|         ) | ||||
|         SELECT group_uuid | ||||
|         FROM parents | ||||
|         GROUP BY group_uuid, name | ||||
|         ORDER BY name; | ||||
|         """ | ||||
|         group_pks = [group.pk for group in Group.objects.raw(query, [direct_groups]).iterator()] | ||||
|         return Group.objects.filter(pk__in=group_pks) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return f"Group {self.name}" | ||||
|  | ||||
| @ -125,6 +159,8 @@ class Group(SerializerModel): | ||||
|                 "parent", | ||||
|             ), | ||||
|         ) | ||||
|         verbose_name = _("Group") | ||||
|         verbose_name_plural = _("Groups") | ||||
|  | ||||
|  | ||||
| class UserManager(DjangoUserManager): | ||||
| @ -160,33 +196,7 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): | ||||
|         """Recursively get all groups this user is a member of. | ||||
|         At least one query is done to get the direct groups of the user, with groups | ||||
|         there are at most 3 queries done""" | ||||
|         direct_groups = list( | ||||
|             x for x in self.ak_groups.all().values_list("pk", flat=True).iterator() | ||||
|         ) | ||||
|         if len(direct_groups) < 1: | ||||
|             return Group.objects.none() | ||||
|         query = """ | ||||
|         WITH RECURSIVE parents AS ( | ||||
|             SELECT authentik_core_group.*, 0 AS relative_depth | ||||
|             FROM authentik_core_group | ||||
|             WHERE authentik_core_group.group_uuid = ANY(%s) | ||||
|  | ||||
|             UNION ALL | ||||
|  | ||||
|             SELECT authentik_core_group.*, parents.relative_depth + 1 | ||||
|             FROM authentik_core_group, parents | ||||
|             WHERE ( | ||||
|                 authentik_core_group.group_uuid = parents.parent_id and | ||||
|                 parents.relative_depth < 20 | ||||
|             ) | ||||
|         ) | ||||
|         SELECT group_uuid | ||||
|         FROM parents | ||||
|         GROUP BY group_uuid, name | ||||
|         ORDER BY name; | ||||
|         """ | ||||
|         group_pks = [group.pk for group in Group.objects.raw(query, [direct_groups]).iterator()] | ||||
|         return Group.objects.filter(pk__in=group_pks) | ||||
|         return Group.children_recursive(self.ak_groups.all()) | ||||
|  | ||||
|     def group_attributes(self, request: Optional[HttpRequest] = None) -> dict[str, Any]: | ||||
|         """Get a dictionary containing the attributes from all groups the user belongs to, | ||||
| @ -261,12 +271,14 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser): | ||||
|         return get_avatar(self) | ||||
|  | ||||
|     class Meta: | ||||
|         permissions = ( | ||||
|             ("reset_user_password", "Reset Password"), | ||||
|             ("impersonate", "Can impersonate other users"), | ||||
|         ) | ||||
|         verbose_name = _("User") | ||||
|         verbose_name_plural = _("Users") | ||||
|         permissions = [ | ||||
|             ("reset_user_password", _("Reset Password")), | ||||
|             ("impersonate", _("Can impersonate other users")), | ||||
|             ("assign_user_permissions", _("Can assign permissions to users")), | ||||
|             ("unassign_user_permissions", _("Can unassign permissions from users")), | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class Provider(SerializerModel): | ||||
| @ -675,7 +687,7 @@ class Token(SerializerModel, ManagedModel, ExpiringModel): | ||||
|             models.Index(fields=["identifier"]), | ||||
|             models.Index(fields=["key"]), | ||||
|         ] | ||||
|         permissions = (("view_token_key", "View token's key"),) | ||||
|         permissions = [("view_token_key", _("View token's key"))] | ||||
|  | ||||
|  | ||||
| class PropertyMapping(SerializerModel, ManagedModel): | ||||
|  | ||||
| @ -7,6 +7,7 @@ from django.db.models import Model | ||||
| from django.db.models.signals import post_save, pre_delete, pre_save | ||||
| from django.dispatch import receiver | ||||
| from django.http.request import HttpRequest | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import Application, AuthenticatedSession, BackchannelProvider, User | ||||
|  | ||||
| @ -15,6 +16,8 @@ password_changed = Signal() | ||||
| # Arguments: credentials: dict[str, any], request: HttpRequest, stage: Stage | ||||
| login_failed = Signal() | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @receiver(post_save, sender=Application) | ||||
| def post_save_application(sender: type[Model], instance, created: bool, **_): | ||||
|  | ||||
| @ -21,10 +21,9 @@ def create_test_flow( | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User: | ||||
|     """Generate a test-admin user""" | ||||
| def create_test_user(name: Optional[str] = None, **kwargs) -> User: | ||||
|     """Generate a test user""" | ||||
|     uid = generate_id(20) if not name else name | ||||
|     group = Group.objects.create(name=uid, is_superuser=True) | ||||
|     kwargs.setdefault("email", f"{uid}@goauthentik.io") | ||||
|     kwargs.setdefault("username", uid) | ||||
|     user: User = User.objects.create( | ||||
| @ -33,6 +32,13 @@ def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User: | ||||
|     ) | ||||
|     user.set_password(uid) | ||||
|     user.save() | ||||
|     return user | ||||
|  | ||||
|  | ||||
| def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User: | ||||
|     """Generate a test-admin user""" | ||||
|     user = create_test_user(name, **kwargs) | ||||
|     group = Group.objects.create(name=user.name or name, is_superuser=True) | ||||
|     group.users.add(user) | ||||
|     return user | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L