rbac: add InitialPermissions (#13795)
* add `InitialPermissions` model to RBAC This is a powerful construct between Permission and Role to set initial permissions for newly created objects. * use safer `request.user` * fixup! use safer `request.user` * force all self-defined serializers to descend from our custom one See https://github.com/goauthentik/authentik/pull/10139 * reorganize initial permission assignment * fixup! reorganize initial permission assignment
This commit is contained in:
@ -7,7 +7,7 @@ from rest_framework.exceptions import ValidationError
|
|||||||
from rest_framework.fields import CharField, DateTimeField
|
from rest_framework.fields import CharField, DateTimeField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ListSerializer, ModelSerializer
|
from rest_framework.serializers import ListSerializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.blueprints.models import BlueprintInstance
|
from authentik.blueprints.models import BlueprintInstance
|
||||||
@ -15,7 +15,7 @@ from authentik.blueprints.v1.importer import Importer
|
|||||||
from authentik.blueprints.v1.oci import OCI_PREFIX
|
from authentik.blueprints.v1.oci import OCI_PREFIX
|
||||||
from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
|
from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
|
||||||
from authentik.rbac.decorators import permission_required
|
from authentik.rbac.decorators import permission_required
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,8 @@ from rest_framework.serializers import (
|
|||||||
raise_errors_on_nested_writes,
|
raise_errors_on_nested_writes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from authentik.rbac.permissions import assign_initial_permissions
|
||||||
|
|
||||||
|
|
||||||
def is_dict(value: Any):
|
def is_dict(value: Any):
|
||||||
"""Ensure a value is a dictionary, useful for JSONFields"""
|
"""Ensure a value is a dictionary, useful for JSONFields"""
|
||||||
@ -29,6 +31,14 @@ def is_dict(value: Any):
|
|||||||
|
|
||||||
|
|
||||||
class ModelSerializer(BaseModelSerializer):
|
class ModelSerializer(BaseModelSerializer):
|
||||||
|
def create(self, validated_data):
|
||||||
|
instance = super().create(validated_data)
|
||||||
|
|
||||||
|
request = self.context.get("request")
|
||||||
|
if request and hasattr(request, "user") and not request.user.is_anonymous:
|
||||||
|
assign_initial_permissions(request.user, instance)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
def update(self, instance: Model, validated_data):
|
def update(self, instance: Model, validated_data):
|
||||||
raise_errors_on_nested_writes("update", self, validated_data)
|
raise_errors_on_nested_writes("update", self, validated_data)
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
"""Test API Utils"""
|
"""Test API Utils"""
|
||||||
|
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
from rest_framework.serializers import (
|
||||||
|
HyperlinkedModelSerializer,
|
||||||
|
)
|
||||||
|
from rest_framework.serializers import (
|
||||||
|
ModelSerializer as BaseModelSerializer,
|
||||||
|
)
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from authentik.core.api.utils import ModelSerializer as CustomModelSerializer
|
||||||
from authentik.core.api.utils import is_dict
|
from authentik.core.api.utils import is_dict
|
||||||
|
from authentik.lib.utils.reflection import all_subclasses
|
||||||
|
|
||||||
|
|
||||||
class TestAPIUtils(APITestCase):
|
class TestAPIUtils(APITestCase):
|
||||||
@ -14,3 +22,14 @@ class TestAPIUtils(APITestCase):
|
|||||||
self.assertIsNone(is_dict({}))
|
self.assertIsNone(is_dict({}))
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
is_dict("foo")
|
is_dict("foo")
|
||||||
|
|
||||||
|
def test_all_serializers_descend_from_custom(self):
|
||||||
|
"""Test that every serializer we define descends from our own ModelSerializer"""
|
||||||
|
# Weirdly, there's only one serializer in `rest_framework` which descends from
|
||||||
|
# ModelSerializer: HyperlinkedModelSerializer
|
||||||
|
expected = {CustomModelSerializer, HyperlinkedModelSerializer}
|
||||||
|
actual = set(all_subclasses(BaseModelSerializer)) - set(
|
||||||
|
all_subclasses(CustomModelSerializer)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|||||||
@ -4,10 +4,9 @@ from rest_framework.exceptions import PermissionDenied, ValidationError
|
|||||||
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
|
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer
|
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||||
from authentik.enterprise.providers.ssf.models import (
|
from authentik.enterprise.providers.ssf.models import (
|
||||||
DeliveryMethods,
|
DeliveryMethods,
|
||||||
EventTypes,
|
EventTypes,
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.serializers import ModelSerializer
|
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.enterprise.api import EnterpriseRequiredMixin
|
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||||
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
|
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
|
||||||
AuthenticatorEndpointGDTCStage,
|
AuthenticatorEndpointGDTCStage,
|
||||||
|
|||||||
41
authentik/rbac/api/initial_permissions.py
Normal file
41
authentik/rbac/api/initial_permissions.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""RBAC Initial Permissions"""
|
||||||
|
|
||||||
|
from rest_framework.serializers import ListSerializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.core.api.utils import ModelSerializer
|
||||||
|
from authentik.rbac.api.rbac import PermissionSerializer
|
||||||
|
from authentik.rbac.models import InitialPermissions
|
||||||
|
|
||||||
|
|
||||||
|
class InitialPermissionsSerializer(ModelSerializer):
|
||||||
|
"""InitialPermissions serializer"""
|
||||||
|
|
||||||
|
permissions_obj = ListSerializer(
|
||||||
|
child=PermissionSerializer(),
|
||||||
|
read_only=True,
|
||||||
|
source="permissions",
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = InitialPermissions
|
||||||
|
fields = [
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"mode",
|
||||||
|
"role",
|
||||||
|
"permissions",
|
||||||
|
"permissions_obj",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class InitialPermissionsViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
"""InitialPermissions viewset"""
|
||||||
|
|
||||||
|
queryset = InitialPermissions.objects.all()
|
||||||
|
serializer_class = InitialPermissionsSerializer
|
||||||
|
search_fields = ["name"]
|
||||||
|
ordering = ["name"]
|
||||||
|
filterset_fields = ["name"]
|
||||||
39
authentik/rbac/migrations/0005_initialpermissions.py
Normal file
39
authentik/rbac/migrations/0005_initialpermissions.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 5.0.13 on 2025-04-07 13:05
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
|
("authentik_rbac", "0004_alter_systempermission_options"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="InitialPermissions",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.TextField(max_length=150, unique=True)),
|
||||||
|
("mode", models.CharField(choices=[("user", "User"), ("role", "Role")])),
|
||||||
|
("permissions", models.ManyToManyField(blank=True, to="auth.permission")),
|
||||||
|
(
|
||||||
|
"role",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="authentik_rbac.role"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Initial Permissions",
|
||||||
|
"verbose_name_plural": "Initial Permissions",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -3,6 +3,7 @@
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.contrib.auth.management import _get_all_permissions
|
from django.contrib.auth.management import _get_all_permissions
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -75,6 +76,35 @@ class Role(SerializerModel):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class InitialPermissionsMode(models.TextChoices):
|
||||||
|
"""Determines which entity the initial permissions are assigned to."""
|
||||||
|
|
||||||
|
USER = "user", _("User")
|
||||||
|
ROLE = "role", _("Role")
|
||||||
|
|
||||||
|
|
||||||
|
class InitialPermissions(SerializerModel):
|
||||||
|
"""Assigns permissions for newly created objects."""
|
||||||
|
|
||||||
|
name = models.TextField(max_length=150, unique=True)
|
||||||
|
mode = models.CharField(choices=InitialPermissionsMode.choices)
|
||||||
|
role = models.ForeignKey(Role, on_delete=models.CASCADE)
|
||||||
|
permissions = models.ManyToManyField(Permission, blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
|
from authentik.rbac.api.initial_permissions import InitialPermissionsSerializer
|
||||||
|
|
||||||
|
return InitialPermissionsSerializer
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"Initial Permissions for Role #{self.role_id}, applying to #{self.mode}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Initial Permissions")
|
||||||
|
verbose_name_plural = _("Initial Permissions")
|
||||||
|
|
||||||
|
|
||||||
class SystemPermission(models.Model):
|
class SystemPermission(models.Model):
|
||||||
"""System-wide permissions that are not related to any direct
|
"""System-wide permissions that are not related to any direct
|
||||||
database model"""
|
database model"""
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
"""RBAC Permissions"""
|
"""RBAC Permissions"""
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from guardian.shortcuts import assign_perm
|
||||||
from rest_framework.permissions import BasePermission, DjangoObjectPermissions
|
from rest_framework.permissions import BasePermission, DjangoObjectPermissions
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
|
from authentik.rbac.models import InitialPermissions, InitialPermissionsMode
|
||||||
|
|
||||||
|
|
||||||
class ObjectPermissions(DjangoObjectPermissions):
|
class ObjectPermissions(DjangoObjectPermissions):
|
||||||
"""RBAC Permissions"""
|
"""RBAC Permissions"""
|
||||||
@ -51,3 +55,20 @@ def HasPermission(*perm: str) -> type[BasePermission]:
|
|||||||
return bool(request.user and request.user.has_perms(perm))
|
return bool(request.user and request.user.has_perms(perm))
|
||||||
|
|
||||||
return checker
|
return checker
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: add `user: User` type annotation without circular dependencies.
|
||||||
|
# The author of this function isn't proficient/patient enough to do it.
|
||||||
|
def assign_initial_permissions(user, instance: Model):
|
||||||
|
# Performance here should not be an issue, but if needed, there are many optimization routes
|
||||||
|
initial_permissions_list = InitialPermissions.objects.filter(role__group__in=user.groups.all())
|
||||||
|
for initial_permissions in initial_permissions_list:
|
||||||
|
for permission in initial_permissions.permissions.all():
|
||||||
|
if permission.content_type != ContentType.objects.get_for_model(instance):
|
||||||
|
continue
|
||||||
|
assign_to = (
|
||||||
|
user
|
||||||
|
if initial_permissions.mode == InitialPermissionsMode.USER
|
||||||
|
else initial_permissions.role.group
|
||||||
|
)
|
||||||
|
assign_perm(permission, assign_to, instance)
|
||||||
|
|||||||
116
authentik/rbac/tests/test_initial_permissions.py
Normal file
116
authentik/rbac/tests/test_initial_permissions.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""Test InitialPermissions"""
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from guardian.shortcuts import assign_perm
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from authentik.core.models import Group
|
||||||
|
from authentik.core.tests.utils import create_test_user
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
from authentik.rbac.models import InitialPermissions, InitialPermissionsMode, Role
|
||||||
|
from authentik.stages.dummy.models import DummyStage
|
||||||
|
|
||||||
|
|
||||||
|
class TestInitialPermissions(APITestCase):
|
||||||
|
"""Test InitialPermissions"""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.user = create_test_user()
|
||||||
|
self.same_role_user = create_test_user()
|
||||||
|
self.different_role_user = create_test_user()
|
||||||
|
|
||||||
|
self.role = Role.objects.create(name=generate_id())
|
||||||
|
self.different_role = Role.objects.create(name=generate_id())
|
||||||
|
|
||||||
|
self.group = Group.objects.create(name=generate_id())
|
||||||
|
self.different_group = Group.objects.create(name=generate_id())
|
||||||
|
|
||||||
|
self.group.roles.add(self.role)
|
||||||
|
self.group.users.add(self.user, self.same_role_user)
|
||||||
|
self.different_group.roles.add(self.different_role)
|
||||||
|
self.different_group.users.add(self.different_role_user)
|
||||||
|
|
||||||
|
self.ip = InitialPermissions.objects.create(
|
||||||
|
name=generate_id(), mode=InitialPermissionsMode.USER, role=self.role
|
||||||
|
)
|
||||||
|
self.view_role = Permission.objects.filter(codename="view_role").first()
|
||||||
|
self.ip.permissions.add(self.view_role)
|
||||||
|
|
||||||
|
assign_perm("authentik_rbac.add_role", self.user)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def test_different_role(self):
|
||||||
|
"""InitialPermissions for different role does nothing"""
|
||||||
|
self.ip.role = self.different_role
|
||||||
|
self.ip.save()
|
||||||
|
|
||||||
|
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
|
||||||
|
|
||||||
|
role = Role.objects.filter(name="test-role").first()
|
||||||
|
self.assertFalse(self.user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
|
||||||
|
def test_different_model(self):
|
||||||
|
"""InitialPermissions for different model does nothing"""
|
||||||
|
assign_perm("authentik_stages_dummy.add_dummystage", self.user)
|
||||||
|
|
||||||
|
self.client.post(
|
||||||
|
reverse("authentik_api:stages-dummy-list"), {"name": "test-stage", "throw-error": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
role = Role.objects.filter(name="test-role").first()
|
||||||
|
self.assertFalse(self.user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
stage = DummyStage.objects.filter(name="test-stage").first()
|
||||||
|
self.assertFalse(self.user.has_perm("authentik_stages_dummy.view_dummystage", stage))
|
||||||
|
|
||||||
|
def test_mode_user(self):
|
||||||
|
"""InitialPermissions adds user permission in user mode"""
|
||||||
|
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
|
||||||
|
|
||||||
|
role = Role.objects.filter(name="test-role").first()
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
self.assertFalse(self.same_role_user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
|
||||||
|
def test_mode_role(self):
|
||||||
|
"""InitialPermissions adds role permission in role mode"""
|
||||||
|
self.ip.mode = InitialPermissionsMode.ROLE
|
||||||
|
self.ip.save()
|
||||||
|
|
||||||
|
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
|
||||||
|
|
||||||
|
role = Role.objects.filter(name="test-role").first()
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
self.assertTrue(self.same_role_user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
|
||||||
|
def test_many_permissions(self):
|
||||||
|
"""InitialPermissions can add multiple permissions"""
|
||||||
|
change_role = Permission.objects.filter(codename="change_role").first()
|
||||||
|
self.ip.permissions.add(change_role)
|
||||||
|
|
||||||
|
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
|
||||||
|
|
||||||
|
role = Role.objects.filter(name="test-role").first()
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_rbac.change_role", role))
|
||||||
|
|
||||||
|
def test_permissions_separated_by_role(self):
|
||||||
|
"""When the triggering user is part of two different roles with InitialPermissions in role
|
||||||
|
mode, it only adds permissions to the relevant role."""
|
||||||
|
self.ip.mode = InitialPermissionsMode.ROLE
|
||||||
|
self.ip.save()
|
||||||
|
different_ip = InitialPermissions.objects.create(
|
||||||
|
name=generate_id(), mode=InitialPermissionsMode.ROLE, role=self.different_role
|
||||||
|
)
|
||||||
|
change_role = Permission.objects.filter(codename="change_role").first()
|
||||||
|
different_ip.permissions.add(change_role)
|
||||||
|
self.different_group.users.add(self.user)
|
||||||
|
|
||||||
|
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
|
||||||
|
|
||||||
|
role = Role.objects.filter(name="test-role").first()
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
self.assertTrue(self.same_role_user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
self.assertFalse(self.different_role_user.has_perm("authentik_rbac.view_role", role))
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_rbac.change_role", role))
|
||||||
|
self.assertFalse(self.same_role_user.has_perm("authentik_rbac.change_role", role))
|
||||||
|
self.assertTrue(self.different_role_user.has_perm("authentik_rbac.change_role", role))
|
||||||
@ -1,5 +1,6 @@
|
|||||||
"""RBAC API urls"""
|
"""RBAC API urls"""
|
||||||
|
|
||||||
|
from authentik.rbac.api.initial_permissions import InitialPermissionsViewSet
|
||||||
from authentik.rbac.api.rbac import RBACPermissionViewSet
|
from authentik.rbac.api.rbac import RBACPermissionViewSet
|
||||||
from authentik.rbac.api.rbac_assigned_by_roles import RoleAssignedPermissionViewSet
|
from authentik.rbac.api.rbac_assigned_by_roles import RoleAssignedPermissionViewSet
|
||||||
from authentik.rbac.api.rbac_assigned_by_users import UserAssignedPermissionViewSet
|
from authentik.rbac.api.rbac_assigned_by_users import UserAssignedPermissionViewSet
|
||||||
@ -21,5 +22,6 @@ api_urlpatterns = [
|
|||||||
("rbac/permissions/users", UserPermissionViewSet, "permissions-users"),
|
("rbac/permissions/users", UserPermissionViewSet, "permissions-users"),
|
||||||
("rbac/permissions/roles", RolePermissionViewSet, "permissions-roles"),
|
("rbac/permissions/roles", RolePermissionViewSet, "permissions-roles"),
|
||||||
("rbac/permissions", RBACPermissionViewSet),
|
("rbac/permissions", RBACPermissionViewSet),
|
||||||
("rbac/roles", RoleViewSet),
|
("rbac/roles", RoleViewSet, "roles"),
|
||||||
|
("rbac/initial_permissions", InitialPermissionsViewSet, "initial-permissions"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
from authentik.stages.dummy.api import DummyStageViewSet
|
from authentik.stages.dummy.api import DummyStageViewSet
|
||||||
|
|
||||||
api_urlpatterns = [("stages/dummy", DummyStageViewSet)]
|
api_urlpatterns = [("stages/dummy", DummyStageViewSet, "stages-dummy")]
|
||||||
|
|||||||
@ -4,11 +4,12 @@ from drf_spectacular.utils import extend_schema
|
|||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import CharField, ModelSerializer
|
from rest_framework.serializers import CharField
|
||||||
from rest_framework.validators import UniqueValidator
|
from rest_framework.validators import UniqueValidator
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.flows.api.stages import StageSerializer
|
||||||
from authentik.flows.challenge import HttpChallengeResponse
|
from authentik.flows.challenge import HttpChallengeResponse
|
||||||
|
|||||||
@ -15,12 +15,12 @@ from rest_framework.filters import OrderingFilter, SearchFilter
|
|||||||
from rest_framework.permissions import BasePermission
|
from rest_framework.permissions import BasePermission
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import DateTimeField, ModelSerializer
|
from rest_framework.serializers import DateTimeField
|
||||||
from rest_framework.views import View
|
from rest_framework.views import View
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authentication import validate_auth
|
from authentik.api.authentication import validate_auth
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.recovery.lib import create_admin_group, create_recovery_token
|
from authentik.recovery.lib import create_admin_group, create_recovery_token
|
||||||
|
|||||||
@ -1201,6 +1201,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"model",
|
||||||
|
"identifiers"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"const": "authentik_rbac.initialpermissions"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"absent",
|
||||||
|
"present",
|
||||||
|
"created",
|
||||||
|
"must_created"
|
||||||
|
],
|
||||||
|
"default": "present"
|
||||||
|
},
|
||||||
|
"conditions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"$ref": "#/$defs/model_authentik_rbac.initialpermissions_permissions"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"$ref": "#/$defs/model_authentik_rbac.initialpermissions"
|
||||||
|
},
|
||||||
|
"identifiers": {
|
||||||
|
"$ref": "#/$defs/model_authentik_rbac.initialpermissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -4828,6 +4868,7 @@
|
|||||||
"authentik_providers_scim.scimprovider",
|
"authentik_providers_scim.scimprovider",
|
||||||
"authentik_providers_scim.scimmapping",
|
"authentik_providers_scim.scimmapping",
|
||||||
"authentik_rbac.role",
|
"authentik_rbac.role",
|
||||||
|
"authentik_rbac.initialpermissions",
|
||||||
"authentik_sources_kerberos.kerberossource",
|
"authentik_sources_kerberos.kerberossource",
|
||||||
"authentik_sources_kerberos.kerberossourcepropertymapping",
|
"authentik_sources_kerberos.kerberossourcepropertymapping",
|
||||||
"authentik_sources_kerberos.userkerberossourceconnection",
|
"authentik_sources_kerberos.userkerberossourceconnection",
|
||||||
@ -7169,12 +7210,16 @@
|
|||||||
"authentik_providers_ssf.view_stream",
|
"authentik_providers_ssf.view_stream",
|
||||||
"authentik_providers_ssf.view_streamevent",
|
"authentik_providers_ssf.view_streamevent",
|
||||||
"authentik_rbac.access_admin_interface",
|
"authentik_rbac.access_admin_interface",
|
||||||
|
"authentik_rbac.add_initialpermissions",
|
||||||
"authentik_rbac.add_role",
|
"authentik_rbac.add_role",
|
||||||
"authentik_rbac.assign_role_permissions",
|
"authentik_rbac.assign_role_permissions",
|
||||||
|
"authentik_rbac.change_initialpermissions",
|
||||||
"authentik_rbac.change_role",
|
"authentik_rbac.change_role",
|
||||||
|
"authentik_rbac.delete_initialpermissions",
|
||||||
"authentik_rbac.delete_role",
|
"authentik_rbac.delete_role",
|
||||||
"authentik_rbac.edit_system_settings",
|
"authentik_rbac.edit_system_settings",
|
||||||
"authentik_rbac.unassign_role_permissions",
|
"authentik_rbac.unassign_role_permissions",
|
||||||
|
"authentik_rbac.view_initialpermissions",
|
||||||
"authentik_rbac.view_role",
|
"authentik_rbac.view_role",
|
||||||
"authentik_rbac.view_system_info",
|
"authentik_rbac.view_system_info",
|
||||||
"authentik_rbac.view_system_settings",
|
"authentik_rbac.view_system_settings",
|
||||||
@ -7461,6 +7506,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"model_authentik_rbac.initialpermissions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 150,
|
||||||
|
"minLength": 1,
|
||||||
|
"title": "Name"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"user",
|
||||||
|
"role"
|
||||||
|
],
|
||||||
|
"title": "Mode"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"title": "Role"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": "Permissions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
|
"model_authentik_rbac.initialpermissions_permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"permission"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"permission": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"add_initialpermissions",
|
||||||
|
"change_initialpermissions",
|
||||||
|
"delete_initialpermissions",
|
||||||
|
"view_initialpermissions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"model_authentik_sources_kerberos.kerberossource": {
|
"model_authentik_sources_kerberos.kerberossource": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -13793,12 +13896,16 @@
|
|||||||
"authentik_providers_ssf.view_stream",
|
"authentik_providers_ssf.view_stream",
|
||||||
"authentik_providers_ssf.view_streamevent",
|
"authentik_providers_ssf.view_streamevent",
|
||||||
"authentik_rbac.access_admin_interface",
|
"authentik_rbac.access_admin_interface",
|
||||||
|
"authentik_rbac.add_initialpermissions",
|
||||||
"authentik_rbac.add_role",
|
"authentik_rbac.add_role",
|
||||||
"authentik_rbac.assign_role_permissions",
|
"authentik_rbac.assign_role_permissions",
|
||||||
|
"authentik_rbac.change_initialpermissions",
|
||||||
"authentik_rbac.change_role",
|
"authentik_rbac.change_role",
|
||||||
|
"authentik_rbac.delete_initialpermissions",
|
||||||
"authentik_rbac.delete_role",
|
"authentik_rbac.delete_role",
|
||||||
"authentik_rbac.edit_system_settings",
|
"authentik_rbac.edit_system_settings",
|
||||||
"authentik_rbac.unassign_role_permissions",
|
"authentik_rbac.unassign_role_permissions",
|
||||||
|
"authentik_rbac.view_initialpermissions",
|
||||||
"authentik_rbac.view_role",
|
"authentik_rbac.view_role",
|
||||||
"authentik_rbac.view_system_info",
|
"authentik_rbac.view_system_info",
|
||||||
"authentik_rbac.view_system_settings",
|
"authentik_rbac.view_system_settings",
|
||||||
|
|||||||
368
schema.yml
368
schema.yml
@ -24209,6 +24209,270 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/rbac/initial_permissions/:
|
||||||
|
get:
|
||||||
|
operationId: rbac_initial_permissions_list
|
||||||
|
description: InitialPermissions viewset
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: name
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: ordering
|
||||||
|
required: false
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: page
|
||||||
|
required: false
|
||||||
|
in: query
|
||||||
|
description: A page number within the paginated result set.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: page_size
|
||||||
|
required: false
|
||||||
|
in: query
|
||||||
|
description: Number of results to return per page.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: search
|
||||||
|
required: false
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedInitialPermissionsList'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
post:
|
||||||
|
operationId: rbac_initial_permissions_create
|
||||||
|
description: InitialPermissions viewset
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InitialPermissionsRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InitialPermissions'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/rbac/initial_permissions/{id}/:
|
||||||
|
get:
|
||||||
|
operationId: rbac_initial_permissions_retrieve
|
||||||
|
description: InitialPermissions viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Initial Permissions.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InitialPermissions'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
put:
|
||||||
|
operationId: rbac_initial_permissions_update
|
||||||
|
description: InitialPermissions viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Initial Permissions.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InitialPermissionsRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InitialPermissions'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
patch:
|
||||||
|
operationId: rbac_initial_permissions_partial_update
|
||||||
|
description: InitialPermissions viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Initial Permissions.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PatchedInitialPermissionsRequest'
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InitialPermissions'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
delete:
|
||||||
|
operationId: rbac_initial_permissions_destroy
|
||||||
|
description: InitialPermissions viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Initial Permissions.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: No response body
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/rbac/initial_permissions/{id}/used_by/:
|
||||||
|
get:
|
||||||
|
operationId: rbac_initial_permissions_used_by_list
|
||||||
|
description: Get a list of all objects that use this object
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: A unique integer value identifying this Initial Permissions.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- rbac
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/UsedBy'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/rbac/permissions/:
|
/rbac/permissions/:
|
||||||
get:
|
get:
|
||||||
operationId: rbac_permissions_list
|
operationId: rbac_permissions_list
|
||||||
@ -24370,6 +24634,7 @@ paths:
|
|||||||
- authentik_providers_scim.scimmapping
|
- authentik_providers_scim.scimmapping
|
||||||
- authentik_providers_scim.scimprovider
|
- authentik_providers_scim.scimprovider
|
||||||
- authentik_providers_ssf.ssfprovider
|
- authentik_providers_ssf.ssfprovider
|
||||||
|
- authentik_rbac.initialpermissions
|
||||||
- authentik_rbac.role
|
- authentik_rbac.role
|
||||||
- authentik_sources_kerberos.groupkerberossourceconnection
|
- authentik_sources_kerberos.groupkerberossourceconnection
|
||||||
- authentik_sources_kerberos.kerberossource
|
- authentik_sources_kerberos.kerberossource
|
||||||
@ -24616,6 +24881,7 @@ paths:
|
|||||||
- authentik_providers_scim.scimmapping
|
- authentik_providers_scim.scimmapping
|
||||||
- authentik_providers_scim.scimprovider
|
- authentik_providers_scim.scimprovider
|
||||||
- authentik_providers_ssf.ssfprovider
|
- authentik_providers_ssf.ssfprovider
|
||||||
|
- authentik_rbac.initialpermissions
|
||||||
- authentik_rbac.role
|
- authentik_rbac.role
|
||||||
- authentik_sources_kerberos.groupkerberossourceconnection
|
- authentik_sources_kerberos.groupkerberossourceconnection
|
||||||
- authentik_sources_kerberos.kerberossource
|
- authentik_sources_kerberos.kerberossource
|
||||||
@ -45974,6 +46240,63 @@ components:
|
|||||||
minLength: 1
|
minLength: 1
|
||||||
required:
|
required:
|
||||||
- reason
|
- reason
|
||||||
|
InitialPermissions:
|
||||||
|
type: object
|
||||||
|
description: InitialPermissions serializer
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
title: ID
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
maxLength: 150
|
||||||
|
mode:
|
||||||
|
$ref: '#/components/schemas/InitialPermissionsModeEnum'
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
permissions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
permissions_obj:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Permission'
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- name
|
||||||
|
- permissions_obj
|
||||||
|
- pk
|
||||||
|
- role
|
||||||
|
InitialPermissionsModeEnum:
|
||||||
|
enum:
|
||||||
|
- user
|
||||||
|
- role
|
||||||
|
type: string
|
||||||
|
InitialPermissionsRequest:
|
||||||
|
type: object
|
||||||
|
description: InitialPermissions serializer
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 150
|
||||||
|
mode:
|
||||||
|
$ref: '#/components/schemas/InitialPermissionsModeEnum'
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
permissions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- name
|
||||||
|
- role
|
||||||
InstallID:
|
InstallID:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -47662,6 +47985,7 @@ components:
|
|||||||
- authentik_providers_scim.scimprovider
|
- authentik_providers_scim.scimprovider
|
||||||
- authentik_providers_scim.scimmapping
|
- authentik_providers_scim.scimmapping
|
||||||
- authentik_rbac.role
|
- authentik_rbac.role
|
||||||
|
- authentik_rbac.initialpermissions
|
||||||
- authentik_sources_kerberos.kerberossource
|
- authentik_sources_kerberos.kerberossource
|
||||||
- authentik_sources_kerberos.kerberossourcepropertymapping
|
- authentik_sources_kerberos.kerberossourcepropertymapping
|
||||||
- authentik_sources_kerberos.userkerberossourceconnection
|
- authentik_sources_kerberos.userkerberossourceconnection
|
||||||
@ -49390,6 +49714,18 @@ components:
|
|||||||
required:
|
required:
|
||||||
- pagination
|
- pagination
|
||||||
- results
|
- results
|
||||||
|
PaginatedInitialPermissionsList:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pagination:
|
||||||
|
$ref: '#/components/schemas/Pagination'
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/InitialPermissions'
|
||||||
|
required:
|
||||||
|
- pagination
|
||||||
|
- results
|
||||||
PaginatedInvitationList:
|
PaginatedInvitationList:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -51939,6 +52275,23 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
description: When enabled, the stage will succeed and continue even when
|
description: When enabled, the stage will succeed and continue even when
|
||||||
incorrect user info is entered.
|
incorrect user info is entered.
|
||||||
|
PatchedInitialPermissionsRequest:
|
||||||
|
type: object
|
||||||
|
description: InitialPermissions serializer
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 150
|
||||||
|
mode:
|
||||||
|
$ref: '#/components/schemas/InitialPermissionsModeEnum'
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
permissions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
PatchedInvitationRequest:
|
PatchedInvitationRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Invitation Serializer
|
description: Invitation Serializer
|
||||||
@ -54104,6 +54457,21 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
|
PermissionRequest:
|
||||||
|
type: object
|
||||||
|
description: Global permission
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 255
|
||||||
|
codename:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 100
|
||||||
|
required:
|
||||||
|
- codename
|
||||||
|
- name
|
||||||
PlexAuthenticationChallenge:
|
PlexAuthenticationChallenge:
|
||||||
type: object
|
type: object
|
||||||
description: Challenge shown to the user in identification stage
|
description: Challenge shown to the user in identification stage
|
||||||
|
|||||||
@ -131,6 +131,7 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement
|
|||||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||||
|
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||||
["/core/tokens", msg("Tokens and App passwords")],
|
["/core/tokens", msg("Tokens and App passwords")],
|
||||||
["/flow/stages/invitations", msg("Invitations")]]],
|
["/flow/stages/invitations", msg("Invitations")]]],
|
||||||
|
|||||||
@ -84,6 +84,10 @@ export const ROUTES: Route[] = [
|
|||||||
await import("@goauthentik/admin/roles/RoleListPage");
|
await import("@goauthentik/admin/roles/RoleListPage");
|
||||||
return html`<ak-role-list></ak-role-list>`;
|
return html`<ak-role-list></ak-role-list>`;
|
||||||
}),
|
}),
|
||||||
|
new Route(new RegExp("^/identity/initial-permissions$"), async () => {
|
||||||
|
await import("@goauthentik/admin/rbac/InitialPermissionsListPage");
|
||||||
|
return html`<ak-initial-permissions-list></ak-initial-permissions-list>`;
|
||||||
|
}),
|
||||||
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => {
|
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => {
|
||||||
await import("@goauthentik/admin/roles/RoleViewPage");
|
await import("@goauthentik/admin/roles/RoleViewPage");
|
||||||
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
|
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
|
||||||
|
|||||||
150
web/src/admin/rbac/InitialPermissionsForm.ts
Normal file
150
web/src/admin/rbac/InitialPermissionsForm.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { InitialPermissionsModeToLabel } from "@goauthentik/admin/rbac/utils";
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
|
||||||
|
import { DataProvision, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
|
||||||
|
import "@goauthentik/elements/chips/Chip";
|
||||||
|
import "@goauthentik/elements/chips/ChipGroup";
|
||||||
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { TemplateResult, html } from "lit";
|
||||||
|
import { customElement } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
InitialPermissions,
|
||||||
|
InitialPermissionsModeEnum,
|
||||||
|
Permission,
|
||||||
|
RbacApi,
|
||||||
|
RbacRolesListRequest,
|
||||||
|
Role,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
|
export function rbacPermissionPair(item: Permission): DualSelectPair {
|
||||||
|
return [item.id.toString(), html`<div class="selection-main">${item.name}</div>`, item.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ak-initial-permissions-form")
|
||||||
|
export class InitialPermissionsForm extends ModelForm<InitialPermissions, string> {
|
||||||
|
loadInstance(pk: string): Promise<InitialPermissions> {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsRetrieve({
|
||||||
|
id: Number(pk),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSuccessMessage(): string {
|
||||||
|
return this.instance
|
||||||
|
? msg("Successfully updated initial permissions.")
|
||||||
|
: msg("Successfully created initial permissions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(data: InitialPermissions): Promise<InitialPermissions> {
|
||||||
|
if (this.instance?.pk) {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsPartialUpdate({
|
||||||
|
id: this.instance.pk,
|
||||||
|
patchedInitialPermissionsRequest: data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsCreate({
|
||||||
|
initialPermissionsRequest: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderForm(): TemplateResult {
|
||||||
|
return html`<form class="pf-c-form pf-m-horizontal">
|
||||||
|
<ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(this.instance?.name)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("Role")} required name="role">
|
||||||
|
<ak-search-select
|
||||||
|
.fetchObjects=${async (query?: string): Promise<Role[]> => {
|
||||||
|
const args: RbacRolesListRequest = {
|
||||||
|
ordering: "name",
|
||||||
|
};
|
||||||
|
if (query !== undefined) {
|
||||||
|
args.search = query;
|
||||||
|
}
|
||||||
|
const users = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args);
|
||||||
|
return users.results;
|
||||||
|
}}
|
||||||
|
.renderElement=${(role: Role): string => {
|
||||||
|
return role.name;
|
||||||
|
}}
|
||||||
|
.renderDescription=${(role: Role): TemplateResult => {
|
||||||
|
return html`${role.name}`;
|
||||||
|
}}
|
||||||
|
.value=${(role: Role | undefined): string | undefined => {
|
||||||
|
return role?.pk;
|
||||||
|
}}
|
||||||
|
.selected=${(role: Role): boolean => {
|
||||||
|
return this.instance?.role === role.pk;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</ak-search-select>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
||||||
|
<select class="pf-c-form-control">
|
||||||
|
<option
|
||||||
|
value=${InitialPermissionsModeEnum.User}
|
||||||
|
?selected=${this.instance?.mode === InitialPermissionsModeEnum.User}
|
||||||
|
>
|
||||||
|
${InitialPermissionsModeToLabel(InitialPermissionsModeEnum.User)}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value=${InitialPermissionsModeEnum.Role}
|
||||||
|
?selected=${this.instance?.mode === InitialPermissionsModeEnum.Role}
|
||||||
|
>
|
||||||
|
${InitialPermissionsModeToLabel(InitialPermissionsModeEnum.Role)}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("Permissions")} name="permissions">
|
||||||
|
<ak-dual-select-provider
|
||||||
|
.provider=${(page: number, search?: string): Promise<DataProvision> => {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG)
|
||||||
|
.rbacPermissionsList({
|
||||||
|
page: page,
|
||||||
|
search: search,
|
||||||
|
})
|
||||||
|
.then((results) => {
|
||||||
|
return {
|
||||||
|
pagination: results.pagination,
|
||||||
|
options: results.results.map(rbacPermissionPair),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
.selected=${(this.instance?.permissionsObj ?? []).map(rbacPermissionPair)}
|
||||||
|
available-label="${msg("Available Permissions")}"
|
||||||
|
selected-label="${msg("Selected Permissions")}"
|
||||||
|
></ak-dual-select-provider>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Permissions to grant when a new object is created.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
</form>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-initial-permissions-form": InitialPermissionsForm;
|
||||||
|
}
|
||||||
|
}
|
||||||
115
web/src/admin/rbac/InitialPermissionsListPage.ts
Normal file
115
web/src/admin/rbac/InitialPermissionsListPage.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import "@goauthentik/admin/rbac/InitialPermissionsForm";
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||||
|
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||||
|
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { TemplateResult, html } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import { InitialPermissions, RbacApi } from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-initial-permissions-list")
|
||||||
|
export class InitialPermissionsListPage extends TablePage<InitialPermissions> {
|
||||||
|
checkbox = true;
|
||||||
|
clearOnRefresh = true;
|
||||||
|
searchEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pageTitle(): string {
|
||||||
|
return msg("Initial Permissions");
|
||||||
|
}
|
||||||
|
pageDescription(): string {
|
||||||
|
return msg("Set initial permissions for newly created objects.");
|
||||||
|
}
|
||||||
|
pageIcon(): string {
|
||||||
|
return "fa fa-lock";
|
||||||
|
}
|
||||||
|
|
||||||
|
@property()
|
||||||
|
order = "name";
|
||||||
|
|
||||||
|
async apiEndpoint(): Promise<PaginatedResponse<InitialPermissions>> {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsList(
|
||||||
|
await this.defaultEndpointConfig(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
columns(): TableColumn[] {
|
||||||
|
return [new TableColumn(msg("Name"), "name"), new TableColumn(msg("Actions"))];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolbarSelected(): TemplateResult {
|
||||||
|
const disabled = this.selectedElements.length < 1;
|
||||||
|
return html`<ak-forms-delete-bulk
|
||||||
|
objectLabel=${msg("Initial Permissions")}
|
||||||
|
.objects=${this.selectedElements}
|
||||||
|
.usedBy=${(item: InitialPermissions) => {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsUsedByList({
|
||||||
|
id: item.pk,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
.delete=${(item: InitialPermissions) => {
|
||||||
|
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsDestroy({
|
||||||
|
id: item.pk,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||||
|
${msg("Delete")}
|
||||||
|
</button>
|
||||||
|
</ak-forms-delete-bulk>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
return html`<ak-page-header
|
||||||
|
icon=${this.pageIcon()}
|
||||||
|
header=${this.pageTitle()}
|
||||||
|
description=${ifDefined(this.pageDescription())}
|
||||||
|
>
|
||||||
|
</ak-page-header>
|
||||||
|
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
|
<div class="pf-c-card">${this.renderTable()}</div>
|
||||||
|
</section>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
row(item: InitialPermissions): TemplateResult[] {
|
||||||
|
return [
|
||||||
|
html`${item.name}`,
|
||||||
|
html`<ak-forms-modal>
|
||||||
|
<span slot="submit"> ${msg("Update")} </span>
|
||||||
|
<span slot="header"> ${msg("Update Initial Permissions")} </span>
|
||||||
|
<ak-initial-permissions-form slot="form" .instancePk=${item.pk}>
|
||||||
|
</ak-initial-permissions-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||||
|
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</pf-tooltip>
|
||||||
|
</button>
|
||||||
|
</ak-forms-modal>`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderObjectCreate(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ak-forms-modal>
|
||||||
|
<span slot="submit"> ${msg("Create")} </span>
|
||||||
|
<span slot="header"> ${msg("Create Initial Permissions")} </span>
|
||||||
|
<ak-initial-permissions-form slot="form"> </ak-initial-permissions-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||||
|
</ak-forms-modal>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"initial-permissions-list": InitialPermissionsListPage;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
web/src/admin/rbac/utils.ts
Normal file
14
web/src/admin/rbac/utils.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
|
import { InitialPermissionsModeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
|
export function InitialPermissionsModeToLabel(mode: InitialPermissionsModeEnum): string {
|
||||||
|
switch (mode) {
|
||||||
|
case InitialPermissionsModeEnum.User:
|
||||||
|
return msg("User");
|
||||||
|
case InitialPermissionsModeEnum.Role:
|
||||||
|
return msg("Role");
|
||||||
|
case InitialPermissionsModeEnum.UnknownDefaultOpenApi:
|
||||||
|
return msg("Unknown Initial Permissions mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user