mirror of
https://github.com/goauthentik/authentik.git
synced 2026-08-30 18:51:39 -07:00
core: Actors (#24353)
* I think I'm happy with this structure Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fixup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add api Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix missing engine mode Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * pull in the requests machinery Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fixup and gen Signed-off-by: Jens Langhammer <jens@goauthentik.io> * gen Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-wire things up Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-add more web stuff Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix unrelated test issue Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix mixin Signed-off-by: Jens Langhammer <jens@goauthentik.io> * gen Signed-off-by: Jens Langhammer <jens@goauthentik.io> * plumb in flows Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial web Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix a couple things Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix api tests & schema Signed-off-by: Jens Langhammer <jens@goauthentik.io> * better pick request flow Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * change method Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix web lint Signed-off-by: Jens Langhammer <jens@goauthentik.io> * explicit perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix migrations Signed-off-by: Jens Langhammer <jens@goauthentik.io> * concrete m2m Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix perms Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add some default config Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix permission check -> requesting user checks against rule bindings' policies Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix unrelated Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix api client Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix styling Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add banner Signed-off-by: Jens Langhammer <jens@goauthentik.io> * attributes mixin prep for object attrs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix typo Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix unrelated test failure Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ensure no self-approval Signed-off-by: Jens Langhammer <jens@goauthentik.io> * configurable expiry Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add meta info to requestable target Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more events Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only unique apps Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial app card impl Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add entitlement modal Signed-off-by: Jens Langhammer <jens@goauthentik.io> * respect filters Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-migrate Signed-off-by: Jens Langhammer <jens@goauthentik.io> * improve ux Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix child bindings? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * better ux for rule bindings Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix type check Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial dcr Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-migrate Signed-off-by: Jens Langhammer <jens@goauthentik.io> * handle error Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * init Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rename Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rename Signed-off-by: Jens Langhammer <jens@goauthentik.io> * self-service Signed-off-by: Jens Langhammer <jens@goauthentik.io> * wip Signed-off-by: Jens Langhammer <jens@goauthentik.io> * move to user view Signed-off-by: Jens Langhammer <jens@goauthentik.io> * always expire self Signed-off-by: Jens Langhammer <jens@goauthentik.io> * t Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * actor Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * effective user early Signed-off-by: Jens Langhammer <jens@goauthentik.io> * extract agents Signed-off-by: Jens Langhammer <jens@goauthentik.io> * optimize Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Apply suggestion from @rissson Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Signed-off-by: Jens L. <jens@beryju.org> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens L. <jens@beryju.org> Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
66
authentik/core/migrations/0063_actor.py
Normal file
66
authentik/core/migrations/0063_actor.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Generated by Django 5.2.16 on 2026-07-31 14:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0062_userswitchingsession_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Actor",
|
||||
fields=[
|
||||
(
|
||||
"user_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
("expires", models.DateTimeField(default=None, null=True)),
|
||||
("expiring", models.BooleanField(default=True)),
|
||||
(
|
||||
"policy_behavior",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("mirror", "Mirror policy engine"),
|
||||
("copy", "Copy policy bindings"),
|
||||
("none", "Don't inherit any policy bindings"),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
related_name="actors",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Actor",
|
||||
"verbose_name_plural": "Actors",
|
||||
"abstract": False,
|
||||
"indexes": [
|
||||
models.Index(fields=["expires"], name="authentik_c_expires_ce0be4_idx"),
|
||||
models.Index(fields=["expiring"], name="authentik_c_expirin_fbf26f_idx"),
|
||||
models.Index(
|
||||
fields=["expiring", "expires"], name="authentik_c_expirin_b80614_idx"
|
||||
),
|
||||
],
|
||||
},
|
||||
bases=("authentik_core.user", models.Model),
|
||||
),
|
||||
]
|
||||
@@ -1591,3 +1591,77 @@ class ObjectAttribute(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
||||
verbose_name = _("Object Attribute")
|
||||
verbose_name_plural = _("Object Attributes")
|
||||
unique_together = (("object_type", "key", "enabled"),)
|
||||
|
||||
|
||||
class ActorPolicyInheritance(models.TextChoices):
|
||||
|
||||
MIRROR = "mirror", _("Mirror policy engine")
|
||||
COPY = "copy", _("Copy policy bindings")
|
||||
NONE = "none", _("Don't inherit any policy bindings")
|
||||
|
||||
|
||||
class Actor(ExpiringModel, User):
|
||||
"""Generic actor which can either perform tasks by itself
|
||||
or on behalf of a parent user."""
|
||||
|
||||
parent = models.ForeignKey(
|
||||
User, on_delete=models.SET_DEFAULT, default=None, null=True, related_name="actors"
|
||||
)
|
||||
policy_behavior = models.TextField(choices=ActorPolicyInheritance)
|
||||
|
||||
class Meta(ExpiringModel.Meta):
|
||||
verbose_name = _("Actor")
|
||||
verbose_name_plural = _("Actors")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# policy_behavior determines how the actor derives access and may only be chosen at
|
||||
# creation time; changing it afterwards is rejected.
|
||||
if (
|
||||
not self._state.adding
|
||||
and hasattr(self, "_original_policy_behavior")
|
||||
and self._original_policy_behavior != self.policy_behavior
|
||||
):
|
||||
raise ValidationError(
|
||||
{"policy_behavior": _("Policy behavior cannot be changed after creation.")}
|
||||
)
|
||||
super().save(*args, **kwargs)
|
||||
self._original_policy_behavior = self.policy_behavior
|
||||
|
||||
@classmethod
|
||||
def from_db(cls, db, field_names, values):
|
||||
instance = super().from_db(db, field_names, values)
|
||||
instance._original_policy_behavior = instance.policy_behavior
|
||||
return instance
|
||||
|
||||
def copy_parent_policy_bindings(self):
|
||||
"""Snapshot the parent's directly-assigned policy bindings onto this actor.
|
||||
|
||||
Unlike MIRROR (which defers to the parent live), COPY takes an independent snapshot at
|
||||
creation: the actor holds its own PolicyBindings, so later changes to the parent don't
|
||||
affect it. Only bindings that reference the parent as a *user* are copied -- access the
|
||||
parent derives from group membership is not (use MIRROR, or add the actor to groups).
|
||||
"""
|
||||
from authentik.policies.models import PolicyBinding
|
||||
|
||||
if not self.parent_id:
|
||||
return
|
||||
for binding in PolicyBinding.objects.filter(user_id=self.parent_id):
|
||||
binding.pk = None
|
||||
binding.policy_binding_uuid = uuid4()
|
||||
binding._state.adding = True
|
||||
binding.user = self
|
||||
binding.save()
|
||||
|
||||
@staticmethod
|
||||
def actor_for(user: User | None, policy_behavior: ActorPolicyInheritance, **kwargs):
|
||||
prefix = f"{user.username}" if user else "global"
|
||||
actor = Actor.objects.create(
|
||||
username=f"{prefix}-{generate_id()}",
|
||||
parent=user,
|
||||
policy_behavior=policy_behavior,
|
||||
type=UserTypes.SERVICE_ACCOUNT,
|
||||
**kwargs,
|
||||
)
|
||||
actor.set_unusable_password()
|
||||
actor.save()
|
||||
return actor
|
||||
|
||||
@@ -23,7 +23,7 @@ from guardian.conf import settings
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.blueprints.v1.common import YAMLTag
|
||||
from authentik.core.models import User
|
||||
from authentik.core.models import User, UserTypes
|
||||
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
|
||||
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
|
||||
from authentik.policies.types import PolicyRequest
|
||||
@@ -93,6 +93,12 @@ def get_user(user: User | AnonymousUser) -> dict[str, Any]:
|
||||
}
|
||||
if user.username == settings.ANONYMOUS_USER_NAME:
|
||||
user_data["is_anonymous"] = True
|
||||
# Actions performed by an actor are recorded on behalf of its parent, so the
|
||||
# audit log always ties the activity back to a responsible human.
|
||||
if getattr(user, "type", None) == UserTypes.SERVICE_ACCOUNT and hasattr(user, "actor"):
|
||||
user_data["is_agent"] = True
|
||||
# FIXME: This will need to be adjusted for OAuth OBO
|
||||
user_data["on_behalf_of"] = get_user(user.actor.parent)
|
||||
return user_data
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from django.utils.timezone import now
|
||||
from sentry_sdk import start_span
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.models import Actor, ActorPolicyInheritance, Group, User, UserTypes
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.policies.apps import HIST_POLICIES_ENGINE_TOTAL_TIME, HIST_POLICIES_EXECUTION_TIME
|
||||
from authentik.policies.exceptions import PolicyEngineException
|
||||
@@ -23,6 +23,36 @@ from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
|
||||
CURRENT_PROCESS = current_process()
|
||||
|
||||
# Actors are always service accounts, so a cheap type check keeps the hot policy path free of an
|
||||
# extra query for ordinary (human) users.
|
||||
_ACTOR_USER_TYPES = frozenset({UserTypes.SERVICE_ACCOUNT, UserTypes.INTERNAL_SERVICE_ACCOUNT})
|
||||
|
||||
|
||||
def _get_mirror_parent(user: User) -> User | None:
|
||||
"""Return the parent a MIRROR actor mirrors its policy from, or None.
|
||||
|
||||
An actor with ``policy_behavior == MIRROR`` is evaluated as its parent: it passes a policy
|
||||
exactly when the parent does. Detection resolves the multi-table-inheritance child, memoized
|
||||
on the user instance.
|
||||
"""
|
||||
if getattr(user, "type", None) not in _ACTOR_USER_TYPES:
|
||||
return None
|
||||
if "_actor" not in user.__dict__:
|
||||
user.__dict__["_actor"] = Actor.objects.filter(pk=user.pk).first()
|
||||
actor: Actor | None = user.__dict__["_actor"]
|
||||
if actor and actor.policy_behavior == ActorPolicyInheritance.MIRROR and actor.parent_id:
|
||||
return actor.parent
|
||||
return None
|
||||
|
||||
|
||||
def effective_policy_user(user: User) -> User:
|
||||
"""Follow MIRROR actors up to the identity whose policy result they mirror."""
|
||||
seen = {user.pk}
|
||||
while (parent := _get_mirror_parent(user)) is not None and parent.pk not in seen:
|
||||
seen.add(parent.pk)
|
||||
user = parent
|
||||
return user
|
||||
|
||||
|
||||
class PolicyProcessInfo:
|
||||
"""Dataclass to hold all information and communication channels to a process"""
|
||||
@@ -185,6 +215,7 @@ class PolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
raise PolicyEngineException(f"{pbm} is not instance of PolicyBindingModel")
|
||||
if not user:
|
||||
raise PolicyEngineException("User must be set")
|
||||
user = effective_policy_user(user)
|
||||
self.__pbm = pbm
|
||||
self.request = PolicyRequest(user)
|
||||
self.request.obj = pbm
|
||||
@@ -286,6 +317,8 @@ class FilterPolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
self._init_defaults(pbm)
|
||||
self.__pbm = pbm
|
||||
self.__users = users
|
||||
self.__original_users = users
|
||||
self.__mirror_of: dict = {}
|
||||
self.__http_request = request
|
||||
self.__result: QuerySet[User] | None = None
|
||||
|
||||
@@ -293,6 +326,24 @@ class FilterPolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
"""Get enabled bindings for the bound PBM"""
|
||||
return self._bindings_for(self.__pbm)
|
||||
|
||||
def _substitute_mirror_actors(self):
|
||||
"""Swap MIRROR actors in the user set for the identity they mirror, so they are
|
||||
evaluated in the same pass as everyone else (`_finalize` maps the verdict back). Only
|
||||
service accounts can be actors, so the scan is a cheap, targeted query, and parents that
|
||||
are shared across actors are evaluated once.
|
||||
"""
|
||||
for actor in self.__original_users.filter(type__in=_ACTOR_USER_TYPES):
|
||||
effective = effective_policy_user(actor)
|
||||
if effective.pk != actor.pk:
|
||||
self.__mirror_of[actor.pk] = effective
|
||||
if not self.__mirror_of:
|
||||
return
|
||||
parent_pks = {parent.pk for parent in self.__mirror_of.values()}
|
||||
self.__users = User.objects.filter(
|
||||
Q(pk__in=self.__original_users.exclude(pk__in=self.__mirror_of.keys()).values("pk"))
|
||||
| Q(pk__in=parent_pks)
|
||||
)
|
||||
|
||||
def build(self) -> FilterPolicyEngine:
|
||||
"""Evaluate bindings against the user queryset"""
|
||||
with (
|
||||
@@ -305,13 +356,14 @@ class FilterPolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
obj_pk=str(self.__pbm.pk),
|
||||
).time(),
|
||||
):
|
||||
self._substitute_mirror_actors()
|
||||
bindings = list(self.bindings())
|
||||
for binding in bindings:
|
||||
self._check_policy_type(binding)
|
||||
|
||||
if not bindings:
|
||||
self.__result = self.__users if self.empty_result else self.__users.none()
|
||||
return self
|
||||
return self._finalize()
|
||||
|
||||
dynamic_bindings = [binding for binding in bindings if binding.policy_id is not None]
|
||||
static_bindings = [
|
||||
@@ -326,7 +378,7 @@ class FilterPolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
self.__result = self.__users if self.empty_result else self.__users.none()
|
||||
else:
|
||||
self.__result = self._filter_static(self.__users, static_bindings, self.mode)
|
||||
return self
|
||||
return self._finalize()
|
||||
|
||||
# Slow path: real Policy objects can't be translated to SQL and need
|
||||
# per-user evaluation. Pre-compute the static verdict ONCE via SQL (reused
|
||||
@@ -362,7 +414,25 @@ class FilterPolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
if self._combine_results(self.mode, self.empty_result, all_results).passing:
|
||||
passing_pks.append(user.pk)
|
||||
self.__result = self.__users.filter(pk__in=passing_pks)
|
||||
return self._finalize()
|
||||
|
||||
def _finalize(self) -> FilterPolicyEngine:
|
||||
"""Map the effective-user verdicts back onto the original user set.
|
||||
|
||||
`build()` evaluated the substituted set (parents standing in for their MIRROR actors), so
|
||||
a substituted actor passes iff the identity it mirrors passes. No-op when nothing was
|
||||
substituted -- `self.__result` already refers to the original users.
|
||||
"""
|
||||
if not self.__mirror_of:
|
||||
return self
|
||||
passing = set(self.__result.values_list("pk", flat=True))
|
||||
final_pks = {
|
||||
pk
|
||||
for pk in self.__original_users.values_list("pk", flat=True)
|
||||
if (self.__mirror_of[pk].pk if pk in self.__mirror_of else pk) in passing
|
||||
}
|
||||
self.__result = self.__original_users.filter(pk__in=final_pks)
|
||||
return self
|
||||
|
||||
def _prefetch_cache(
|
||||
self, candidates: list[User], dynamic_bindings: list[PolicyBinding]
|
||||
@@ -441,7 +511,7 @@ class ListPolicyEngine[T: PolicyBindingModel](_PolicyEngineBase):
|
||||
self.empty_result = True
|
||||
self.use_cache = True
|
||||
self.__objs = objs
|
||||
self.__user = user
|
||||
self.__user = effective_policy_user(user)
|
||||
self.__http_request = request
|
||||
self.__result: QuerySet[T] | None = None
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.test import TestCase
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils.timezone import now
|
||||
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.models import Actor, ActorPolicyInheritance, Group, User, UserTypes
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
@@ -360,3 +360,42 @@ class TestFilterPolicyEngine(TestCase):
|
||||
|
||||
self.assertEqual(second, {self.user_a.pk, self.user_b.pk, self.user_c.pk})
|
||||
mock_get_many.assert_called_once()
|
||||
|
||||
def _mirror_actor(self, parent: User) -> Actor:
|
||||
uid = generate_id()
|
||||
return Actor.objects.create(
|
||||
username=uid,
|
||||
name=uid,
|
||||
email=f"{uid}@goauthentik.io",
|
||||
parent=parent,
|
||||
policy_behavior=ActorPolicyInheritance.MIRROR,
|
||||
type=UserTypes.SERVICE_ACCOUNT,
|
||||
)
|
||||
|
||||
def test_mirror_actor_evaluated_as_parent(self):
|
||||
"""A MIRROR actor passes iff the identity it mirrors passes, folded into the same
|
||||
evaluation -- no per-actor PolicyEngine is spawned on the static path."""
|
||||
actor_a = self._mirror_actor(self.user_a) # user_a is in group_a
|
||||
actor_c = self._mirror_actor(self.user_c) # user_c is in no group
|
||||
users = User.objects.filter(pk__in=[actor_a.pk, actor_c.pk, self.user_a.pk, self.user_c.pk])
|
||||
pbm = PolicyBindingModel.objects.create()
|
||||
PolicyBinding.objects.create(target=pbm, group=self.group_a, order=0)
|
||||
|
||||
with patch("authentik.policies.engine.PolicyEngine") as mock_engine:
|
||||
result = set(FilterPolicyEngine(pbm, users).build().result.values_list("pk", flat=True))
|
||||
|
||||
# actor_a mirrors user_a (in group_a) -> passes; actor_c mirrors user_c -> denied.
|
||||
self.assertEqual(result, {self.user_a.pk, actor_a.pk})
|
||||
self.assertEqual(mock_engine.call_count, 0)
|
||||
|
||||
def test_mirror_actor_without_parent_in_set(self):
|
||||
"""The mirrored parent need not itself be in the queryset."""
|
||||
actor_a = self._mirror_actor(self.user_a)
|
||||
users = User.objects.filter(pk__in=[actor_a.pk, self.user_c.pk])
|
||||
pbm = PolicyBindingModel.objects.create()
|
||||
PolicyBinding.objects.create(target=pbm, group=self.group_a, order=0)
|
||||
|
||||
engine = FilterPolicyEngine(pbm, users)
|
||||
result = set(engine.build().result.values_list("pk", flat=True))
|
||||
# Only the actor passes (via user_a's membership); user_a itself is not in the result set.
|
||||
self.assertEqual(result, {actor_a.pk})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1
packages/client-ts/src/models/ModelEnum.ts
generated
1
packages/client-ts/src/models/ModelEnum.ts
generated
@@ -24,6 +24,7 @@ export const ModelEnum = {
|
||||
AuthentikCoreApplicationentitlement: "authentik_core.applicationentitlement",
|
||||
AuthentikCoreToken: "authentik_core.token",
|
||||
AuthentikCoreObjectattribute: "authentik_core.objectattribute",
|
||||
AuthentikCoreActor: "authentik_core.actor",
|
||||
AuthentikCryptoCertificatekeypair: "authentik_crypto.certificatekeypair",
|
||||
AuthentikEndpointsDeviceuserbinding: "authentik_endpoints.deviceuserbinding",
|
||||
AuthentikEndpointsDeviceaccessgroup: "authentik_endpoints.deviceaccessgroup",
|
||||
|
||||
@@ -45013,6 +45013,7 @@ components:
|
||||
- authentik_core.applicationentitlement
|
||||
- authentik_core.token
|
||||
- authentik_core.objectattribute
|
||||
- authentik_core.actor
|
||||
- authentik_crypto.certificatekeypair
|
||||
- authentik_endpoints.deviceuserbinding
|
||||
- authentik_endpoints.deviceaccessgroup
|
||||
|
||||
Reference in New Issue
Block a user