rbac: rework API for terraform, add blueprint support (#10698)

* rbac: rework API slightly to improve terraform compatibility

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* sigh https://www.django-rest-framework.org/api-guide/filtering/#filtering-and-object-lookups

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add permission support for users global permissions

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add role support to blueprints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix yaml tags

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add generated read-only role

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make permissions optional

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add object permission support to blueprints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests kinda

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add more tests and fix bugs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-08-02 16:34:30 +02:00
committed by GitHub
parent 3541ec467c
commit d24e2abe7f
31 changed files with 4117 additions and 77 deletions

15
.vscode/settings.json vendored
View File

@ -23,15 +23,16 @@
"todo-tree.tree.showCountsInTree": true, "todo-tree.tree.showCountsInTree": true,
"todo-tree.tree.showBadges": true, "todo-tree.tree.showBadges": true,
"yaml.customTags": [ "yaml.customTags": [
"!Find sequence",
"!KeyOf scalar",
"!Context scalar",
"!Context sequence",
"!Format sequence",
"!Condition sequence", "!Condition sequence",
"!Env sequence", "!Context scalar",
"!Enumerate sequence",
"!Env scalar", "!Env scalar",
"!If sequence" "!Find sequence",
"!Format sequence",
"!If sequence",
"!Index scalar",
"!KeyOf scalar",
"!Value scalar"
], ],
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index", "typescript.preferences.importModuleSpecifierEnding": "index",

View File

@ -113,16 +113,19 @@ class Command(BaseCommand):
) )
model_path = f"{model._meta.app_label}.{model._meta.model_name}" model_path = f"{model._meta.app_label}.{model._meta.model_name}"
self.schema["properties"]["entries"]["items"]["oneOf"].append( self.schema["properties"]["entries"]["items"]["oneOf"].append(
self.template_entry(model_path, serializer) self.template_entry(model_path, model, serializer)
) )
def template_entry(self, model_path: str, serializer: Serializer) -> dict: def template_entry(self, model_path: str, model: type[Model], serializer: Serializer) -> dict:
"""Template entry for a single model""" """Template entry for a single model"""
model_schema = self.to_jsonschema(serializer) model_schema = self.to_jsonschema(serializer)
model_schema["required"] = [] model_schema["required"] = []
def_name = f"model_{model_path}" def_name = f"model_{model_path}"
def_path = f"#/$defs/{def_name}" def_path = f"#/$defs/{def_name}"
self.schema["$defs"][def_name] = model_schema self.schema["$defs"][def_name] = model_schema
def_name_perm = f"model_{model_path}_permissions"
def_path_perm = f"#/$defs/{def_name_perm}"
self.schema["$defs"][def_name_perm] = self.model_permissions(model)
return { return {
"type": "object", "type": "object",
"required": ["model", "identifiers"], "required": ["model", "identifiers"],
@ -135,6 +138,7 @@ class Command(BaseCommand):
"default": "present", "default": "present",
}, },
"conditions": {"type": "array", "items": {"type": "boolean"}}, "conditions": {"type": "array", "items": {"type": "boolean"}},
"permissions": {"$ref": def_path_perm},
"attrs": {"$ref": def_path}, "attrs": {"$ref": def_path},
"identifiers": {"$ref": def_path}, "identifiers": {"$ref": def_path},
}, },
@ -185,3 +189,20 @@ class Command(BaseCommand):
if required: if required:
result["required"] = required result["required"] = required
return result return result
def model_permissions(self, model: type[Model]) -> dict:
perms = [x[0] for x in model._meta.permissions]
for action in model._meta.default_permissions:
perms.append(f"{action}_{model._meta.model_name}")
return {
"type": "array",
"items": {
"type": "object",
"required": ["permission"],
"properties": {
"permission": {"type": "string", "enum": perms},
"user": {"type": "integer"},
"role": {"type": "string"},
},
},
}

View File

@ -0,0 +1,24 @@
version: 1
entries:
- model: authentik_core.user
id: user
identifiers:
username: "%(id)s"
attrs:
name: "%(id)s"
- model: authentik_rbac.role
id: role
identifiers:
name: "%(id)s"
- model: authentik_flows.flow
identifiers:
slug: "%(id)s"
attrs:
designation: authentication
name: foo
title: foo
permissions:
- permission: view_flow
user: !KeyOf user
- permission: view_flow
role: !KeyOf role

View File

@ -0,0 +1,8 @@
version: 1
entries:
- model: authentik_rbac.role
identifiers:
name: "%(id)s"
attrs:
permissions:
- authentik_blueprints.view_blueprintinstance

View File

@ -0,0 +1,9 @@
version: 1
entries:
- model: authentik_core.user
identifiers:
username: "%(id)s"
attrs:
name: "%(id)s"
permissions:
- authentik_blueprints.view_blueprintinstance

View File

@ -0,0 +1,57 @@
"""Test blueprints v1"""
from django.test import TransactionTestCase
from guardian.shortcuts import get_perms
from authentik.blueprints.v1.importer import Importer
from authentik.core.models import User
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
from authentik.rbac.models import Role
class TestBlueprintsV1RBAC(TransactionTestCase):
"""Test Blueprints rbac attribute"""
def test_user_permission(self):
"""Test permissions"""
uid = generate_id()
import_yaml = load_fixture("fixtures/rbac_user.yaml", id=uid)
importer = Importer.from_string(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
user = User.objects.filter(username=uid).first()
self.assertIsNotNone(user)
self.assertTrue(user.has_perms(["authentik_blueprints.view_blueprintinstance"]))
def test_role_permission(self):
"""Test permissions"""
uid = generate_id()
import_yaml = load_fixture("fixtures/rbac_role.yaml", id=uid)
importer = Importer.from_string(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
role = Role.objects.filter(name=uid).first()
self.assertIsNotNone(role)
self.assertEqual(
list(role.group.permissions.all().values_list("codename", flat=True)),
["view_blueprintinstance"],
)
def test_object_permission(self):
"""Test permissions"""
uid = generate_id()
import_yaml = load_fixture("fixtures/rbac_object.yaml", id=uid)
importer = Importer.from_string(import_yaml)
self.assertTrue(importer.validate()[0])
self.assertTrue(importer.apply())
flow = Flow.objects.filter(slug=uid).first()
user = User.objects.filter(username=uid).first()
role = Role.objects.filter(name=uid).first()
self.assertIsNotNone(flow)
self.assertEqual(get_perms(user, flow), ["view_flow"])
self.assertEqual(get_perms(role.group, flow), ["view_flow"])

View File

@ -1,7 +1,7 @@
"""transfer common classes""" """transfer common classes"""
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Iterable, Mapping from collections.abc import Generator, Iterable, Mapping
from copy import copy from copy import copy
from dataclasses import asdict, dataclass, field, is_dataclass from dataclasses import asdict, dataclass, field, is_dataclass
from enum import Enum from enum import Enum
@ -58,6 +58,15 @@ class BlueprintEntryDesiredState(Enum):
MUST_CREATED = "must_created" MUST_CREATED = "must_created"
@dataclass
class BlueprintEntryPermission:
"""Describe object-level permissions"""
permission: Union[str, "YAMLTag"]
user: Union[int, "YAMLTag", None] = field(default=None)
role: Union[str, "YAMLTag", None] = field(default=None)
@dataclass @dataclass
class BlueprintEntry: class BlueprintEntry:
"""Single entry of a blueprint""" """Single entry of a blueprint"""
@ -69,6 +78,7 @@ class BlueprintEntry:
conditions: list[Any] = field(default_factory=list) conditions: list[Any] = field(default_factory=list)
identifiers: dict[str, Any] = field(default_factory=dict) identifiers: dict[str, Any] = field(default_factory=dict)
attrs: dict[str, Any] | None = field(default_factory=dict) attrs: dict[str, Any] | None = field(default_factory=dict)
permissions: list[BlueprintEntryPermission] = field(default_factory=list)
id: str | None = None id: str | None = None
@ -150,6 +160,17 @@ class BlueprintEntry:
"""Get the blueprint model, with yaml tags resolved if present""" """Get the blueprint model, with yaml tags resolved if present"""
return str(self.tag_resolver(self.model, blueprint)) return str(self.tag_resolver(self.model, blueprint))
def get_permissions(
self, blueprint: "Blueprint"
) -> Generator[BlueprintEntryPermission, None, None]:
"""Get permissions of this entry, with all yaml tags resolved"""
for perm in self.permissions:
yield BlueprintEntryPermission(
permission=self.tag_resolver(perm.permission, blueprint),
user=self.tag_resolver(perm.user, blueprint),
role=self.tag_resolver(perm.role, blueprint),
)
def check_all_conditions_match(self, blueprint: "Blueprint") -> bool: def check_all_conditions_match(self, blueprint: "Blueprint") -> bool:
"""Check all conditions of this entry match (evaluate to True)""" """Check all conditions of this entry match (evaluate to True)"""
return all(self.tag_resolver(self.conditions, blueprint)) return all(self.tag_resolver(self.conditions, blueprint))

View File

@ -16,6 +16,7 @@ from django.db.models.query_utils import Q
from django.db.transaction import atomic from django.db.transaction import atomic
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from guardian.models import UserObjectPermission from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.serializers import BaseSerializer, Serializer from rest_framework.serializers import BaseSerializer, Serializer
from structlog.stdlib import BoundLogger, get_logger from structlog.stdlib import BoundLogger, get_logger
@ -35,6 +36,7 @@ from authentik.core.models import (
PropertyMapping, PropertyMapping,
Provider, Provider,
Source, Source,
User,
UserSourceConnection, UserSourceConnection,
) )
from authentik.enterprise.license import LicenseKey from authentik.enterprise.license import LicenseKey
@ -54,11 +56,13 @@ from authentik.events.utils import cleanse_dict
from authentik.flows.models import FlowToken, Stage from authentik.flows.models import FlowToken, Stage
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.reflection import get_apps
from authentik.outposts.models import OutpostServiceConnection from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.rbac.models import Role
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -136,6 +140,16 @@ def transaction_rollback():
pass pass
def rbac_models() -> dict:
models = {}
for app in get_apps():
for model in app.get_models():
if not is_model_allowed(model):
continue
models[model._meta.model_name] = app.label
return models
class Importer: class Importer:
"""Import Blueprint from raw dict or YAML/JSON""" """Import Blueprint from raw dict or YAML/JSON"""
@ -154,7 +168,10 @@ class Importer:
def default_context(self): def default_context(self):
"""Default context""" """Default context"""
return {"goauthentik.io/enterprise/licensed": LicenseKey.get_total().is_valid()} return {
"goauthentik.io/enterprise/licensed": LicenseKey.get_total().is_valid(),
"goauthentik.io/rbac/models": rbac_models(),
}
@staticmethod @staticmethod
def from_string(yaml_input: str, context: dict | None = None) -> "Importer": def from_string(yaml_input: str, context: dict | None = None) -> "Importer":
@ -320,6 +337,15 @@ class Importer:
) from exc ) from exc
return serializer return serializer
def _apply_permissions(self, instance: Model, entry: BlueprintEntry):
"""Apply object-level permissions for an entry"""
for perm in entry.get_permissions(self._import):
if perm.user is not None:
assign_perm(perm.permission, User.objects.get(pk=perm.user), instance)
if perm.role is not None:
role = Role.objects.get(pk=perm.role)
role.assign_permission(perm.permission, obj=instance)
def apply(self) -> bool: def apply(self) -> bool:
"""Apply (create/update) models yaml, in database transaction""" """Apply (create/update) models yaml, in database transaction"""
try: try:
@ -384,6 +410,7 @@ class Importer:
if "pk" in entry.identifiers: if "pk" in entry.identifiers:
self.__pk_map[entry.identifiers["pk"]] = instance.pk self.__pk_map[entry.identifiers["pk"]] = instance.pk
entry._state = BlueprintEntryState(instance) entry._state = BlueprintEntryState(instance)
self._apply_permissions(instance, entry)
elif state == BlueprintEntryDesiredState.ABSENT: elif state == BlueprintEntryDesiredState.ABSENT:
instance: Model | None = serializer.instance instance: Model | None = serializer.instance
if instance.pk: if instance.pk:

View File

@ -5,6 +5,7 @@ from json import loads
from typing import Any from typing import Any
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.models import Permission
from django.contrib.sessions.backends.cache import KEY_PREFIX from django.contrib.sessions.backends.cache import KEY_PREFIX
from django.core.cache import cache from django.core.cache import cache
from django.db.models.functions import ExtractHour from django.db.models.functions import ExtractHour
@ -33,15 +34,21 @@ from drf_spectacular.utils import (
) )
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, IntegerField, ListField, SerializerMethodField from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
BooleanField,
CharField,
ChoiceField,
DateTimeField,
IntegerField,
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 ( from rest_framework.serializers import (
BooleanField,
DateTimeField,
ListSerializer, ListSerializer,
PrimaryKeyRelatedField, PrimaryKeyRelatedField,
ValidationError,
) )
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
@ -78,6 +85,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner
from authentik.flows.views.executor import QS_KEY_TOKEN from authentik.flows.views.executor import QS_KEY_TOKEN
from authentik.lib.avatars import get_avatar from authentik.lib.avatars import get_avatar
from authentik.rbac.decorators import permission_required from authentik.rbac.decorators import permission_required
from authentik.rbac.models import get_permission_choices
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
@ -141,12 +149,19 @@ class UserSerializer(ModelSerializer):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context: if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["password"] = CharField(required=False, allow_null=True) self.fields["password"] = CharField(required=False, allow_null=True)
self.fields["permissions"] = ListField(
required=False, child=ChoiceField(choices=get_permission_choices())
)
def create(self, validated_data: dict) -> User: def create(self, validated_data: dict) -> User:
"""If this serializer is used in the blueprint context, we allow for """If this serializer is used in the blueprint context, we allow for
directly setting a password. However should be done via the `set_password` directly setting a password. However should be done via the `set_password`
method instead of directly setting it like rest_framework.""" method instead of directly setting it like rest_framework."""
password = validated_data.pop("password", None) password = validated_data.pop("password", None)
permissions = Permission.objects.filter(
codename__in=[x.split(".")[1] for x in validated_data.pop("permissions", [])]
)
validated_data["user_permissions"] = permissions
instance: User = super().create(validated_data) instance: User = super().create(validated_data)
self._set_password(instance, password) self._set_password(instance, password)
return instance return instance
@ -155,6 +170,10 @@ class UserSerializer(ModelSerializer):
"""Same as `create` above, set the password directly if we're in a blueprint """Same as `create` above, set the password directly if we're in a blueprint
context""" context"""
password = validated_data.pop("password", None) password = validated_data.pop("password", None)
permissions = Permission.objects.filter(
codename__in=[x.split(".")[1] for x in validated_data.pop("permissions", [])]
)
validated_data["user_permissions"] = permissions
instance = super().update(instance, validated_data) instance = super().update(instance, validated_data)
self._set_password(instance, password) self._set_password(instance, password)
return instance return instance

View File

@ -23,6 +23,7 @@ class SAMLPropertyMappingFilter(PropertyMappingFilterSet):
class Meta(PropertyMappingFilterSet.Meta): class Meta(PropertyMappingFilterSet.Meta):
model = SAMLPropertyMapping model = SAMLPropertyMapping
fields = PropertyMappingFilterSet.Meta.fields + ["saml_name", "friendly_name"]
class SAMLPropertyMappingViewSet(UsedByMixin, ModelViewSet): class SAMLPropertyMappingViewSet(UsedByMixin, ModelViewSet):

View File

@ -59,6 +59,12 @@ class PermissionSerializer(ModelSerializer):
] ]
class PermissionAssignResultSerializer(PassiveSerializer):
"""Result from assigning permissions to a user/role"""
id = CharField()
class PermissionFilter(FilterSet): class PermissionFilter(FilterSet):
"""Filter permissions""" """Filter permissions"""

View File

@ -16,7 +16,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.utils import ModelSerializer, PassiveSerializer from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.policies.event_matcher.models import model_choices from authentik.policies.event_matcher.models import model_choices
from authentik.rbac.api.rbac import PermissionAssignSerializer from authentik.rbac.api.rbac import PermissionAssignResultSerializer, PermissionAssignSerializer
from authentik.rbac.decorators import permission_required from authentik.rbac.decorators import permission_required
from authentik.rbac.models import Role from authentik.rbac.models import Role
@ -28,7 +28,7 @@ class RoleObjectPermissionSerializer(ModelSerializer):
model = ReadOnlyField(source="content_type.model") model = ReadOnlyField(source="content_type.model")
codename = ReadOnlyField(source="permission.codename") codename = ReadOnlyField(source="permission.codename")
name = ReadOnlyField(source="permission.name") name = ReadOnlyField(source="permission.name")
object_pk = ReadOnlyField() object_pk = CharField()
class Meta: class Meta:
model = GroupObjectPermission model = GroupObjectPermission
@ -88,8 +88,9 @@ class RoleAssignedPermissionViewSet(ListModelMixin, GenericViewSet):
@extend_schema( @extend_schema(
request=PermissionAssignSerializer(), request=PermissionAssignSerializer(),
responses={ responses={
204: OpenApiResponse(description="Successfully assigned"), 200: PermissionAssignResultSerializer(many=True),
}, },
operation_id="rbac_permissions_assigned_by_roles_assign",
) )
@action(methods=["POST"], detail=True, pagination_class=None, filter_backends=[]) @action(methods=["POST"], detail=True, pagination_class=None, filter_backends=[])
def assign(self, request: Request, *args, **kwargs) -> Response: def assign(self, request: Request, *args, **kwargs) -> Response:
@ -98,10 +99,12 @@ class RoleAssignedPermissionViewSet(ListModelMixin, GenericViewSet):
role: Role = self.get_object() role: Role = self.get_object()
data = PermissionAssignSerializer(data=request.data) data = PermissionAssignSerializer(data=request.data)
data.is_valid(raise_exception=True) data.is_valid(raise_exception=True)
ids = []
with atomic(): with atomic():
for perm in data.validated_data["permissions"]: for perm in data.validated_data["permissions"]:
assign_perm(perm, role.group, data.validated_data["model_instance"]) assigned_perm = assign_perm(perm, role.group, data.validated_data["model_instance"])
return Response(status=204) ids.append(PermissionAssignResultSerializer(instance={"id": assigned_perm.pk}).data)
return Response(ids, status=200)
@permission_required("authentik_rbac.unassign_role_permissions") @permission_required("authentik_rbac.unassign_role_permissions")
@extend_schema( @extend_schema(

View File

@ -9,7 +9,7 @@ from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm, remove_perm from guardian.shortcuts import assign_perm, remove_perm
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import BooleanField, ReadOnlyField from rest_framework.fields import BooleanField, CharField, ReadOnlyField
from rest_framework.mixins import ListModelMixin from rest_framework.mixins import ListModelMixin
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -19,7 +19,7 @@ from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.utils import ModelSerializer from authentik.core.api.utils import ModelSerializer
from authentik.core.models import User, UserTypes from authentik.core.models import User, UserTypes
from authentik.policies.event_matcher.models import model_choices from authentik.policies.event_matcher.models import model_choices
from authentik.rbac.api.rbac import PermissionAssignSerializer from authentik.rbac.api.rbac import PermissionAssignResultSerializer, PermissionAssignSerializer
from authentik.rbac.decorators import permission_required from authentik.rbac.decorators import permission_required
@ -30,7 +30,7 @@ class UserObjectPermissionSerializer(ModelSerializer):
model = ReadOnlyField(source="content_type.model") model = ReadOnlyField(source="content_type.model")
codename = ReadOnlyField(source="permission.codename") codename = ReadOnlyField(source="permission.codename")
name = ReadOnlyField(source="permission.name") name = ReadOnlyField(source="permission.name")
object_pk = ReadOnlyField() object_pk = CharField()
class Meta: class Meta:
model = UserObjectPermission model = UserObjectPermission
@ -90,8 +90,9 @@ class UserAssignedPermissionViewSet(ListModelMixin, GenericViewSet):
@extend_schema( @extend_schema(
request=PermissionAssignSerializer(), request=PermissionAssignSerializer(),
responses={ responses={
204: OpenApiResponse(description="Successfully assigned"), 200: PermissionAssignResultSerializer(many=True),
}, },
operation_id="rbac_permissions_assigned_by_users_assign",
) )
@action(methods=["POST"], detail=True, pagination_class=None, filter_backends=[]) @action(methods=["POST"], detail=True, pagination_class=None, filter_backends=[])
def assign(self, request: Request, *args, **kwargs) -> Response: def assign(self, request: Request, *args, **kwargs) -> Response:
@ -101,10 +102,12 @@ class UserAssignedPermissionViewSet(ListModelMixin, GenericViewSet):
raise ValidationError("Permissions cannot be assigned to an internal service account.") raise ValidationError("Permissions cannot be assigned to an internal service account.")
data = PermissionAssignSerializer(data=request.data) data = PermissionAssignSerializer(data=request.data)
data.is_valid(raise_exception=True) data.is_valid(raise_exception=True)
ids = []
with atomic(): with atomic():
for perm in data.validated_data["permissions"]: for perm in data.validated_data["permissions"]:
assign_perm(perm, user, data.validated_data["model_instance"]) assigned_perm = assign_perm(perm, user, data.validated_data["model_instance"])
return Response(status=204) ids.append(PermissionAssignResultSerializer(instance={"id": assigned_perm.pk}).data)
return Response(ids, status=200)
@permission_required("authentik_core.unassign_user_permissions") @permission_required("authentik_core.unassign_user_permissions")
@extend_schema( @extend_schema(

View File

@ -6,7 +6,12 @@ from django_filters.filterset import FilterSet
from guardian.models import GroupObjectPermission from guardian.models import GroupObjectPermission
from guardian.shortcuts import get_objects_for_group from guardian.shortcuts import get_objects_for_group
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.mixins import ListModelMixin from rest_framework.mixins import (
DestroyModelMixin,
ListModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
)
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.pagination import SmallerPagination from authentik.api.pagination import SmallerPagination
@ -64,10 +69,12 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer):
class RolePermissionFilter(FilterSet): class RolePermissionFilter(FilterSet):
"""Role permission filter""" """Role permission filter"""
uuid = UUIDFilter("group__role__uuid", required=True) uuid = UUIDFilter("group__role__uuid")
class RolePermissionViewSet(ListModelMixin, GenericViewSet): class RolePermissionViewSet(
ListModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet
):
"""Get a role's assigned object permissions""" """Get a role's assigned object permissions"""
serializer_class = ExtraRoleObjectPermissionSerializer serializer_class = ExtraRoleObjectPermissionSerializer

View File

@ -6,7 +6,12 @@ from django_filters.filterset import FilterSet
from guardian.models import UserObjectPermission from guardian.models import UserObjectPermission
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.mixins import ListModelMixin from rest_framework.mixins import (
DestroyModelMixin,
ListModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
)
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.pagination import SmallerPagination from authentik.api.pagination import SmallerPagination
@ -64,10 +69,12 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer):
class UserPermissionFilter(FilterSet): class UserPermissionFilter(FilterSet):
"""User-assigned permission filter""" """User-assigned permission filter"""
user_id = NumberFilter("user__id", required=True) user_id = NumberFilter("user__id")
class UserPermissionViewSet(ListModelMixin, GenericViewSet): class UserPermissionViewSet(
ListModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet
):
"""Get a users's assigned object permissions""" """Get a users's assigned object permissions"""
serializer_class = ExtraUserObjectPermissionSerializer serializer_class = ExtraUserObjectPermissionSerializer

View File

@ -1,15 +1,44 @@
"""RBAC Roles""" """RBAC Roles"""
from django.contrib.auth.models import Permission
from rest_framework.fields import (
ChoiceField,
ListField,
)
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
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.api.utils import ModelSerializer
from authentik.rbac.models import Role from authentik.rbac.models import Role, get_permission_choices
class RoleSerializer(ModelSerializer): class RoleSerializer(ModelSerializer):
"""Role serializer""" """Role serializer"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["permissions"] = ListField(
required=False, child=ChoiceField(choices=get_permission_choices())
)
def create(self, validated_data: dict) -> Role:
permissions = Permission.objects.filter(
codename__in=[x.split(".")[1] for x in validated_data.pop("permissions", [])]
)
instance: Role = super().create(validated_data)
instance.group.permissions.set(permissions)
return instance
def update(self, instance: Role, validated_data: dict) -> Role:
permissions = Permission.objects.filter(
codename__in=[x.split(".")[1] for x in validated_data.pop("permissions", [])]
)
instance: Role = super().update(instance, validated_data)
instance.group.permissions.set(permissions)
return instance
class Meta: class Meta:
model = Role model = Role
fields = ["pk", "name"] fields = ["pk", "name"]

View File

@ -2,6 +2,7 @@
from uuid import uuid4 from uuid import uuid4
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 _
@ -11,6 +12,26 @@ from rest_framework.serializers import BaseSerializer
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
def get_permissions():
return (
Permission.objects.all()
.select_related("content_type")
.filter(
content_type__app_label__startswith="authentik",
)
)
def get_permission_choices() -> list[tuple[str, str]]:
return [
(
f"{x.content_type.app_label}.{x.codename}",
f"{x.content_type.app_label}.{x.codename}",
)
for x in get_permissions()
]
class Role(SerializerModel): class Role(SerializerModel):
"""RBAC role, which can have different permissions (both global and per-object) attached """RBAC role, which can have different permissions (both global and per-object) attached
to it.""" to it."""

View File

@ -73,7 +73,7 @@ class TestRBACRoleAPI(APITestCase):
"permissions": ["authentik_stages_invitation.view_invitation"], "permissions": ["authentik_stages_invitation.view_invitation"],
}, },
) )
self.assertEqual(res.status_code, 204) self.assertEqual(res.status_code, 200)
self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation")) self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation"))
def test_assign_object(self): def test_assign_object(self):
@ -96,7 +96,7 @@ class TestRBACRoleAPI(APITestCase):
"object_pk": str(inv.pk), "object_pk": str(inv.pk),
}, },
) )
self.assertEqual(res.status_code, 204) self.assertEqual(res.status_code, 200)
self.assertTrue( self.assertTrue(
self.user.has_perm( self.user.has_perm(
"authentik_stages_invitation.view_invitation", "authentik_stages_invitation.view_invitation",

View File

@ -79,7 +79,7 @@ class TestRBACUserAPI(APITestCase):
"permissions": ["authentik_stages_invitation.view_invitation"], "permissions": ["authentik_stages_invitation.view_invitation"],
}, },
) )
self.assertEqual(res.status_code, 204) self.assertEqual(res.status_code, 200)
self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation")) self.assertTrue(self.user.has_perm("authentik_stages_invitation.view_invitation"))
def test_assign_global_internal_sa(self): def test_assign_global_internal_sa(self):
@ -121,7 +121,7 @@ class TestRBACUserAPI(APITestCase):
"object_pk": str(inv.pk), "object_pk": str(inv.pk),
}, },
) )
self.assertEqual(res.status_code, 204) self.assertEqual(res.status_code, 200)
self.assertTrue( self.assertTrue(
self.user.has_perm( self.user.has_perm(
"authentik_stages_invitation.view_invitation", "authentik_stages_invitation.view_invitation",

View File

@ -32,7 +32,7 @@ class TestRBACPermissionRoles(APITestCase):
) )
self.role.assign_permission("authentik_stages_invitation.view_invitation", obj=inv) self.role.assign_permission("authentik_stages_invitation.view_invitation", obj=inv)
res = self.client.get(reverse("authentik_api:permissions-roles-list")) res = self.client.get(reverse("authentik_api:permissions-roles-list"))
self.assertEqual(res.status_code, 400) self.assertEqual(res.status_code, 200)
def test_list_role(self): def test_list_role(self):
"""Test list of all permissions""" """Test list of all permissions"""

View File

@ -33,7 +33,7 @@ class TestRBACPermissionUsers(APITestCase):
) )
assign_perm("authentik_stages_invitation.view_invitation", self.user, inv) assign_perm("authentik_stages_invitation.view_invitation", self.user, inv)
res = self.client.get(reverse("authentik_api:permissions-users-list")) res = self.client.get(reverse("authentik_api:permissions-users-list"))
self.assertEqual(res.status_code, 400) self.assertEqual(res.status_code, 200)
def test_list_role(self): def test_list_role(self):
"""Test list of all permissions""" """Test list of all permissions"""

View File

@ -0,0 +1,28 @@
metadata:
name: Default - RBAC - Read-only
version: 1
entries:
- model: authentik_rbac.role
identifiers:
name: authentik Read-only
id: role
attrs:
permissions: !Enumerate [
!Context goauthentik.io/rbac/models,
SEQ,
!Format [
"%s.view_%s",
!Value 0,
!Index 0,
],
]
- model: authentik_core.group
identifiers:
name: authentik Read-only
attrs:
roles:
- !KeyOf role
is_superuser: false
attributes:
notes: |
An group with an auto-generated role that allows read-only permissions on all objects.

File diff suppressed because it is too large Load Diff

View File

@ -14825,6 +14825,10 @@ paths:
operationId: propertymappings_saml_list operationId: propertymappings_saml_list
description: SAMLPropertyMapping Viewset description: SAMLPropertyMapping Viewset
parameters: parameters:
- in: query
name: friendly_name
schema:
type: string
- in: query - in: query
name: managed name: managed
schema: schema:
@ -14859,6 +14863,10 @@ paths:
description: Number of results to return per page. description: Number of results to return per page.
schema: schema:
type: integer type: integer
- in: query
name: saml_name
schema:
type: string
- name: search - name: search
required: false required: false
in: query in: query
@ -15953,10 +15961,6 @@ paths:
operationId: propertymappings_source_scim_list operationId: propertymappings_source_scim_list
description: SCIMSourcePropertyMapping Viewset description: SCIMSourcePropertyMapping Viewset
parameters: parameters:
- in: query
name: expression
schema:
type: string
- in: query - in: query
name: managed name: managed
schema: schema:
@ -15965,6 +15969,10 @@ paths:
type: string type: string
explode: true explode: true
style: form style: form
- in: query
name: managed__isnull
schema:
type: boolean
- in: query - in: query
name: name name: name
schema: schema:
@ -15987,11 +15995,6 @@ paths:
description: Number of results to return per page. description: Number of results to return per page.
schema: schema:
type: integer type: integer
- in: query
name: pm_uuid
schema:
type: string
format: uuid
- name: search - name: search
required: false required: false
in: query in: query
@ -21393,7 +21396,7 @@ paths:
description: '' description: ''
/rbac/permissions/assigned_by_roles/{uuid}/assign/: /rbac/permissions/assigned_by_roles/{uuid}/assign/:
post: post:
operationId: rbac_permissions_assigned_by_roles_assign_create operationId: rbac_permissions_assigned_by_roles_assign
description: |- description: |-
Assign permission(s) to role. When `object_pk` is set, the permissions Assign permission(s) to role. When `object_pk` is set, the permissions
are only assigned to the specific object, otherwise they are assigned globally. are only assigned to the specific object, otherwise they are assigned globally.
@ -21416,8 +21419,14 @@ paths:
security: security:
- authentik: [] - authentik: []
responses: responses:
'204': '200':
description: Successfully assigned content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PermissionAssignResult'
description: ''
'400': '400':
content: content:
application/json: application/json:
@ -21614,7 +21623,7 @@ paths:
description: '' description: ''
/rbac/permissions/assigned_by_users/{id}/assign/: /rbac/permissions/assigned_by_users/{id}/assign/:
post: post:
operationId: rbac_permissions_assigned_by_users_assign_create operationId: rbac_permissions_assigned_by_users_assign
description: Assign permission(s) to user description: Assign permission(s) to user
parameters: parameters:
- in: path - in: path
@ -21634,8 +21643,14 @@ paths:
security: security:
- authentik: [] - authentik: []
responses: responses:
'204': '200':
description: Successfully assigned content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PermissionAssignResult'
description: ''
'400': '400':
content: content:
application/json: application/json:
@ -21719,7 +21734,6 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
required: true
tags: tags:
- rbac - rbac
security: security:
@ -21743,6 +21757,146 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/rbac/permissions/roles/{id}/:
get:
operationId: rbac_permissions_roles_retrieve
description: Get a role's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this group object permission.
required: true
tags:
- rbac
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraRoleObjectPermission'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: rbac_permissions_roles_update
description: Get a role's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this group object permission.
required: true
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraRoleObjectPermissionRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraRoleObjectPermission'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: rbac_permissions_roles_partial_update
description: Get a role's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this group object permission.
required: true
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedExtraRoleObjectPermissionRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraRoleObjectPermission'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: rbac_permissions_roles_destroy
description: Get a role's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this group object permission.
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/permissions/users/: /rbac/permissions/users/:
get: get:
operationId: rbac_permissions_users_list operationId: rbac_permissions_users_list
@ -21776,7 +21930,6 @@ paths:
name: user_id name: user_id
schema: schema:
type: integer type: integer
required: true
tags: tags:
- rbac - rbac
security: security:
@ -21800,6 +21953,146 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/rbac/permissions/users/{id}/:
get:
operationId: rbac_permissions_users_retrieve
description: Get a users's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this user object permission.
required: true
tags:
- rbac
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraUserObjectPermission'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: rbac_permissions_users_update
description: Get a users's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this user object permission.
required: true
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraUserObjectPermissionRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraUserObjectPermission'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: rbac_permissions_users_partial_update
description: Get a users's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this user object permission.
required: true
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedExtraUserObjectPermissionRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ExtraUserObjectPermission'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: rbac_permissions_users_destroy
description: Get a users's assigned object permissions
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this user object permission.
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/roles/: /rbac/roles/:
get: get:
operationId: rbac_roles_list operationId: rbac_roles_list
@ -36544,8 +36837,6 @@ components:
readOnly: true readOnly: true
object_pk: object_pk:
type: string type: string
title: Object ID
readOnly: true
name: name:
type: string type: string
readOnly: true readOnly: true
@ -36575,6 +36866,15 @@ components:
- name - name
- object_description - object_description
- object_pk - object_pk
ExtraRoleObjectPermissionRequest:
type: object
description: User permission with additional object-related data
properties:
object_pk:
type: string
minLength: 1
required:
- object_pk
ExtraUserObjectPermission: ExtraUserObjectPermission:
type: object type: object
description: User permission with additional object-related data description: User permission with additional object-related data
@ -36594,8 +36894,6 @@ components:
readOnly: true readOnly: true
object_pk: object_pk:
type: string type: string
title: Object ID
readOnly: true
name: name:
type: string type: string
readOnly: true readOnly: true
@ -36625,6 +36923,15 @@ components:
- name - name
- object_description - object_description
- object_pk - object_pk
ExtraUserObjectPermissionRequest:
type: object
description: User permission with additional object-related data
properties:
object_pk:
type: string
minLength: 1
required:
- object_pk
FilePathRequest: FilePathRequest:
type: object type: object
description: Serializer to upload file description: Serializer to upload file
@ -42500,6 +42807,20 @@ components:
expression: expression:
type: string type: string
minLength: 1 minLength: 1
PatchedExtraRoleObjectPermissionRequest:
type: object
description: User permission with additional object-related data
properties:
object_pk:
type: string
minLength: 1
PatchedExtraUserObjectPermissionRequest:
type: object
description: User permission with additional object-related data
properties:
object_pk:
type: string
minLength: 1
PatchedFlowRequest: PatchedFlowRequest:
type: object type: object
description: Flow Serializer description: Flow Serializer
@ -44497,6 +44818,14 @@ components:
minLength: 1 minLength: 1
required: required:
- permissions - permissions
PermissionAssignResult:
type: object
description: Result from assigning permissions to a user/role
properties:
id:
type: string
required:
- id
PlexAuthenticationChallenge: PlexAuthenticationChallenge:
type: object type: object
description: Challenge shown to the user in identification stage description: Challenge shown to the user in identification stage
@ -46317,8 +46646,6 @@ components:
readOnly: true readOnly: true
object_pk: object_pk:
type: string type: string
title: Object ID
readOnly: true
name: name:
type: string type: string
readOnly: true readOnly: true
@ -49162,8 +49489,6 @@ components:
readOnly: true readOnly: true
object_pk: object_pk:
type: string type: string
title: Object ID
readOnly: true
name: name:
type: string type: string
readOnly: true readOnly: true

View File

@ -53,7 +53,7 @@ export class RoleObjectPermissionForm extends ModelForm<RoleAssignData, number>
} }
send(data: RoleAssignData): Promise<unknown> { send(data: RoleAssignData): Promise<unknown> {
return new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesAssignCreate({ return new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesAssign({
uuid: data.role, uuid: data.role,
permissionAssignRequest: { permissionAssignRequest: {
permissions: Object.keys(data.permissions).filter((key) => data.permissions[key]), permissions: Object.keys(data.permissions).filter((key) => data.permissions[key]),

View File

@ -54,7 +54,7 @@ export class UserObjectPermissionForm extends ModelForm<UserAssignData, number>
} }
send(data: UserAssignData): Promise<unknown> { send(data: UserAssignData): Promise<unknown> {
return new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersAssignCreate({ return new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersAssign({
id: data.user, id: data.user,
permissionAssignRequest: { permissionAssignRequest: {
permissions: Object.keys(data.permissions).filter((key) => data.permissions[key]), permissions: Object.keys(data.permissions).filter((key) => data.permissions[key]),

View File

@ -37,7 +37,7 @@ export class RolePermissionForm extends ModelForm<RolePermissionAssign, number>
} }
async send(data: RolePermissionAssign) { async send(data: RolePermissionAssign) {
await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesAssignCreate({ await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesAssign({
uuid: this.roleUuid || "", uuid: this.roleUuid || "",
permissionAssignRequest: { permissionAssignRequest: {
permissions: data.permissions, permissions: data.permissions,

View File

@ -37,7 +37,7 @@ export class UserPermissionForm extends ModelForm<UserPermissionAssign, number>
} }
async send(data: UserPermissionAssign) { async send(data: UserPermissionAssign) {
await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersAssignCreate({ await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersAssign({
id: this.userId || 0, id: this.userId || 0,
permissionAssignRequest: { permissionAssignRequest: {
permissions: data.permissions, permissions: data.permissions,

View File

@ -2,7 +2,9 @@
Some models behave differently and allow for access to different API fields when created via blueprint. Some models behave differently and allow for access to different API fields when created via blueprint.
### `authentik_core.token` ## `authentik_core.token`
### `key`
:::info :::info
Requires authentik 2023.4 Requires authentik 2023.4
@ -26,7 +28,9 @@ For example:
intent: api intent: api
``` ```
### `authentik_core.user` ## `authentik_core.user`
### `password`
:::info :::info
Requires authentik 2023.6 Requires authentik 2023.6
@ -49,7 +53,29 @@ For example:
password: this-should-be-a-long-value password: this-should-be-a-long-value
``` ```
### `authentik_core.application` ### `permissions`
:::info
Requires authentik 2024.8
:::
The `permissions` field can be used to set global permissions for a user. A full list of possible permissions is included in the JSON schema for blueprints.
For example:
```yaml
# [...]
- model: authentik_core.user
identifiers:
username: test-user
attrs:
permissions:
- authentik_blueprints.view_blueprintinstance
```
## `authentik_core.application`
### `icon`
:::info :::info
Requires authentik 2023.5 Requires authentik 2023.5
@ -69,7 +95,9 @@ For example:
icon: https://goauthentik.io/img/icon.png icon: https://goauthentik.io/img/icon.png
``` ```
### `authentik_sources_oauth.oauthsource`, `authentik_sources_saml.samlsource`, `authentik_sources_plex.plexsource` ## `authentik_sources_oauth.oauthsource`, `authentik_sources_saml.samlsource`, `authentik_sources_plex.plexsource`
### `icon`
:::info :::info
Requires authentik 2023.5 Requires authentik 2023.5
@ -89,7 +117,9 @@ For example:
icon: https://goauthentik.io/img/icon.png icon: https://goauthentik.io/img/icon.png
``` ```
### `authentik_flows.flow` ## `authentik_flows.flow`
### `icon`
:::info :::info
Requires authentik 2023.5 Requires authentik 2023.5
@ -110,3 +140,25 @@ For example:
designation: authentication designation: authentication
background: https://goauthentik.io/img/icon.png background: https://goauthentik.io/img/icon.png
``` ```
## `authentik_rbac.role`
### `permissions`
:::info
Requires authentik 2024.8
:::
The `permissions` field can be used to set global permissions for a role. A full list of possible permissions is included in the JSON schema for blueprints.
For example:
```yaml
# [...]
- model: authentik_rbac.role
identifiers:
name: test-role
attrs:
permissions:
- authentik_blueprints.view_blueprintinstance
```

View File

@ -60,6 +60,11 @@ entries:
designation: stage_configuration designation: stage_configuration
name: default-oobe-setup name: default-oobe-setup
title: Welcome to authentik! title: Welcome to authentik!
# Optionally set object-level permissions on the object
# Requires authentik 2024.8
permissions:
- permission: inspect_flow
user: !Find [authentik_core.user, [username, akadmin]]
``` ```
## Special Labels ## Special Labels

View File

@ -7,15 +7,15 @@ For VS Code, for example, add these entries to your `settings.json`:
``` ```
{ {
"yaml.customTags": [ "yaml.customTags": [
"!KeyOf scalar", "!Condition sequence",
"!Context scalar",
"!Enumerate sequence",
"!Env scalar", "!Env scalar",
"!Find sequence", "!Find sequence",
"!Context scalar",
"!Format sequence", "!Format sequence",
"!If sequence", "!If sequence",
"!Condition sequence",
"!Enumerate sequence",
"!Index scalar", "!Index scalar",
"!KeyOf scalar",
"!Value scalar" "!Value scalar"
] ]
} }