core: app entitlements (#12090)
* core: initial app entitlements Signed-off-by: Jens Langhammer <jens@goauthentik.io> * base off of pbm Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests and oauth2 Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add to proxy Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rewrite to use bindings Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make policy bindings form and list more customizable Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * double fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * refine permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add missing rbac modal to app entitlements Signed-off-by: Jens Langhammer <jens@goauthentik.io> * separate scope for app entitlements Signed-off-by: Jens Langhammer <jens@goauthentik.io> * include entitlements mapping in proxy Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add API validation to prevent policies from being bound to entitlements Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make preview Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add initial docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove duplicate docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
54
authentik/core/api/application_entitlements.py
Normal file
54
authentik/core/api/application_entitlements.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Application Roles API Viewset"""
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.core.api.utils import ModelSerializer
|
||||||
|
from authentik.core.models import (
|
||||||
|
Application,
|
||||||
|
ApplicationEntitlement,
|
||||||
|
User,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationEntitlementSerializer(ModelSerializer):
|
||||||
|
"""ApplicationEntitlement Serializer"""
|
||||||
|
|
||||||
|
def validate_app(self, app: Application) -> Application:
|
||||||
|
"""Ensure user has permission to view"""
|
||||||
|
user: User = self._context["request"].user
|
||||||
|
if user.has_perm("view_application", app) or user.has_perm(
|
||||||
|
"authentik_core.view_application"
|
||||||
|
):
|
||||||
|
return app
|
||||||
|
raise ValidationError(_("User does not have access to application."), code="invalid")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ApplicationEntitlement
|
||||||
|
fields = [
|
||||||
|
"pbm_uuid",
|
||||||
|
"name",
|
||||||
|
"app",
|
||||||
|
"attributes",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationEntitlementViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
"""ApplicationEntitlement Viewset"""
|
||||||
|
|
||||||
|
queryset = ApplicationEntitlement.objects.all()
|
||||||
|
serializer_class = ApplicationEntitlementSerializer
|
||||||
|
search_fields = [
|
||||||
|
"pbm_uuid",
|
||||||
|
"name",
|
||||||
|
"app",
|
||||||
|
"attributes",
|
||||||
|
]
|
||||||
|
filterset_fields = [
|
||||||
|
"pbm_uuid",
|
||||||
|
"name",
|
||||||
|
"app",
|
||||||
|
]
|
||||||
|
ordering = ["name"]
|
@ -22,7 +22,7 @@ from authentik.blueprints.v1.common import (
|
|||||||
from authentik.blueprints.v1.importer import Importer
|
from authentik.blueprints.v1.importer import Importer
|
||||||
from authentik.core.api.applications import ApplicationSerializer
|
from authentik.core.api.applications import ApplicationSerializer
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.core.models import Provider
|
from authentik.core.models import Application, Provider
|
||||||
from authentik.lib.utils.reflection import all_subclasses
|
from authentik.lib.utils.reflection import all_subclasses
|
||||||
from authentik.policies.api.bindings import PolicyBindingSerializer
|
from authentik.policies.api.bindings import PolicyBindingSerializer
|
||||||
|
|
||||||
@ -51,6 +51,13 @@ class TransactionProviderField(DictField):
|
|||||||
class TransactionPolicyBindingSerializer(PolicyBindingSerializer):
|
class TransactionPolicyBindingSerializer(PolicyBindingSerializer):
|
||||||
"""PolicyBindingSerializer which does not require target as target is set implicitly"""
|
"""PolicyBindingSerializer which does not require target as target is set implicitly"""
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
# As the PolicyBindingSerializer checks that the correct things can be bound to a target
|
||||||
|
# but we don't have a target here as that's set by the blueprint, pass in an empty app
|
||||||
|
# which will have the correct allowed combination of group/user/policy.
|
||||||
|
attrs["target"] = Application()
|
||||||
|
return super().validate(attrs)
|
||||||
|
|
||||||
class Meta(PolicyBindingSerializer.Meta):
|
class Meta(PolicyBindingSerializer.Meta):
|
||||||
fields = [x for x in PolicyBindingSerializer.Meta.fields if x != "target"]
|
fields = [x for x in PolicyBindingSerializer.Meta.fields if x != "target"]
|
||||||
|
|
||||||
|
45
authentik/core/migrations/0041_applicationentitlement.py
Normal file
45
authentik/core/migrations/0041_applicationentitlement.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 5.0.9 on 2024-11-20 15:16
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_core", "0040_provider_invalidation_flow"),
|
||||||
|
("authentik_policies", "0011_policybinding_failure_result_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ApplicationEntitlement",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"policybindingmodel_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="authentik_policies.policybindingmodel",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("attributes", models.JSONField(blank=True, default=dict)),
|
||||||
|
("name", models.TextField()),
|
||||||
|
(
|
||||||
|
"app",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="authentik_core.application"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Application Entitlement",
|
||||||
|
"verbose_name_plural": "Application Entitlements",
|
||||||
|
"unique_together": {("app", "name")},
|
||||||
|
},
|
||||||
|
bases=("authentik_policies.policybindingmodel", models.Model),
|
||||||
|
),
|
||||||
|
]
|
@ -314,6 +314,32 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
|
|||||||
always_merger.merge(final_attributes, self.attributes)
|
always_merger.merge(final_attributes, self.attributes)
|
||||||
return final_attributes
|
return final_attributes
|
||||||
|
|
||||||
|
def app_entitlements(self, app: "Application | None") -> QuerySet["ApplicationEntitlement"]:
|
||||||
|
"""Get all entitlements this user has for `app`."""
|
||||||
|
if not app:
|
||||||
|
return []
|
||||||
|
all_groups = self.all_groups()
|
||||||
|
qs = app.applicationentitlement_set.filter(
|
||||||
|
Q(
|
||||||
|
Q(bindings__user=self) | Q(bindings__group__in=all_groups),
|
||||||
|
bindings__negate=False,
|
||||||
|
)
|
||||||
|
| Q(
|
||||||
|
Q(~Q(bindings__user=self), bindings__user__isnull=False)
|
||||||
|
| Q(~Q(bindings__group__in=all_groups), bindings__group__isnull=False),
|
||||||
|
bindings__negate=True,
|
||||||
|
),
|
||||||
|
bindings__enabled=True,
|
||||||
|
).order_by("name")
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def app_entitlements_attributes(self, app: "Application | None") -> dict:
|
||||||
|
"""Get a dictionary containing all merged attributes from app entitlements for `app`."""
|
||||||
|
final_attributes = {}
|
||||||
|
for attrs in self.app_entitlements(app).values_list("attributes", flat=True):
|
||||||
|
always_merger.merge(final_attributes, attrs)
|
||||||
|
return final_attributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> Serializer:
|
def serializer(self) -> Serializer:
|
||||||
from authentik.core.api.users import UserSerializer
|
from authentik.core.api.users import UserSerializer
|
||||||
@ -581,6 +607,31 @@ class Application(SerializerModel, PolicyBindingModel):
|
|||||||
verbose_name_plural = _("Applications")
|
verbose_name_plural = _("Applications")
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationEntitlement(AttributesMixin, SerializerModel, PolicyBindingModel):
|
||||||
|
"""Application-scoped entitlement to control authorization in an application"""
|
||||||
|
|
||||||
|
name = models.TextField()
|
||||||
|
|
||||||
|
app = models.ForeignKey(Application, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Application Entitlement")
|
||||||
|
verbose_name_plural = _("Application Entitlements")
|
||||||
|
unique_together = (("app", "name"),)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Application Entitlement {self.name} for app {self.app_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serializer(self) -> type[Serializer]:
|
||||||
|
from authentik.core.api.application_entitlements import ApplicationEntitlementSerializer
|
||||||
|
|
||||||
|
return ApplicationEntitlementSerializer
|
||||||
|
|
||||||
|
def supported_policy_binding_targets(self):
|
||||||
|
return ["group", "user"]
|
||||||
|
|
||||||
|
|
||||||
class SourceUserMatchingModes(models.TextChoices):
|
class SourceUserMatchingModes(models.TextChoices):
|
||||||
"""Different modes a source can handle new/returning users"""
|
"""Different modes a source can handle new/returning users"""
|
||||||
|
|
||||||
|
153
authentik/core/tests/test_application_entitlements.py
Normal file
153
authentik/core/tests/test_application_entitlements.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
"""Test Application Entitlements API"""
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from guardian.shortcuts import assign_perm
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from authentik.core.models import Application, ApplicationEntitlement, Group
|
||||||
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
from authentik.policies.dummy.models import DummyPolicy
|
||||||
|
from authentik.policies.models import PolicyBinding
|
||||||
|
from authentik.providers.oauth2.models import OAuth2Provider
|
||||||
|
|
||||||
|
|
||||||
|
class TestApplicationEntitlements(APITestCase):
|
||||||
|
"""Test application entitlements"""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.user = create_test_user()
|
||||||
|
self.other_user = create_test_user()
|
||||||
|
self.provider = OAuth2Provider.objects.create(
|
||||||
|
name="test",
|
||||||
|
authorization_flow=create_test_flow(),
|
||||||
|
)
|
||||||
|
self.app: Application = Application.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
slug=generate_id(),
|
||||||
|
provider=self.provider,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user(self):
|
||||||
|
"""Test user-direct assignment"""
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
PolicyBinding.objects.create(target=ent, user=self.user, order=0)
|
||||||
|
ents = self.user.app_entitlements(self.app)
|
||||||
|
self.assertEqual(len(ents), 1)
|
||||||
|
self.assertEqual(ents[0].name, ent.name)
|
||||||
|
|
||||||
|
def test_group(self):
|
||||||
|
"""Test direct group"""
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
self.user.ak_groups.add(group)
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
PolicyBinding.objects.create(target=ent, group=group, order=0)
|
||||||
|
ents = self.user.app_entitlements(self.app)
|
||||||
|
self.assertEqual(len(ents), 1)
|
||||||
|
self.assertEqual(ents[0].name, ent.name)
|
||||||
|
|
||||||
|
def test_group_indirect(self):
|
||||||
|
"""Test indirect group"""
|
||||||
|
parent = Group.objects.create(name=generate_id())
|
||||||
|
group = Group.objects.create(name=generate_id(), parent=parent)
|
||||||
|
self.user.ak_groups.add(group)
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
PolicyBinding.objects.create(target=ent, group=parent, order=0)
|
||||||
|
ents = self.user.app_entitlements(self.app)
|
||||||
|
self.assertEqual(len(ents), 1)
|
||||||
|
self.assertEqual(ents[0].name, ent.name)
|
||||||
|
|
||||||
|
def test_negate_user(self):
|
||||||
|
"""Test with negate flag"""
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
PolicyBinding.objects.create(target=ent, user=self.other_user, order=0, negate=True)
|
||||||
|
ents = self.user.app_entitlements(self.app)
|
||||||
|
self.assertEqual(len(ents), 1)
|
||||||
|
self.assertEqual(ents[0].name, ent.name)
|
||||||
|
|
||||||
|
def test_negate_group(self):
|
||||||
|
"""Test with negate flag"""
|
||||||
|
other_group = Group.objects.create(name=generate_id())
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
PolicyBinding.objects.create(target=ent, group=other_group, order=0, negate=True)
|
||||||
|
ents = self.user.app_entitlements(self.app)
|
||||||
|
self.assertEqual(len(ents), 1)
|
||||||
|
self.assertEqual(ents[0].name, ent.name)
|
||||||
|
|
||||||
|
def test_api_perms_global(self):
|
||||||
|
"""Test API creation with global permissions"""
|
||||||
|
assign_perm("authentik_core.add_applicationentitlement", self.user)
|
||||||
|
assign_perm("authentik_core.view_application", self.user)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
res = self.client.post(
|
||||||
|
reverse("authentik_api:applicationentitlement-list"),
|
||||||
|
data={
|
||||||
|
"name": generate_id(),
|
||||||
|
"app": self.app.pk,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 201)
|
||||||
|
|
||||||
|
def test_api_perms_scoped(self):
|
||||||
|
"""Test API creation with scoped permissions"""
|
||||||
|
assign_perm("authentik_core.add_applicationentitlement", self.user)
|
||||||
|
assign_perm("authentik_core.view_application", self.user, self.app)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
res = self.client.post(
|
||||||
|
reverse("authentik_api:applicationentitlement-list"),
|
||||||
|
data={
|
||||||
|
"name": generate_id(),
|
||||||
|
"app": self.app.pk,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 201)
|
||||||
|
|
||||||
|
def test_api_perms_missing(self):
|
||||||
|
"""Test API creation with no permissions"""
|
||||||
|
assign_perm("authentik_core.add_applicationentitlement", self.user)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
res = self.client.post(
|
||||||
|
reverse("authentik_api:applicationentitlement-list"),
|
||||||
|
data={
|
||||||
|
"name": generate_id(),
|
||||||
|
"app": self.app.pk,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 400)
|
||||||
|
self.assertJSONEqual(res.content, {"app": ["User does not have access to application."]})
|
||||||
|
|
||||||
|
def test_api_bindings_policy(self):
|
||||||
|
"""Test that API doesn't allow policies to be bound to this"""
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
policy = DummyPolicy.objects.create(name=generate_id())
|
||||||
|
admin = create_test_admin_user()
|
||||||
|
self.client.force_login(admin)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:policybinding-list"),
|
||||||
|
data={
|
||||||
|
"target": ent.pbm_uuid,
|
||||||
|
"policy": policy.pk,
|
||||||
|
"order": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
response.content.decode(),
|
||||||
|
{"non_field_errors": ["One of 'group', 'user' must be set."]},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_api_bindings_group(self):
|
||||||
|
"""Test that API doesn't allow policies to be bound to this"""
|
||||||
|
ent = ApplicationEntitlement.objects.create(app=self.app, name=generate_id())
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
admin = create_test_admin_user()
|
||||||
|
self.client.force_login(admin)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:policybinding-list"),
|
||||||
|
data={
|
||||||
|
"target": ent.pbm_uuid,
|
||||||
|
"group": group.pk,
|
||||||
|
"order": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
self.assertTrue(PolicyBinding.objects.filter(target=ent.pbm_uuid).exists())
|
@ -6,6 +6,7 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from authentik.core.api.application_entitlements import ApplicationEntitlementViewSet
|
||||||
from authentik.core.api.applications import ApplicationViewSet
|
from authentik.core.api.applications import ApplicationViewSet
|
||||||
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
||||||
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
||||||
@ -69,6 +70,7 @@ urlpatterns = [
|
|||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
("core/authenticated_sessions", AuthenticatedSessionViewSet),
|
("core/authenticated_sessions", AuthenticatedSessionViewSet),
|
||||||
("core/applications", ApplicationViewSet),
|
("core/applications", ApplicationViewSet),
|
||||||
|
("core/application_entitlements", ApplicationEntitlementViewSet),
|
||||||
path(
|
path(
|
||||||
"core/transactional/applications/",
|
"core/transactional/applications/",
|
||||||
TransactionalApplicationView.as_view(),
|
TransactionalApplicationView.as_view(),
|
||||||
|
@ -84,19 +84,17 @@ class PolicyBindingSerializer(ModelSerializer):
|
|||||||
|
|
||||||
def validate(self, attrs: OrderedDict) -> OrderedDict:
|
def validate(self, attrs: OrderedDict) -> OrderedDict:
|
||||||
"""Check that either policy, group or user is set."""
|
"""Check that either policy, group or user is set."""
|
||||||
count = sum(
|
target: PolicyBindingModel = attrs.get("target")
|
||||||
[
|
supported = target.supported_policy_binding_targets()
|
||||||
bool(attrs.get("policy", None)),
|
supported.sort()
|
||||||
bool(attrs.get("group", None)),
|
count = sum([bool(attrs.get(x, None)) for x in supported])
|
||||||
bool(attrs.get("user", None)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
invalid = count > 1
|
invalid = count > 1
|
||||||
empty = count < 1
|
empty = count < 1
|
||||||
|
warning = ", ".join(f"'{x}'" for x in supported)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValidationError("Only one of 'policy', 'group' or 'user' can be set.")
|
raise ValidationError(f"Only one of {warning} can be set.")
|
||||||
if empty:
|
if empty:
|
||||||
raise ValidationError("One of 'policy', 'group' or 'user' must be set.")
|
raise ValidationError(f"One of {warning} must be set.")
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Generated by Django 4.2.5 on 2023-09-13 18:07
|
# Generated by Django 4.2.5 on 2023-09-13 18:07
|
||||||
|
import authentik.lib.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@ -23,4 +25,13 @@ class Migration(migrations.Migration):
|
|||||||
default=30, help_text="Timeout after which Policy execution is terminated."
|
default=30, help_text="Timeout after which Policy execution is terminated."
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="policybinding",
|
||||||
|
name="target",
|
||||||
|
field=authentik.lib.models.InheritanceForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="bindings",
|
||||||
|
to="authentik_policies.policybindingmodel",
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -47,6 +47,10 @@ class PolicyBindingModel(models.Model):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"PolicyBindingModel {self.pbm_uuid}"
|
return f"PolicyBindingModel {self.pbm_uuid}"
|
||||||
|
|
||||||
|
def supported_policy_binding_targets(self):
|
||||||
|
"""Return the list of objects that can be bound to this object."""
|
||||||
|
return ["policy", "user", "group"]
|
||||||
|
|
||||||
|
|
||||||
class PolicyBinding(SerializerModel):
|
class PolicyBinding(SerializerModel):
|
||||||
"""Relationship between a Policy and a PolicyBindingModel."""
|
"""Relationship between a Policy and a PolicyBindingModel."""
|
||||||
@ -81,7 +85,9 @@ class PolicyBinding(SerializerModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
target = InheritanceForeignKey(PolicyBindingModel, on_delete=models.CASCADE, related_name="+")
|
target = InheritanceForeignKey(
|
||||||
|
PolicyBindingModel, on_delete=models.CASCADE, related_name="bindings"
|
||||||
|
)
|
||||||
negate = models.BooleanField(
|
negate = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("Negates the outcome of the policy. Messages are unaffected."),
|
help_text=_("Negates the outcome of the policy. Messages are unaffected."),
|
||||||
|
@ -38,7 +38,7 @@ class TestBindingsAPI(APITestCase):
|
|||||||
)
|
)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{"non_field_errors": ["Only one of 'policy', 'group' or 'user' can be set."]},
|
{"non_field_errors": ["Only one of 'group', 'policy', 'user' can be set."]},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_too_little(self):
|
def test_invalid_too_little(self):
|
||||||
@ -49,5 +49,5 @@ class TestBindingsAPI(APITestCase):
|
|||||||
)
|
)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{"non_field_errors": ["One of 'policy', 'group' or 'user' must be set."]},
|
{"non_field_errors": ["One of 'group', 'policy', 'user' must be set."]},
|
||||||
)
|
)
|
||||||
|
@ -127,6 +127,7 @@ class Traefik3MiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]
|
|||||||
authResponseHeaders=[
|
authResponseHeaders=[
|
||||||
"X-authentik-username",
|
"X-authentik-username",
|
||||||
"X-authentik-groups",
|
"X-authentik-groups",
|
||||||
|
"X-authentik-entitlements",
|
||||||
"X-authentik-email",
|
"X-authentik-email",
|
||||||
"X-authentik-name",
|
"X-authentik-name",
|
||||||
"X-authentik-uid",
|
"X-authentik-uid",
|
||||||
|
@ -147,6 +147,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
|
|||||||
"goauthentik.io/providers/oauth2/scope-openid",
|
"goauthentik.io/providers/oauth2/scope-openid",
|
||||||
"goauthentik.io/providers/oauth2/scope-profile",
|
"goauthentik.io/providers/oauth2/scope-profile",
|
||||||
"goauthentik.io/providers/oauth2/scope-email",
|
"goauthentik.io/providers/oauth2/scope-email",
|
||||||
|
"goauthentik.io/providers/oauth2/scope-entitlements",
|
||||||
"goauthentik.io/providers/proxy/scope-proxy",
|
"goauthentik.io/providers/proxy/scope-proxy",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -3201,6 +3201,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"model",
|
||||||
|
"identifiers"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"const": "authentik_core.applicationentitlement"
|
||||||
|
},
|
||||||
|
"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_core.applicationentitlement_permissions"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"$ref": "#/$defs/model_authentik_core.applicationentitlement"
|
||||||
|
},
|
||||||
|
"identifiers": {
|
||||||
|
"$ref": "#/$defs/model_authentik_core.applicationentitlement"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -4640,6 +4680,7 @@
|
|||||||
"authentik_core.group",
|
"authentik_core.group",
|
||||||
"authentik_core.user",
|
"authentik_core.user",
|
||||||
"authentik_core.application",
|
"authentik_core.application",
|
||||||
|
"authentik_core.applicationentitlement",
|
||||||
"authentik_core.token",
|
"authentik_core.token",
|
||||||
"authentik_enterprise.license",
|
"authentik_enterprise.license",
|
||||||
"authentik_providers_google_workspace.googleworkspaceprovider",
|
"authentik_providers_google_workspace.googleworkspaceprovider",
|
||||||
@ -6369,6 +6410,7 @@
|
|||||||
"authentik_brands.delete_brand",
|
"authentik_brands.delete_brand",
|
||||||
"authentik_brands.view_brand",
|
"authentik_brands.view_brand",
|
||||||
"authentik_core.add_application",
|
"authentik_core.add_application",
|
||||||
|
"authentik_core.add_applicationentitlement",
|
||||||
"authentik_core.add_authenticatedsession",
|
"authentik_core.add_authenticatedsession",
|
||||||
"authentik_core.add_group",
|
"authentik_core.add_group",
|
||||||
"authentik_core.add_groupsourceconnection",
|
"authentik_core.add_groupsourceconnection",
|
||||||
@ -6381,6 +6423,7 @@
|
|||||||
"authentik_core.add_usersourceconnection",
|
"authentik_core.add_usersourceconnection",
|
||||||
"authentik_core.assign_user_permissions",
|
"authentik_core.assign_user_permissions",
|
||||||
"authentik_core.change_application",
|
"authentik_core.change_application",
|
||||||
|
"authentik_core.change_applicationentitlement",
|
||||||
"authentik_core.change_authenticatedsession",
|
"authentik_core.change_authenticatedsession",
|
||||||
"authentik_core.change_group",
|
"authentik_core.change_group",
|
||||||
"authentik_core.change_groupsourceconnection",
|
"authentik_core.change_groupsourceconnection",
|
||||||
@ -6391,6 +6434,7 @@
|
|||||||
"authentik_core.change_user",
|
"authentik_core.change_user",
|
||||||
"authentik_core.change_usersourceconnection",
|
"authentik_core.change_usersourceconnection",
|
||||||
"authentik_core.delete_application",
|
"authentik_core.delete_application",
|
||||||
|
"authentik_core.delete_applicationentitlement",
|
||||||
"authentik_core.delete_authenticatedsession",
|
"authentik_core.delete_authenticatedsession",
|
||||||
"authentik_core.delete_group",
|
"authentik_core.delete_group",
|
||||||
"authentik_core.delete_groupsourceconnection",
|
"authentik_core.delete_groupsourceconnection",
|
||||||
@ -6406,6 +6450,7 @@
|
|||||||
"authentik_core.reset_user_password",
|
"authentik_core.reset_user_password",
|
||||||
"authentik_core.unassign_user_permissions",
|
"authentik_core.unassign_user_permissions",
|
||||||
"authentik_core.view_application",
|
"authentik_core.view_application",
|
||||||
|
"authentik_core.view_applicationentitlement",
|
||||||
"authentik_core.view_authenticatedsession",
|
"authentik_core.view_authenticatedsession",
|
||||||
"authentik_core.view_group",
|
"authentik_core.view_group",
|
||||||
"authentik_core.view_groupsourceconnection",
|
"authentik_core.view_groupsourceconnection",
|
||||||
@ -12614,6 +12659,7 @@
|
|||||||
"authentik_brands.delete_brand",
|
"authentik_brands.delete_brand",
|
||||||
"authentik_brands.view_brand",
|
"authentik_brands.view_brand",
|
||||||
"authentik_core.add_application",
|
"authentik_core.add_application",
|
||||||
|
"authentik_core.add_applicationentitlement",
|
||||||
"authentik_core.add_authenticatedsession",
|
"authentik_core.add_authenticatedsession",
|
||||||
"authentik_core.add_group",
|
"authentik_core.add_group",
|
||||||
"authentik_core.add_groupsourceconnection",
|
"authentik_core.add_groupsourceconnection",
|
||||||
@ -12626,6 +12672,7 @@
|
|||||||
"authentik_core.add_usersourceconnection",
|
"authentik_core.add_usersourceconnection",
|
||||||
"authentik_core.assign_user_permissions",
|
"authentik_core.assign_user_permissions",
|
||||||
"authentik_core.change_application",
|
"authentik_core.change_application",
|
||||||
|
"authentik_core.change_applicationentitlement",
|
||||||
"authentik_core.change_authenticatedsession",
|
"authentik_core.change_authenticatedsession",
|
||||||
"authentik_core.change_group",
|
"authentik_core.change_group",
|
||||||
"authentik_core.change_groupsourceconnection",
|
"authentik_core.change_groupsourceconnection",
|
||||||
@ -12636,6 +12683,7 @@
|
|||||||
"authentik_core.change_user",
|
"authentik_core.change_user",
|
||||||
"authentik_core.change_usersourceconnection",
|
"authentik_core.change_usersourceconnection",
|
||||||
"authentik_core.delete_application",
|
"authentik_core.delete_application",
|
||||||
|
"authentik_core.delete_applicationentitlement",
|
||||||
"authentik_core.delete_authenticatedsession",
|
"authentik_core.delete_authenticatedsession",
|
||||||
"authentik_core.delete_group",
|
"authentik_core.delete_group",
|
||||||
"authentik_core.delete_groupsourceconnection",
|
"authentik_core.delete_groupsourceconnection",
|
||||||
@ -12651,6 +12699,7 @@
|
|||||||
"authentik_core.reset_user_password",
|
"authentik_core.reset_user_password",
|
||||||
"authentik_core.unassign_user_permissions",
|
"authentik_core.unassign_user_permissions",
|
||||||
"authentik_core.view_application",
|
"authentik_core.view_application",
|
||||||
|
"authentik_core.view_applicationentitlement",
|
||||||
"authentik_core.view_authenticatedsession",
|
"authentik_core.view_authenticatedsession",
|
||||||
"authentik_core.view_group",
|
"authentik_core.view_group",
|
||||||
"authentik_core.view_groupsourceconnection",
|
"authentik_core.view_groupsourceconnection",
|
||||||
@ -13263,6 +13312,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"model_authentik_core.applicationentitlement": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"title": "Name"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "App"
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Attributes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
|
"model_authentik_core.applicationentitlement_permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"permission"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"permission": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"add_applicationentitlement",
|
||||||
|
"change_applicationentitlement",
|
||||||
|
"delete_applicationentitlement",
|
||||||
|
"view_applicationentitlement"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"model_authentik_core.token": {
|
"model_authentik_core.token": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -42,9 +42,21 @@ entries:
|
|||||||
"given_name": request.user.name,
|
"given_name": request.user.name,
|
||||||
"preferred_username": request.user.username,
|
"preferred_username": request.user.username,
|
||||||
"nickname": request.user.username,
|
"nickname": request.user.username,
|
||||||
# groups is not part of the official userinfo schema, but is a quasi-standard
|
|
||||||
"groups": [group.name for group in request.user.ak_groups.all()],
|
"groups": [group.name for group in request.user.ak_groups.all()],
|
||||||
}
|
}
|
||||||
|
- identifiers:
|
||||||
|
managed: goauthentik.io/providers/oauth2/scope-entitlements
|
||||||
|
model: authentik_providers_oauth2.scopemapping
|
||||||
|
attrs:
|
||||||
|
name: "authentik default OAuth Mapping: Application Entitlements"
|
||||||
|
scope_name: entitlements
|
||||||
|
description: "Application entitlements"
|
||||||
|
expression: |
|
||||||
|
entitlements = [entitlement.name for entitlement in request.user.app_entitlements(provider.application)]
|
||||||
|
return {
|
||||||
|
"entitlements": entitlements,
|
||||||
|
"roles": entitlements,
|
||||||
|
}
|
||||||
- identifiers:
|
- identifiers:
|
||||||
managed: goauthentik.io/providers/oauth2/scope-offline_access
|
managed: goauthentik.io/providers/oauth2/scope-offline_access
|
||||||
model: authentik_providers_oauth2.scopemapping
|
model: authentik_providers_oauth2.scopemapping
|
||||||
|
@ -14,6 +14,7 @@ type Claims struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
PreferredUsername string `json:"preferred_username"`
|
PreferredUsername string `json:"preferred_username"`
|
||||||
Groups []string `json:"groups"`
|
Groups []string `json:"groups"`
|
||||||
|
Entitlements []string `json:"entitlements"`
|
||||||
Sid string `json:"sid"`
|
Sid string `json:"sid"`
|
||||||
Proxy *ProxyClaims `json:"ak_proxy"`
|
Proxy *ProxyClaims `json:"ak_proxy"`
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ func (a *Application) addHeaders(headers http.Header, c *Claims) {
|
|||||||
// https://goauthentik.io/docs/providers/proxy/proxy
|
// https://goauthentik.io/docs/providers/proxy/proxy
|
||||||
headers.Set("X-authentik-username", c.PreferredUsername)
|
headers.Set("X-authentik-username", c.PreferredUsername)
|
||||||
headers.Set("X-authentik-groups", strings.Join(c.Groups, "|"))
|
headers.Set("X-authentik-groups", strings.Join(c.Groups, "|"))
|
||||||
|
headers.Set("X-authentik-entitlements", strings.Join(c.Entitlements, "|"))
|
||||||
headers.Set("X-authentik-email", c.Email)
|
headers.Set("X-authentik-email", c.Email)
|
||||||
headers.Set("X-authentik-name", c.Name)
|
headers.Set("X-authentik-name", c.Name)
|
||||||
headers.Set("X-authentik-uid", c.Sub)
|
headers.Set("X-authentik-uid", c.Sub)
|
||||||
|
341
schema.yml
341
schema.yml
@ -3097,6 +3097,285 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/core/application_entitlements/:
|
||||||
|
get:
|
||||||
|
operationId: core_application_entitlements_list
|
||||||
|
description: ApplicationEntitlement Viewset
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: app
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
- 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
|
||||||
|
- in: query
|
||||||
|
name: pbm_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
- name: search
|
||||||
|
required: false
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedApplicationEntitlementList'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
post:
|
||||||
|
operationId: core_application_entitlements_create
|
||||||
|
description: ApplicationEntitlement Viewset
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlementRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlement'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/core/application_entitlements/{pbm_uuid}/:
|
||||||
|
get:
|
||||||
|
operationId: core_application_entitlements_retrieve
|
||||||
|
description: ApplicationEntitlement Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pbm_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this Application Entitlement.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlement'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
put:
|
||||||
|
operationId: core_application_entitlements_update
|
||||||
|
description: ApplicationEntitlement Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pbm_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this Application Entitlement.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlementRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlement'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
patch:
|
||||||
|
operationId: core_application_entitlements_partial_update
|
||||||
|
description: ApplicationEntitlement Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pbm_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this Application Entitlement.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PatchedApplicationEntitlementRequest'
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlement'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
delete:
|
||||||
|
operationId: core_application_entitlements_destroy
|
||||||
|
description: ApplicationEntitlement Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pbm_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this Application Entitlement.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
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: ''
|
||||||
|
/core/application_entitlements/{pbm_uuid}/used_by/:
|
||||||
|
get:
|
||||||
|
operationId: core_application_entitlements_used_by_list
|
||||||
|
description: Get a list of all objects that use this object
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pbm_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this Application Entitlement.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
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: ''
|
||||||
/core/applications/:
|
/core/applications/:
|
||||||
get:
|
get:
|
||||||
operationId: core_applications_list
|
operationId: core_applications_list
|
||||||
@ -23305,6 +23584,7 @@ paths:
|
|||||||
- authentik_blueprints.blueprintinstance
|
- authentik_blueprints.blueprintinstance
|
||||||
- authentik_brands.brand
|
- authentik_brands.brand
|
||||||
- authentik_core.application
|
- authentik_core.application
|
||||||
|
- authentik_core.applicationentitlement
|
||||||
- authentik_core.group
|
- authentik_core.group
|
||||||
- authentik_core.token
|
- authentik_core.token
|
||||||
- authentik_core.user
|
- authentik_core.user
|
||||||
@ -23545,6 +23825,7 @@ paths:
|
|||||||
- authentik_blueprints.blueprintinstance
|
- authentik_blueprints.blueprintinstance
|
||||||
- authentik_brands.brand
|
- authentik_brands.brand
|
||||||
- authentik_core.application
|
- authentik_core.application
|
||||||
|
- authentik_core.applicationentitlement
|
||||||
- authentik_core.group
|
- authentik_core.group
|
||||||
- authentik_core.token
|
- authentik_core.token
|
||||||
- authentik_core.user
|
- authentik_core.user
|
||||||
@ -38137,6 +38418,38 @@ components:
|
|||||||
- pk
|
- pk
|
||||||
- provider_obj
|
- provider_obj
|
||||||
- slug
|
- slug
|
||||||
|
ApplicationEntitlement:
|
||||||
|
type: object
|
||||||
|
description: ApplicationEntitlement Serializer
|
||||||
|
properties:
|
||||||
|
pbm_uuid:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
app:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
attributes: {}
|
||||||
|
required:
|
||||||
|
- app
|
||||||
|
- name
|
||||||
|
- pbm_uuid
|
||||||
|
ApplicationEntitlementRequest:
|
||||||
|
type: object
|
||||||
|
description: ApplicationEntitlement Serializer
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
app:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
attributes: {}
|
||||||
|
required:
|
||||||
|
- app
|
||||||
|
- name
|
||||||
ApplicationRequest:
|
ApplicationRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Application Serializer
|
description: Application Serializer
|
||||||
@ -42944,7 +43257,9 @@ components:
|
|||||||
flow_designation:
|
flow_designation:
|
||||||
$ref: '#/components/schemas/FlowDesignationEnum'
|
$ref: '#/components/schemas/FlowDesignationEnum'
|
||||||
captcha_stage:
|
captcha_stage:
|
||||||
$ref: '#/components/schemas/CaptchaChallenge'
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/CaptchaChallenge'
|
||||||
|
nullable: true
|
||||||
enroll_url:
|
enroll_url:
|
||||||
type: string
|
type: string
|
||||||
recovery_url:
|
recovery_url:
|
||||||
@ -44875,6 +45190,7 @@ components:
|
|||||||
- authentik_core.group
|
- authentik_core.group
|
||||||
- authentik_core.user
|
- authentik_core.user
|
||||||
- authentik_core.application
|
- authentik_core.application
|
||||||
|
- authentik_core.applicationentitlement
|
||||||
- authentik_core.token
|
- authentik_core.token
|
||||||
- authentik_enterprise.license
|
- authentik_enterprise.license
|
||||||
- authentik_providers_google_workspace.googleworkspaceprovider
|
- authentik_providers_google_workspace.googleworkspaceprovider
|
||||||
@ -45955,6 +46271,18 @@ components:
|
|||||||
- radius
|
- radius
|
||||||
- rac
|
- rac
|
||||||
type: string
|
type: string
|
||||||
|
PaginatedApplicationEntitlementList:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pagination:
|
||||||
|
$ref: '#/components/schemas/Pagination'
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ApplicationEntitlement'
|
||||||
|
required:
|
||||||
|
- pagination
|
||||||
|
- results
|
||||||
PaginatedApplicationList:
|
PaginatedApplicationList:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -47857,6 +48185,17 @@ components:
|
|||||||
required:
|
required:
|
||||||
- backends
|
- backends
|
||||||
- name
|
- name
|
||||||
|
PatchedApplicationEntitlementRequest:
|
||||||
|
type: object
|
||||||
|
description: ApplicationEntitlement Serializer
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
app:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
attributes: {}
|
||||||
PatchedApplicationRequest:
|
PatchedApplicationRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Application Serializer
|
description: Application Serializer
|
||||||
|
@ -9,7 +9,7 @@ http://localhost {
|
|||||||
uri /outpost.goauthentik.io/auth/caddy
|
uri /outpost.goauthentik.io/auth/caddy
|
||||||
|
|
||||||
# capitalization of the headers is important, otherwise they will be empty
|
# capitalization of the headers is important, otherwise they will be empty
|
||||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
|
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
|
||||||
|
|
||||||
# optional, in this config trust all private ranges, should probably be set to the outposts IP
|
# optional, in this config trust all private ranges, should probably be set to the outposts IP
|
||||||
trusted_proxies private_ranges
|
trusted_proxies private_ranges
|
||||||
|
@ -23,12 +23,14 @@ server {
|
|||||||
# translate headers from the outposts back to the actual upstream
|
# translate headers from the outposts back to the actual upstream
|
||||||
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
||||||
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
|
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
|
||||||
|
auth_request_set $authentik_entitlements $upstream_http_x_authentik_entitlements;
|
||||||
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
||||||
auth_request_set $authentik_name $upstream_http_x_authentik_name;
|
auth_request_set $authentik_name $upstream_http_x_authentik_name;
|
||||||
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
|
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
|
||||||
|
|
||||||
proxy_set_header X-authentik-username $authentik_username;
|
proxy_set_header X-authentik-username $authentik_username;
|
||||||
proxy_set_header X-authentik-groups $authentik_groups;
|
proxy_set_header X-authentik-groups $authentik_groups;
|
||||||
|
proxy_set_header X-authentik-entitlements $authentik_entitlements;
|
||||||
proxy_set_header X-authentik-email $authentik_email;
|
proxy_set_header X-authentik-email $authentik_email;
|
||||||
proxy_set_header X-authentik-name $authentik_name;
|
proxy_set_header X-authentik-name $authentik_name;
|
||||||
proxy_set_header X-authentik-uid $authentik_uid;
|
proxy_set_header X-authentik-uid $authentik_uid;
|
||||||
|
@ -26,6 +26,7 @@ http:
|
|||||||
authResponseHeaders:
|
authResponseHeaders:
|
||||||
- X-authentik-username
|
- X-authentik-username
|
||||||
- X-authentik-groups
|
- X-authentik-groups
|
||||||
|
- X-authentik-entitlements
|
||||||
- X-authentik-email
|
- X-authentik-email
|
||||||
- X-authentik-name
|
- X-authentik-name
|
||||||
- X-authentik-uid
|
- X-authentik-uid
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@goauthentik/admin/applications/ApplicationAuthorizeChart";
|
import "@goauthentik/admin/applications/ApplicationAuthorizeChart";
|
||||||
import "@goauthentik/admin/applications/ApplicationCheckAccessForm";
|
import "@goauthentik/admin/applications/ApplicationCheckAccessForm";
|
||||||
import "@goauthentik/admin/applications/ApplicationForm";
|
import "@goauthentik/admin/applications/ApplicationForm";
|
||||||
|
import "@goauthentik/admin/applications/entitlements/ApplicationEntitlementPage";
|
||||||
import "@goauthentik/admin/policies/BoundPoliciesList";
|
import "@goauthentik/admin/policies/BoundPoliciesList";
|
||||||
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
@ -301,6 +302,28 @@ export class ApplicationViewPage extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section
|
||||||
|
slot="page-app-entitlements"
|
||||||
|
data-tab-title="${msg("Application entitlements")}"
|
||||||
|
>
|
||||||
|
<div slot="header" class="pf-c-banner pf-m-info">
|
||||||
|
${msg("Application entitlements are in preview.")}
|
||||||
|
<a href="mailto:hello+feature/app-ent@goauthentik.io"
|
||||||
|
>${msg("Send us feedback!")}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
|
<div class="pf-c-card">
|
||||||
|
<div class="pf-c-card__title">
|
||||||
|
${msg(
|
||||||
|
"These entitlements can be used to configure user access in this application.",
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ak-application-entitlements-list .app=${this.application.pk}>
|
||||||
|
</ak-application-entitlements-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section
|
<section
|
||||||
slot="page-policy-bindings"
|
slot="page-policy-bindings"
|
||||||
data-tab-title="${msg("Policy / Group / User Bindings")}"
|
data-tab-title="${msg("Policy / Group / User Bindings")}"
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
import "@goauthentik/elements/CodeMirror";
|
||||||
|
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||||
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
import "@goauthentik/elements/forms/Radio";
|
||||||
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
import YAML from "yaml";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { CSSResult } from "lit";
|
||||||
|
import { TemplateResult, html } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||||
|
|
||||||
|
import { ApplicationEntitlement, CoreApi } from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-application-entitlement-form")
|
||||||
|
export class ApplicationEntitlementForm extends ModelForm<ApplicationEntitlement, string> {
|
||||||
|
async loadInstance(pk: string): Promise<ApplicationEntitlement> {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsRetrieve({
|
||||||
|
pbmUuid: pk,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@property()
|
||||||
|
targetPk?: string;
|
||||||
|
|
||||||
|
getSuccessMessage(): string {
|
||||||
|
if (this.instance?.pbmUuid) {
|
||||||
|
return msg("Successfully updated entitlement.");
|
||||||
|
} else {
|
||||||
|
return msg("Successfully created entitlement.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [...super.styles, PFContent];
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: ApplicationEntitlement): Promise<unknown> {
|
||||||
|
if (this.targetPk) {
|
||||||
|
data.app = this.targetPk;
|
||||||
|
}
|
||||||
|
if (this.instance?.pbmUuid) {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsUpdate({
|
||||||
|
pbmUuid: this.instance.pbmUuid || "",
|
||||||
|
applicationEntitlementRequest: data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsCreate({
|
||||||
|
applicationEntitlementRequest: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderForm(): TemplateResult {
|
||||||
|
return html` <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${first(this.instance?.name, "")}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${msg("Attributes")}
|
||||||
|
?required=${false}
|
||||||
|
name="attributes"
|
||||||
|
>
|
||||||
|
<ak-codemirror
|
||||||
|
mode=${CodeMirrorMode.YAML}
|
||||||
|
value="${YAML.stringify(first(this.instance?.attributes, {}))}"
|
||||||
|
>
|
||||||
|
</ak-codemirror>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Set custom attributes using YAML or JSON.")}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-application-entitlement-form": ApplicationEntitlementForm;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
import "@goauthentik/admin/applications/entitlements/ApplicationEntitlementForm";
|
||||||
|
import "@goauthentik/admin/policies/BoundPoliciesList";
|
||||||
|
import { PolicyBindingCheckTarget } from "@goauthentik/admin/policies/utils";
|
||||||
|
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { PFSize } from "@goauthentik/common/enums";
|
||||||
|
import "@goauthentik/components/ak-status-label";
|
||||||
|
import "@goauthentik/elements/Tabs";
|
||||||
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
|
import "@goauthentik/elements/forms/ModalForm";
|
||||||
|
import "@goauthentik/elements/forms/ProxyForm";
|
||||||
|
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||||
|
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
|
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 {
|
||||||
|
ApplicationEntitlement,
|
||||||
|
CoreApi,
|
||||||
|
RbacPermissionsAssignedByUsersListModelEnum,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-application-entitlements-list")
|
||||||
|
export class ApplicationEntitlementsPage extends Table<ApplicationEntitlement> {
|
||||||
|
@property()
|
||||||
|
app?: string;
|
||||||
|
|
||||||
|
checkbox = true;
|
||||||
|
clearOnRefresh = true;
|
||||||
|
expandable = true;
|
||||||
|
|
||||||
|
order = "order";
|
||||||
|
|
||||||
|
async apiEndpoint(): Promise<PaginatedResponse<ApplicationEntitlement>> {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsList({
|
||||||
|
...(await this.defaultEndpointConfig()),
|
||||||
|
app: this.app || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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("Application entitlement(s)")}
|
||||||
|
.objects=${this.selectedElements}
|
||||||
|
.usedBy=${(item: ApplicationEntitlement) => {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsUsedByList({
|
||||||
|
pbmUuid: item.pbmUuid || "",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
.delete=${(item: ApplicationEntitlement) => {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreApplicationEntitlementsDestroy({
|
||||||
|
pbmUuid: item.pbmUuid || "",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||||
|
${msg("Delete")}
|
||||||
|
</button>
|
||||||
|
</ak-forms-delete-bulk>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
row(item: ApplicationEntitlement): TemplateResult[] {
|
||||||
|
return [
|
||||||
|
html`${item.name}`,
|
||||||
|
html`<ak-forms-modal size=${PFSize.Medium}>
|
||||||
|
<span slot="submit"> ${msg("Update")} </span>
|
||||||
|
<span slot="header"> ${msg("Update Entitlement")} </span>
|
||||||
|
<ak-application-entitlement-form
|
||||||
|
slot="form"
|
||||||
|
.instancePk=${item.pbmUuid}
|
||||||
|
targetPk=${ifDefined(this.app)}
|
||||||
|
>
|
||||||
|
</ak-application-entitlement-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>
|
||||||
|
<ak-rbac-object-permission-modal
|
||||||
|
model=${RbacPermissionsAssignedByUsersListModelEnum.CoreApplicationentitlement}
|
||||||
|
objectPk=${item.pbmUuid}
|
||||||
|
>
|
||||||
|
</ak-rbac-object-permission-modal>`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExpanded(item: ApplicationEntitlement): TemplateResult {
|
||||||
|
return html` <td></td>
|
||||||
|
<td role="cell" colspan="4">
|
||||||
|
<div class="pf-c-table__expandable-row-content">
|
||||||
|
<div class="pf-c-content">
|
||||||
|
<p>
|
||||||
|
${msg(
|
||||||
|
"These bindings control which users have access to this entitlement.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ak-bound-policies-list
|
||||||
|
.target=${item.pbmUuid}
|
||||||
|
.allowedTypes=${[
|
||||||
|
PolicyBindingCheckTarget.group,
|
||||||
|
PolicyBindingCheckTarget.user,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
</ak-bound-policies-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEmpty(): TemplateResult {
|
||||||
|
return super.renderEmpty(
|
||||||
|
html`<ak-empty-state
|
||||||
|
header=${msg("No app entitlements created.")}
|
||||||
|
icon="pf-icon-module"
|
||||||
|
>
|
||||||
|
<div slot="body">
|
||||||
|
${msg(
|
||||||
|
"This application does currently not have any application entitlement defined.",
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div slot="primary"></div>
|
||||||
|
</ak-empty-state>`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolbar(): TemplateResult {
|
||||||
|
return html`<ak-forms-modal size=${PFSize.Medium}>
|
||||||
|
<span slot="submit"> ${msg("Create")} </span>
|
||||||
|
<span slot="header"> ${msg("Create Entitlement")} </span>
|
||||||
|
<ak-application-entitlement-form slot="form" targetPk=${ifDefined(this.app)}>
|
||||||
|
</ak-application-entitlement-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||||
|
${msg("Create entitlement")}
|
||||||
|
</button>
|
||||||
|
</ak-forms-modal> `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-application-roles-list": ApplicationEntitlementsPage;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,11 @@
|
|||||||
import "@goauthentik/admin/groups/GroupForm";
|
import "@goauthentik/admin/groups/GroupForm";
|
||||||
import "@goauthentik/admin/policies/PolicyBindingForm";
|
import "@goauthentik/admin/policies/PolicyBindingForm";
|
||||||
|
import { PolicyBindingNotice } from "@goauthentik/admin/policies/PolicyBindingForm";
|
||||||
import "@goauthentik/admin/policies/PolicyWizard";
|
import "@goauthentik/admin/policies/PolicyWizard";
|
||||||
|
import {
|
||||||
|
PolicyBindingCheckTarget,
|
||||||
|
PolicyBindingCheckTargetToLabel,
|
||||||
|
} from "@goauthentik/admin/policies/utils";
|
||||||
import "@goauthentik/admin/users/UserForm";
|
import "@goauthentik/admin/users/UserForm";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { PFSize } from "@goauthentik/common/enums.js";
|
import { PFSize } from "@goauthentik/common/enums.js";
|
||||||
@ -13,7 +18,7 @@ import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
|||||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
@ -24,14 +29,25 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
@property()
|
@property()
|
||||||
target?: string;
|
target?: string;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Array })
|
||||||
policyOnly = false;
|
allowedTypes: PolicyBindingCheckTarget[] = [
|
||||||
|
PolicyBindingCheckTarget.group,
|
||||||
|
PolicyBindingCheckTarget.user,
|
||||||
|
PolicyBindingCheckTarget.policy,
|
||||||
|
];
|
||||||
|
|
||||||
|
@property({ type: Array })
|
||||||
|
typeNotices: PolicyBindingNotice[] = [];
|
||||||
|
|
||||||
checkbox = true;
|
checkbox = true;
|
||||||
clearOnRefresh = true;
|
clearOnRefresh = true;
|
||||||
|
|
||||||
order = "order";
|
order = "order";
|
||||||
|
|
||||||
|
get allowedTypesLabel(): string {
|
||||||
|
return this.allowedTypes.map((ct) => PolicyBindingCheckTargetToLabel(ct)).join(" / ");
|
||||||
|
}
|
||||||
|
|
||||||
async apiEndpoint(): Promise<PaginatedResponse<PolicyBinding>> {
|
async apiEndpoint(): Promise<PaginatedResponse<PolicyBinding>> {
|
||||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||||
...(await this.defaultEndpointConfig()),
|
...(await this.defaultEndpointConfig()),
|
||||||
@ -42,7 +58,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
columns(): TableColumn[] {
|
columns(): TableColumn[] {
|
||||||
return [
|
return [
|
||||||
new TableColumn(msg("Order"), "order"),
|
new TableColumn(msg("Order"), "order"),
|
||||||
new TableColumn(msg("Policy / User / Group")),
|
new TableColumn(this.allowedTypesLabel),
|
||||||
new TableColumn(msg("Enabled"), "enabled"),
|
new TableColumn(msg("Enabled"), "enabled"),
|
||||||
new TableColumn(msg("Timeout"), "timeout"),
|
new TableColumn(msg("Timeout"), "timeout"),
|
||||||
new TableColumn(msg("Actions")),
|
new TableColumn(msg("Actions")),
|
||||||
@ -121,7 +137,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
return [
|
return [
|
||||||
{ key: msg("Order"), value: item.order.toString() },
|
{ key: msg("Order"), value: item.order.toString() },
|
||||||
{
|
{
|
||||||
key: msg("Policy / User / Group"),
|
key: this.allowedTypesLabel,
|
||||||
value: this.getPolicyUserGroupRowLabel(item),
|
value: this.getPolicyUserGroupRowLabel(item),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -156,8 +172,9 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
<ak-policy-binding-form
|
<ak-policy-binding-form
|
||||||
slot="form"
|
slot="form"
|
||||||
.instancePk=${item.pk}
|
.instancePk=${item.pk}
|
||||||
|
.allowedTypes=${this.allowedTypes}
|
||||||
|
.typeNotices=${this.typeNotices}
|
||||||
targetPk=${ifDefined(this.target)}
|
targetPk=${ifDefined(this.target)}
|
||||||
?policyOnly=${this.policyOnly}
|
|
||||||
>
|
>
|
||||||
</ak-policy-binding-form>
|
</ak-policy-binding-form>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||||
@ -183,7 +200,8 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
<ak-policy-binding-form
|
<ak-policy-binding-form
|
||||||
slot="form"
|
slot="form"
|
||||||
targetPk=${ifDefined(this.target)}
|
targetPk=${ifDefined(this.target)}
|
||||||
?policyOnly=${this.policyOnly}
|
.allowedTypes=${this.allowedTypes}
|
||||||
|
.typeNotices=${this.typeNotices}
|
||||||
>
|
>
|
||||||
</ak-policy-binding-form>
|
</ak-policy-binding-form>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||||
@ -196,22 +214,25 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderToolbar(): TemplateResult {
|
renderToolbar(): TemplateResult {
|
||||||
return html`<ak-policy-wizard
|
return html`${this.allowedTypes.includes(PolicyBindingCheckTarget.policy)
|
||||||
createText=${msg("Create and bind Policy")}
|
? html`<ak-policy-wizard
|
||||||
?showBindingPage=${true}
|
createText=${msg("Create and bind Policy")}
|
||||||
bindingTarget=${ifDefined(this.target)}
|
?showBindingPage=${true}
|
||||||
></ak-policy-wizard>
|
bindingTarget=${ifDefined(this.target)}
|
||||||
|
></ak-policy-wizard>`
|
||||||
|
: nothing}
|
||||||
<ak-forms-modal size=${PFSize.Medium}>
|
<ak-forms-modal size=${PFSize.Medium}>
|
||||||
<span slot="submit"> ${msg("Create")} </span>
|
<span slot="submit"> ${msg("Create")} </span>
|
||||||
<span slot="header"> ${msg("Create Binding")} </span>
|
<span slot="header"> ${msg("Create Binding")} </span>
|
||||||
<ak-policy-binding-form
|
<ak-policy-binding-form
|
||||||
slot="form"
|
slot="form"
|
||||||
targetPk=${ifDefined(this.target)}
|
targetPk=${ifDefined(this.target)}
|
||||||
?policyOnly=${this.policyOnly}
|
.allowedTypes=${this.allowedTypes}
|
||||||
|
.typeNotices=${this.typeNotices}
|
||||||
>
|
>
|
||||||
</ak-policy-binding-form>
|
</ak-policy-binding-form>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||||
${msg("Bind existing policy/group/user")}
|
${msg(str`Bind existing ${this.allowedTypesLabel}`)}
|
||||||
</button>
|
</button>
|
||||||
</ak-forms-modal> `;
|
</ak-forms-modal> `;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import {
|
||||||
|
PolicyBindingCheckTarget,
|
||||||
|
PolicyBindingCheckTargetToLabel,
|
||||||
|
} from "@goauthentik/admin/policies/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first, groupBy } from "@goauthentik/common/utils";
|
import { first, groupBy } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/components/ak-toggle-group";
|
import "@goauthentik/components/ak-toggle-group";
|
||||||
@ -7,7 +11,7 @@ import "@goauthentik/elements/forms/Radio";
|
|||||||
import "@goauthentik/elements/forms/SearchSelect";
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult } from "lit";
|
import { CSSResult, nothing } from "lit";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
@ -25,11 +29,7 @@ import {
|
|||||||
User,
|
User,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
enum target {
|
export type PolicyBindingNotice = { type: PolicyBindingCheckTarget; notice: string };
|
||||||
policy = "policy",
|
|
||||||
group = "group",
|
|
||||||
user = "user",
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ak-policy-binding-form")
|
@customElement("ak-policy-binding-form")
|
||||||
export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
||||||
@ -38,13 +38,13 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
policyBindingUuid: pk,
|
policyBindingUuid: pk,
|
||||||
});
|
});
|
||||||
if (binding?.policyObj) {
|
if (binding?.policyObj) {
|
||||||
this.policyGroupUser = target.policy;
|
this.policyGroupUser = PolicyBindingCheckTarget.policy;
|
||||||
}
|
}
|
||||||
if (binding?.groupObj) {
|
if (binding?.groupObj) {
|
||||||
this.policyGroupUser = target.group;
|
this.policyGroupUser = PolicyBindingCheckTarget.group;
|
||||||
}
|
}
|
||||||
if (binding?.userObj) {
|
if (binding?.userObj) {
|
||||||
this.policyGroupUser = target.user;
|
this.policyGroupUser = PolicyBindingCheckTarget.user;
|
||||||
}
|
}
|
||||||
this.defaultOrder = await this.getOrder();
|
this.defaultOrder = await this.getOrder();
|
||||||
return binding;
|
return binding;
|
||||||
@ -54,10 +54,17 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
targetPk?: string;
|
targetPk?: string;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
policyGroupUser: target = target.policy;
|
policyGroupUser: PolicyBindingCheckTarget = PolicyBindingCheckTarget.policy;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Array })
|
||||||
policyOnly = false;
|
allowedTypes: PolicyBindingCheckTarget[] = [
|
||||||
|
PolicyBindingCheckTarget.group,
|
||||||
|
PolicyBindingCheckTarget.user,
|
||||||
|
PolicyBindingCheckTarget.policy,
|
||||||
|
];
|
||||||
|
|
||||||
|
@property({ type: Array })
|
||||||
|
typeNotices: PolicyBindingNotice[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
defaultOrder = 0;
|
defaultOrder = 0;
|
||||||
@ -74,20 +81,26 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
return [...super.styles, PFContent];
|
return [...super.styles, PFContent];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
// Overwrite the default for policyGroupUser with the first allowed type,
|
||||||
|
// as this function is called when the correct parameters are set
|
||||||
|
this.policyGroupUser = this.allowedTypes[0];
|
||||||
|
}
|
||||||
|
|
||||||
send(data: PolicyBinding): Promise<unknown> {
|
send(data: PolicyBinding): Promise<unknown> {
|
||||||
if (this.targetPk) {
|
if (this.targetPk) {
|
||||||
data.target = this.targetPk;
|
data.target = this.targetPk;
|
||||||
}
|
}
|
||||||
switch (this.policyGroupUser) {
|
switch (this.policyGroupUser) {
|
||||||
case target.policy:
|
case PolicyBindingCheckTarget.policy:
|
||||||
data.user = null;
|
data.user = null;
|
||||||
data.group = null;
|
data.group = null;
|
||||||
break;
|
break;
|
||||||
case target.group:
|
case PolicyBindingCheckTarget.group:
|
||||||
data.policy = null;
|
data.policy = null;
|
||||||
data.user = null;
|
data.user = null;
|
||||||
break;
|
break;
|
||||||
case target.user:
|
case PolicyBindingCheckTarget.user:
|
||||||
data.policy = null;
|
data.policy = null;
|
||||||
data.group = null;
|
data.group = null;
|
||||||
break;
|
break;
|
||||||
@ -122,13 +135,18 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
renderModeSelector(): TemplateResult {
|
renderModeSelector(): TemplateResult {
|
||||||
return html` <ak-toggle-group
|
return html` <ak-toggle-group
|
||||||
value=${this.policyGroupUser}
|
value=${this.policyGroupUser}
|
||||||
@ak-toggle=${(ev: CustomEvent<{ value: target }>) => {
|
@ak-toggle=${(ev: CustomEvent<{ value: PolicyBindingCheckTarget }>) => {
|
||||||
this.policyGroupUser = ev.detail.value;
|
this.policyGroupUser = ev.detail.value;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value=${target.policy}>${msg("Policy")}</option>
|
${Object.keys(PolicyBindingCheckTarget).map((ct) => {
|
||||||
<option value=${target.group}>${msg("Group")}</option>
|
if (this.allowedTypes.includes(ct as PolicyBindingCheckTarget)) {
|
||||||
<option value=${target.user}>${msg("User")}</option>
|
return html`<option value=${ct}>
|
||||||
|
${PolicyBindingCheckTargetToLabel(ct as PolicyBindingCheckTarget)}
|
||||||
|
</option>`;
|
||||||
|
}
|
||||||
|
return nothing;
|
||||||
|
})}
|
||||||
</ak-toggle-group>`;
|
</ak-toggle-group>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +157,7 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Policy")}
|
label=${msg("Policy")}
|
||||||
name="policy"
|
name="policy"
|
||||||
?hidden=${this.policyGroupUser !== target.policy}
|
?hidden=${this.policyGroupUser !== PolicyBindingCheckTarget.policy}
|
||||||
>
|
>
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
.groupBy=${(items: Policy[]) => {
|
.groupBy=${(items: Policy[]) => {
|
||||||
@ -169,11 +187,16 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
?blankable=${true}
|
?blankable=${true}
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
|
${this.typeNotices
|
||||||
|
.filter(({ type }) => type === PolicyBindingCheckTarget.policy)
|
||||||
|
.map((msg) => {
|
||||||
|
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`;
|
||||||
|
})}
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Group")}
|
label=${msg("Group")}
|
||||||
name="group"
|
name="group"
|
||||||
?hidden=${this.policyGroupUser !== target.group}
|
?hidden=${this.policyGroupUser !== PolicyBindingCheckTarget.group}
|
||||||
>
|
>
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||||
@ -201,18 +224,16 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
?blankable=${true}
|
?blankable=${true}
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
${this.policyOnly
|
${this.typeNotices
|
||||||
? html`<p class="pf-c-form__helper-text">
|
.filter(({ type }) => type === PolicyBindingCheckTarget.group)
|
||||||
${msg(
|
.map((msg) => {
|
||||||
"Group mappings can only be checked if a user is already logged in when trying to access this source.",
|
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`;
|
||||||
)}
|
})}
|
||||||
</p>`
|
|
||||||
: html``}
|
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User")}
|
label=${msg("User")}
|
||||||
name="user"
|
name="user"
|
||||||
?hidden=${this.policyGroupUser !== target.user}
|
?hidden=${this.policyGroupUser !== PolicyBindingCheckTarget.user}
|
||||||
>
|
>
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||||
@ -240,13 +261,11 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
|||||||
?blankable=${true}
|
?blankable=${true}
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
${this.policyOnly
|
${this.typeNotices
|
||||||
? html`<p class="pf-c-form__helper-text">
|
.filter(({ type }) => type === PolicyBindingCheckTarget.user)
|
||||||
${msg(
|
.map((msg) => {
|
||||||
"User mappings can only be checked if a user is already logged in when trying to access this source.",
|
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`;
|
||||||
)}
|
})}
|
||||||
</p>`
|
|
||||||
: html``}
|
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
18
web/src/admin/policies/utils.ts
Normal file
18
web/src/admin/policies/utils.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
|
export enum PolicyBindingCheckTarget {
|
||||||
|
policy = "policy",
|
||||||
|
group = "group",
|
||||||
|
user = "user",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PolicyBindingCheckTargetToLabel(ct: PolicyBindingCheckTarget): string {
|
||||||
|
switch (ct) {
|
||||||
|
case PolicyBindingCheckTarget.group:
|
||||||
|
return msg("Group");
|
||||||
|
case PolicyBindingCheckTarget.user:
|
||||||
|
return msg("User");
|
||||||
|
case PolicyBindingCheckTarget.policy:
|
||||||
|
return msg("Policy");
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import "@goauthentik/admin/policies/BoundPoliciesList";
|
|||||||
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
||||||
import "@goauthentik/admin/sources/oauth/OAuthSourceDiagram";
|
import "@goauthentik/admin/sources/oauth/OAuthSourceDiagram";
|
||||||
import "@goauthentik/admin/sources/oauth/OAuthSourceForm";
|
import "@goauthentik/admin/sources/oauth/OAuthSourceForm";
|
||||||
|
import { sourceBindingTypeNotices } from "@goauthentik/admin/sources/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
@ -240,7 +241,10 @@ export class OAuthSourceViewPage extends AKElement {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
|
<ak-bound-policies-list
|
||||||
|
.target=${this.source.pk}
|
||||||
|
.typeNotices=${sourceBindingTypeNotices()}
|
||||||
|
>
|
||||||
</ak-bound-policies-list>
|
</ak-bound-policies-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@goauthentik/admin/policies/BoundPoliciesList";
|
import "@goauthentik/admin/policies/BoundPoliciesList";
|
||||||
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
||||||
import "@goauthentik/admin/sources/plex/PlexSourceForm";
|
import "@goauthentik/admin/sources/plex/PlexSourceForm";
|
||||||
|
import { sourceBindingTypeNotices } from "@goauthentik/admin/sources/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
@ -130,7 +131,10 @@ export class PlexSourceViewPage extends AKElement {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
|
<ak-bound-policies-list
|
||||||
|
.target=${this.source.pk}
|
||||||
|
.typeNotices=${sourceBindingTypeNotices()}
|
||||||
|
>
|
||||||
</ak-bound-policies-list>
|
</ak-bound-policies-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@goauthentik/admin/policies/BoundPoliciesList";
|
import "@goauthentik/admin/policies/BoundPoliciesList";
|
||||||
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
import "@goauthentik/admin/rbac/ObjectPermissionsPage";
|
||||||
import "@goauthentik/admin/sources/saml/SAMLSourceForm";
|
import "@goauthentik/admin/sources/saml/SAMLSourceForm";
|
||||||
|
import { sourceBindingTypeNotices } from "@goauthentik/admin/sources/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
@ -207,7 +208,10 @@ export class SAMLSourceViewPage extends AKElement {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
|
<ak-bound-policies-list
|
||||||
|
.target=${this.source.pk}
|
||||||
|
.typeNotices=${sourceBindingTypeNotices()}
|
||||||
|
>
|
||||||
</ak-bound-policies-list>
|
</ak-bound-policies-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { PolicyBindingCheckTarget } from "@goauthentik/admin/policies/utils";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
|
|
||||||
export function renderSourceIcon(name: string, iconUrl: string | undefined | null): TemplateResult {
|
export function renderSourceIcon(name: string, iconUrl: string | undefined | null): TemplateResult {
|
||||||
@ -11,3 +14,20 @@ export function renderSourceIcon(name: string, iconUrl: string | undefined | nul
|
|||||||
}
|
}
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sourceBindingTypeNotices() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: PolicyBindingCheckTarget.group,
|
||||||
|
notice: msg(
|
||||||
|
"Group mappings can only be checked if a user is already logged in when trying to access this source.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: PolicyBindingCheckTarget.user,
|
||||||
|
notice: msg(
|
||||||
|
"User mappings can only be checked if a user is already logged in when trying to access this source.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
@ -48,6 +48,16 @@ sequenceDiagram
|
|||||||
rp->>user: User is logged in
|
rp->>user: User is logged in
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| Endpoint | URL |
|
||||||
|
| -------------------- | -------------------------------------------------------------------- |
|
||||||
|
| Authorization | `/application/o/authorize/` |
|
||||||
|
| Token | `/application/o/token/` |
|
||||||
|
| User Info | `/application/o/userinfo/` |
|
||||||
|
| Token Revoke | `/application/o/revoke/` |
|
||||||
|
| End Session | `/application/o/<application slug>/end-session/` |
|
||||||
|
| JWKS | `/application/o/<application slug>/jwks/` |
|
||||||
|
| OpenID Configuration | `/application/o/<application slug>/.well-known/openid-configuration` |
|
||||||
|
|
||||||
### Additional configuration options with Redirect URIs
|
### Additional configuration options with Redirect URIs
|
||||||
|
|
||||||
When using an OAuth 2.0 provider in authentik, the OP must validate the provided redirect URI by the RP. An authentik admin can configure a list in the **Redirect URI** field on the Provider.
|
When using an OAuth 2.0 provider in authentik, the OP must validate the provided redirect URI by the RP. An authentik admin can configure a list in the **Redirect URI** field on the Provider.
|
||||||
@ -122,16 +132,6 @@ Starting with authentik 2024.2, the refresh token grant type requires the `offli
|
|||||||
|
|
||||||
Scopes can be configured using scope mappings, a type of [property mapping](../property-mappings/index.md#scope-mappings).
|
Scopes can be configured using scope mappings, a type of [property mapping](../property-mappings/index.md#scope-mappings).
|
||||||
|
|
||||||
| Endpoint | URL |
|
|
||||||
| -------------------- | -------------------------------------------------------------------- |
|
|
||||||
| Authorization | `/application/o/authorize/` |
|
|
||||||
| Token | `/application/o/token/` |
|
|
||||||
| User Info | `/application/o/userinfo/` |
|
|
||||||
| Token Revoke | `/application/o/revoke/` |
|
|
||||||
| End Session | `/application/o/<application slug>/end-session/` |
|
|
||||||
| JWKS | `/application/o/<application slug>/jwks/` |
|
|
||||||
| OpenID Configuration | `/application/o/<application slug>/.well-known/openid-configuration` |
|
|
||||||
|
|
||||||
## Scope authorization
|
## Scope authorization
|
||||||
|
|
||||||
By default, every user that has access to an application can request any of the configured scopes. Starting with authentik 2022.4, you can do additional checks for the scope in an expression policy (bound to the application):
|
By default, every user that has access to an application can request any of the configured scopes. Starting with authentik 2022.4, you can do additional checks for the scope in an expression policy (bound to the application):
|
||||||
@ -143,7 +143,23 @@ if "my-admin-scope" in request.context["oauth_scopes"]:
|
|||||||
return True
|
return True
|
||||||
```
|
```
|
||||||
|
|
||||||
## Special scopes
|
## Default & special scopes
|
||||||
|
|
||||||
|
When a client does not request any scopes, authentik will treat the request as if all configured scopes were requested. Depending on the configured authorization flow, consent still needs to be given, and all scopes are listed there.
|
||||||
|
|
||||||
|
This does _not_ apply to special scopes, as those are not configurable in the provider.
|
||||||
|
|
||||||
|
### Default
|
||||||
|
|
||||||
|
- `openid`: A scope required by the OpenID Connect spec to specify that an OAuth interaction is OpenID Connect. Does not add any data to the token.
|
||||||
|
- `profile`: Include basic profile information, such as username, name and group membership.
|
||||||
|
- `email`: Include the users' email address.
|
||||||
|
- `entitlements`: Include application entitlement data.
|
||||||
|
- `offline_access`: An OAuth 2.0 scope which indicates that the application is requesting a refresh token.
|
||||||
|
|
||||||
|
### authentik
|
||||||
|
|
||||||
|
- `goauthentik.io/api`: This scope grants the refresh token access to the authentik API on behalf of the user
|
||||||
|
|
||||||
### GitHub compatibility
|
### GitHub compatibility
|
||||||
|
|
||||||
@ -152,19 +168,9 @@ return True
|
|||||||
- `user:email`: Allows read-only access to `/user`, including email address
|
- `user:email`: Allows read-only access to `/user`, including email address
|
||||||
- `read:org`: Allows read-only access to `/user/teams`, listing all the user's groups as teams.
|
- `read:org`: Allows read-only access to `/user/teams`, listing all the user's groups as teams.
|
||||||
|
|
||||||
### authentik
|
|
||||||
|
|
||||||
- `goauthentik.io/api`: This scope grants the refresh token access to the authentik API on behalf of the user
|
|
||||||
|
|
||||||
## Default scopes <span class="badge badge--version">authentik 2022.7+</span>
|
|
||||||
|
|
||||||
When a client does not request any scopes, authentik will treat the request as if all configured scopes were requested. Depending on the configured authorization flow, consent still needs to be given, and all scopes are listed there.
|
|
||||||
|
|
||||||
This does _not_ apply to special scopes, as those are not configurable in the provider.
|
|
||||||
|
|
||||||
## Signing & Encryption
|
## Signing & Encryption
|
||||||
|
|
||||||
[JWT](https://jwt.io/introduction)s created by authentik will always be signed.
|
[JWTs](https://jwt.io/introduction) created by authentik will always be signed.
|
||||||
|
|
||||||
When a _Signing Key_ is selected in the provider, the JWT will be signed asymmetrically with the private key of the selected certificate, and can be verified using the public key of the certificate. The public key data of the signing key can be retrieved via the JWKS endpoint listed on the provider page.
|
When a _Signing Key_ is selected in the provider, the JWT will be signed asymmetrically with the private key of the selected certificate, and can be verified using the public key of the certificate. The public key data of the signing key can be retrieved via the JWKS endpoint listed on the provider page.
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ app.company {
|
|||||||
uri /outpost.goauthentik.io/auth/caddy
|
uri /outpost.goauthentik.io/auth/caddy
|
||||||
|
|
||||||
# capitalization of the headers is important, otherwise they will be empty
|
# capitalization of the headers is important, otherwise they will be empty
|
||||||
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
|
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
|
||||||
|
|
||||||
# optional, in this config trust all private ranges, should probably be set to the outposts IP
|
# optional, in this config trust all private ranges, should probably be set to the outposts IP
|
||||||
trusted_proxies private_ranges
|
trusted_proxies private_ranges
|
||||||
|
@ -40,7 +40,7 @@ metadata:
|
|||||||
nginx.ingress.kubernetes.io/auth-signin: |-
|
nginx.ingress.kubernetes.io/auth-signin: |-
|
||||||
https://app.company/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri
|
https://app.company/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri
|
||||||
nginx.ingress.kubernetes.io/auth-response-headers: |-
|
nginx.ingress.kubernetes.io/auth-response-headers: |-
|
||||||
Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid
|
Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-entitlements,X-authentik-email,X-authentik-name,X-authentik-uid
|
||||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
nginx.ingress.kubernetes.io/auth-snippet: |
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
```
|
```
|
||||||
|
@ -26,12 +26,14 @@ location / {
|
|||||||
# translate headers from the outposts back to the actual upstream
|
# translate headers from the outposts back to the actual upstream
|
||||||
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
||||||
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
|
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
|
||||||
|
auth_request_set $authentik_entitlements $upstream_http_x_authentik_entitlements;
|
||||||
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
||||||
auth_request_set $authentik_name $upstream_http_x_authentik_name;
|
auth_request_set $authentik_name $upstream_http_x_authentik_name;
|
||||||
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
|
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
|
||||||
|
|
||||||
proxy_set_header X-authentik-username $authentik_username;
|
proxy_set_header X-authentik-username $authentik_username;
|
||||||
proxy_set_header X-authentik-groups $authentik_groups;
|
proxy_set_header X-authentik-groups $authentik_groups;
|
||||||
|
proxy_set_header X-authentik-entitlements $authentik_entitlements;
|
||||||
proxy_set_header X-authentik-email $authentik_email;
|
proxy_set_header X-authentik-email $authentik_email;
|
||||||
proxy_set_header X-authentik-name $authentik_name;
|
proxy_set_header X-authentik-name $authentik_name;
|
||||||
proxy_set_header X-authentik-uid $authentik_uid;
|
proxy_set_header X-authentik-uid $authentik_uid;
|
||||||
|
@ -39,12 +39,14 @@ server {
|
|||||||
# translate headers from the outposts back to the actual upstream
|
# translate headers from the outposts back to the actual upstream
|
||||||
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
||||||
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
|
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
|
||||||
|
auth_request_set $authentik_entitlements $upstream_http_x_authentik_entitlements;
|
||||||
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
||||||
auth_request_set $authentik_name $upstream_http_x_authentik_name;
|
auth_request_set $authentik_name $upstream_http_x_authentik_name;
|
||||||
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
|
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;
|
||||||
|
|
||||||
proxy_set_header X-authentik-username $authentik_username;
|
proxy_set_header X-authentik-username $authentik_username;
|
||||||
proxy_set_header X-authentik-groups $authentik_groups;
|
proxy_set_header X-authentik-groups $authentik_groups;
|
||||||
|
proxy_set_header X-authentik-entitlements $authentik_entitlements;
|
||||||
proxy_set_header X-authentik-email $authentik_email;
|
proxy_set_header X-authentik-email $authentik_email;
|
||||||
proxy_set_header X-authentik-name $authentik_name;
|
proxy_set_header X-authentik-name $authentik_name;
|
||||||
proxy_set_header X-authentik-uid $authentik_uid;
|
proxy_set_header X-authentik-uid $authentik_uid;
|
||||||
|
@ -32,7 +32,7 @@ services:
|
|||||||
# `authentik-proxy` refers to the service name in the compose file.
|
# `authentik-proxy` refers to the service name in the compose file.
|
||||||
traefik.http.middlewares.authentik.forwardauth.address: http://authentik-proxy:9000/outpost.goauthentik.io/auth/traefik
|
traefik.http.middlewares.authentik.forwardauth.address: http://authentik-proxy:9000/outpost.goauthentik.io/auth/traefik
|
||||||
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
|
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
|
||||||
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version
|
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-entitlements,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
whoami:
|
whoami:
|
||||||
|
@ -13,6 +13,7 @@ spec:
|
|||||||
authResponseHeaders:
|
authResponseHeaders:
|
||||||
- X-authentik-username
|
- X-authentik-username
|
||||||
- X-authentik-groups
|
- X-authentik-groups
|
||||||
|
- X-authentik-entitlements
|
||||||
- X-authentik-email
|
- X-authentik-email
|
||||||
- X-authentik-name
|
- X-authentik-name
|
||||||
- X-authentik-uid
|
- X-authentik-uid
|
||||||
|
@ -8,6 +8,7 @@ http:
|
|||||||
authResponseHeaders:
|
authResponseHeaders:
|
||||||
- X-authentik-username
|
- X-authentik-username
|
||||||
- X-authentik-groups
|
- X-authentik-groups
|
||||||
|
- X-authentik-entitlements
|
||||||
- X-authentik-email
|
- X-authentik-email
|
||||||
- X-authentik-name
|
- X-authentik-name
|
||||||
- X-authentik-uid
|
- X-authentik-uid
|
||||||
|
@ -36,6 +36,12 @@ Example value: `foo|bar|baz`
|
|||||||
|
|
||||||
The groups the user is member of, separated by a pipe
|
The groups the user is member of, separated by a pipe
|
||||||
|
|
||||||
|
### `X-authentik-entitlements`
|
||||||
|
|
||||||
|
Example value: `foo|bar|baz`
|
||||||
|
|
||||||
|
The entitlements on the application this user has access to, separated by a pipe
|
||||||
|
|
||||||
### `X-authentik-email`
|
### `X-authentik-email`
|
||||||
|
|
||||||
Example value: `root@localhost`
|
Example value: `root@localhost`
|
||||||
|
Reference in New Issue
Block a user