Compare commits

..

1 Commits

Author SHA1 Message Date
b3b50c5914 core: enforce unique group name on database level
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-12-11 18:12:39 +01:00
148 changed files with 1611 additions and 7796 deletions

View File

@ -35,7 +35,7 @@ runs:
run: | run: |
export PSQL_TAG=${{ inputs.postgresql_version }} export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/docker-compose.yml up -d docker compose -f .github/actions/setup/docker-compose.yml up -d
poetry install --sync poetry install
cd web && npm ci cd web && npm ci
- name: Generate config - name: Generate config
shell: poetry run python {0} shell: poetry run python {0}

View File

@ -134,7 +134,7 @@ jobs:
- name: Setup authentik env - name: Setup authentik env
uses: ./.github/actions/setup uses: ./.github/actions/setup
- name: Create k8s Kind Cluster - name: Create k8s Kind Cluster
uses: helm/kind-action@v1.11.0 uses: helm/kind-action@v1.10.0
- name: run integration - name: run integration
run: | run: |
poetry run coverage run manage.py test tests/integration poetry run coverage run manage.py test tests/integration

View File

@ -26,17 +26,12 @@ jobs:
env: env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with: with:
image-name: ghcr.io/goauthentik/server,beryju/authentik,authentik/server image-name: ghcr.io/goauthentik/server,beryju/authentik
- name: Login to Docker Registry (legacy) - name: Docker Login Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to Docker Registry (org)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@ -97,21 +92,16 @@ jobs:
env: env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
with: with:
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }},authentik/${{ matrix.type }} image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
- name: make empty clients - name: make empty clients
run: | run: |
mkdir -p ./gen-ts-api mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api mkdir -p ./gen-go-api
- name: Login to Docker Registry (legacy) - name: Docker Login Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to Docker Registry (org)
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_CORP_USERNAME }}
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@ -33,8 +33,7 @@
"!If sequence", "!If sequence",
"!Index scalar", "!Index scalar",
"!KeyOf scalar", "!KeyOf scalar",
"!Value scalar", "!Value scalar"
"!AtIndex scalar"
], ],
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index", "typescript.preferences.importModuleSpecifierEnding": "index",

View File

@ -19,18 +19,10 @@ Dockerfile @goauthentik/infrastructure
*Dockerfile @goauthentik/infrastructure *Dockerfile @goauthentik/infrastructure
.dockerignore @goauthentik/infrastructure .dockerignore @goauthentik/infrastructure
docker-compose.yml @goauthentik/infrastructure docker-compose.yml @goauthentik/infrastructure
Makefile @goauthentik/infrastructure
.editorconfig @goauthentik/infrastructure
CODEOWNERS @goauthentik/infrastructure
# Web # Web
web/ @goauthentik/frontend web/ @goauthentik/frontend
tests/wdio/ @goauthentik/frontend tests/wdio/ @goauthentik/frontend
# Locale
locale/ @goauthentik/backend @goauthentik/frontend
web/xliff/ @goauthentik/backend @goauthentik/frontend
# Docs & Website # Docs & Website
website/ @goauthentik/docs website/ @goauthentik/docs
CODE_OF_CONDUCT.md @goauthentik/docs
# Security # Security
SECURITY.md @goauthentik/security @goauthentik/docs website/docs/security/ @goauthentik/security
website/docs/security/ @goauthentik/security @goauthentik/docs

View File

@ -2,7 +2,7 @@ authentik takes security very seriously. We follow the rules of [responsible di
## Independent audits and pentests ## Independent audits and pentests
We are committed to engaging in regular pentesting and security audits of authentik. Defining and adhering to a cadence of external testing ensures a stronger probability that our code base, our features, and our architecture is as secure and non-exploitable as possible. For more details about specfic audits and pentests, refer to "Audits and Certificates" in our [Security documentation](https://docs.goauthentik.io/docs/security). We are committed to engaging in regular pentesting and security audits of authentik. Defining and adhering to a cadence of external testing ensures a stronger probability that our code base, our features, and our architecture is as secure and non-exploitable as possible. For more details about specfic audits and pentests, refer to "Audits and Certificates" in our [Security documentation]](https://docs.goauthentik.io/docs/security).
## What authentik classifies as a CVE ## What authentik classifies as a CVE

View File

@ -146,10 +146,6 @@ entries:
] ]
] ]
nested_context: !Context context2 nested_context: !Context context2
at_index_sequence: !AtIndex [!Context sequence, 0]
at_index_sequence_default: !AtIndex [!Context sequence, 100, "non existent"]
at_index_mapping: !AtIndex [!Context mapping, "key2"]
at_index_mapping_default: !AtIndex [!Context mapping, "invalid", "non existent"]
identifiers: identifiers:
name: test name: test
conditions: conditions:

View File

@ -215,10 +215,6 @@ class TestBlueprintsV1(TransactionTestCase):
}, },
"nested_context": "context-nested-value", "nested_context": "context-nested-value",
"env_null": None, "env_null": None,
"at_index_sequence": "foo",
"at_index_sequence_default": "non existent",
"at_index_mapping": 2,
"at_index_mapping_default": "non existent",
} }
).exists() ).exists()
) )

View File

@ -24,10 +24,6 @@ from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
class UNSET:
"""Used to test whether a key has not been set."""
def get_attrs(obj: SerializerModel) -> dict[str, Any]: def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"""Get object's attributes via their serializer, and convert it to a normal dict""" """Get object's attributes via their serializer, and convert it to a normal dict"""
serializer: Serializer = obj.serializer(obj) serializer: Serializer = obj.serializer(obj)
@ -560,53 +556,6 @@ class Value(EnumeratedItem):
raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc raise EntryInvalidError.from_entry(f"Empty/invalid context: {context}", entry) from exc
class AtIndex(YAMLTag):
"""Get value at index of a sequence or mapping"""
obj: YAMLTag | dict | list | tuple
attribute: int | str | YAMLTag
default: Any | UNSET
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
super().__init__()
self.obj = loader.construct_object(node.value[0])
self.attribute = loader.construct_object(node.value[1])
if len(node.value) == 2: # noqa: PLR2004
self.default = UNSET
else:
self.default = loader.construct_object(node.value[2])
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
if isinstance(self.obj, YAMLTag):
obj = self.obj.resolve(entry, blueprint)
else:
obj = self.obj
if isinstance(self.attribute, YAMLTag):
attribute = self.attribute.resolve(entry, blueprint)
else:
attribute = self.attribute
if isinstance(obj, list | tuple):
try:
return obj[attribute]
except TypeError as exc:
raise EntryInvalidError.from_entry(
f"Invalid index for list: {attribute}", entry
) from exc
except IndexError as exc:
if self.default is UNSET:
raise EntryInvalidError.from_entry(
f"Index out of range: {attribute}", entry
) from exc
return self.default
if attribute in obj:
return obj[attribute]
else:
if self.default is UNSET:
raise EntryInvalidError.from_entry(f"Key does not exist: {attribute}", entry)
return self.default
class BlueprintDumper(SafeDumper): class BlueprintDumper(SafeDumper):
"""Dump dataclasses to yaml""" """Dump dataclasses to yaml"""
@ -657,7 +606,6 @@ class BlueprintLoader(SafeLoader):
self.add_constructor("!Enumerate", Enumerate) self.add_constructor("!Enumerate", Enumerate)
self.add_constructor("!Value", Value) self.add_constructor("!Value", Value)
self.add_constructor("!Index", Index) self.add_constructor("!Index", Index)
self.add_constructor("!AtIndex", AtIndex)
class EntryInvalidError(SentryIgnoredException): class EntryInvalidError(SentryIgnoredException):

View File

@ -1,54 +0,0 @@
"""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"]

View File

@ -103,9 +103,6 @@ class GroupSerializer(ModelSerializer):
"users": { "users": {
"default": list, "default": list,
}, },
# TODO: This field isn't unique on the database which is hard to backport
# hence we just validate the uniqueness here
"name": {"validators": [UniqueValidator(Group.objects.all())]},
} }

View File

@ -159,9 +159,9 @@ class SourceViewSet(
class UserSourceConnectionSerializer(SourceSerializer): class UserSourceConnectionSerializer(SourceSerializer):
"""User source connection""" """OAuth Source Serializer"""
source_obj = SourceSerializer(read_only=True, source="source") source = SourceSerializer(read_only=True)
class Meta: class Meta:
model = UserSourceConnection model = UserSourceConnection
@ -169,10 +169,10 @@ class UserSourceConnectionSerializer(SourceSerializer):
"pk", "pk",
"user", "user",
"source", "source",
"source_obj",
"created", "created",
] ]
extra_kwargs = { extra_kwargs = {
"user": {"read_only": True},
"created": {"read_only": True}, "created": {"read_only": True},
} }
@ -197,9 +197,9 @@ class UserSourceConnectionViewSet(
class GroupSourceConnectionSerializer(SourceSerializer): class GroupSourceConnectionSerializer(SourceSerializer):
"""Group Source Connection""" """Group Source Connection Serializer"""
source_obj = SourceSerializer(read_only=True) source = SourceSerializer(read_only=True)
class Meta: class Meta:
model = GroupSourceConnection model = GroupSourceConnection
@ -207,11 +207,12 @@ class GroupSourceConnectionSerializer(SourceSerializer):
"pk", "pk",
"group", "group",
"source", "source",
"source_obj",
"identifier", "identifier",
"created", "created",
] ]
extra_kwargs = { extra_kwargs = {
"group": {"read_only": True},
"identifier": {"read_only": True},
"created": {"read_only": True}, "created": {"read_only": True},
} }

View File

@ -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 Application, Provider from authentik.core.models import 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,13 +51,6 @@ 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"]

View File

@ -0,0 +1,21 @@
# Generated by Django 5.0.8 on 2024-08-08 12:09
from django.db import migrations, models
from authentik.lib.migrations import fallback_names
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0039_source_group_matching_mode_alter_group_name_and_more"),
]
operations = [
migrations.RunPython(fallback_names("authentik_core", "group", "name")),
migrations.AlterField(
model_name="group",
name="name",
field=models.TextField(unique=True, verbose_name="name"),
),
]

View File

@ -1,45 +0,0 @@
# 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),
),
]

View File

@ -173,7 +173,7 @@ class Group(SerializerModel, AttributesMixin):
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(_("name")) name = models.TextField(verbose_name=_("name"), unique=True)
is_superuser = models.BooleanField( is_superuser = models.BooleanField(
default=False, help_text=_("Users added to this group will be superusers.") default=False, help_text=_("Users added to this group will be superusers.")
) )
@ -314,32 +314,6 @@ 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
@ -607,31 +581,6 @@ 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"""

View File

@ -238,7 +238,13 @@ class SourceFlowManager:
self.request.GET, self.request.GET,
flow_slug=flow_slug, flow_slug=flow_slug,
) )
flow_context.setdefault(PLAN_CONTEXT_REDIRECT, final_redirect) # Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-user"
)
if PLAN_CONTEXT_REDIRECT not in flow_context:
flow_context[PLAN_CONTEXT_REDIRECT] = final_redirect
if not flow: if not flow:
return bad_request_message( return bad_request_message(

View File

@ -1,153 +0,0 @@
"""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())

View File

@ -6,7 +6,6 @@ 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
@ -70,7 +69,6 @@ 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(),

View File

@ -40,7 +40,6 @@ class Migration(migrations.Migration):
("require_authenticated", "Require Authenticated"), ("require_authenticated", "Require Authenticated"),
("require_unauthenticated", "Require Unauthenticated"), ("require_unauthenticated", "Require Unauthenticated"),
("require_superuser", "Require Superuser"), ("require_superuser", "Require Superuser"),
("require_redirect", "Require Redirect"),
("require_outpost", "Require Outpost"), ("require_outpost", "Require Outpost"),
], ],
default="none", default="none",

View File

@ -33,7 +33,6 @@ class FlowAuthenticationRequirement(models.TextChoices):
REQUIRE_AUTHENTICATED = "require_authenticated" REQUIRE_AUTHENTICATED = "require_authenticated"
REQUIRE_UNAUTHENTICATED = "require_unauthenticated" REQUIRE_UNAUTHENTICATED = "require_unauthenticated"
REQUIRE_SUPERUSER = "require_superuser" REQUIRE_SUPERUSER = "require_superuser"
REQUIRE_REDIRECT = "require_redirect"
REQUIRE_OUTPOST = "require_outpost" REQUIRE_OUTPOST = "require_outpost"

View File

@ -42,8 +42,6 @@ PLAN_CONTEXT_OUTPOST = "outpost"
# Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan # Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan
# was restored. # was restored.
PLAN_CONTEXT_IS_RESTORED = "is_restored" PLAN_CONTEXT_IS_RESTORED = "is_restored"
PLAN_CONTEXT_IS_REDIRECTED = "is_redirected"
PLAN_CONTEXT_REDIRECT_STAGE_TARGET = "redirect_stage_target"
CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_flows") CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_flows")
CACHE_PREFIX = "goauthentik.io/flows/planner/" CACHE_PREFIX = "goauthentik.io/flows/planner/"
@ -183,7 +181,7 @@ class FlowPlanner:
self.flow = flow self.flow = flow
self._logger = get_logger().bind(flow_slug=flow.slug) self._logger = get_logger().bind(flow_slug=flow.slug)
def _check_authentication(self, request: HttpRequest, context: dict[str, Any]): def _check_authentication(self, request: HttpRequest):
"""Check the flow's authentication level is matched by `request`""" """Check the flow's authentication level is matched by `request`"""
if ( if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
@ -200,11 +198,6 @@ class FlowPlanner:
and not request.user.is_superuser and not request.user.is_superuser
): ):
raise FlowNonApplicableException() raise FlowNonApplicableException()
if (
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_REDIRECT
and context.get(PLAN_CONTEXT_IS_REDIRECTED) is None
):
raise FlowNonApplicableException()
outpost_user = ClientIPMiddleware.get_outpost_user(request) outpost_user = ClientIPMiddleware.get_outpost_user(request)
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST: if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
if not outpost_user: if not outpost_user:
@ -236,13 +229,18 @@ class FlowPlanner:
) )
context = default_context or {} context = default_context or {}
# Bit of a workaround here, if there is a pending user set in the default context # Bit of a workaround here, if there is a pending user set in the default context
# we use that user for our cache key to make sure they don't get the generic response # we use that user for our cache key
# to make sure they don't get the generic response
if context and PLAN_CONTEXT_PENDING_USER in context: if context and PLAN_CONTEXT_PENDING_USER in context:
user = context[PLAN_CONTEXT_PENDING_USER] user = context[PLAN_CONTEXT_PENDING_USER]
else: else:
user = request.user user = request.user
# We only need to check the flow authentication if it's planned without a user
context.update(self._check_authentication(request, context)) # in the context, as a user in the context can only be set via the explicit code API
# or if a flow is restarted due to `invalid_response_action` being set to
# `restart_with_context`, which can only happen if the user was already authorized
# to use the flow
context.update(self._check_authentication(request))
# First off, check the flow's direct policy bindings # First off, check the flow's direct policy bindings
# to make sure the user even has access to the flow # to make sure the user even has access to the flow
engine = PolicyEngine(self.flow, user, request) engine = PolicyEngine(self.flow, user, request)

View File

@ -93,11 +93,7 @@ class ChallengeStageView(StageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Return a challenge for the frontend to solve""" """Return a challenge for the frontend to solve"""
try: challenge = self._get_challenge(*args, **kwargs)
challenge = self._get_challenge(*args, **kwargs)
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
if not challenge.is_valid(): if not challenge.is_valid():
self.logger.warning( self.logger.warning(
"f(ch): Invalid challenge", "f(ch): Invalid challenge",
@ -173,7 +169,11 @@ class ChallengeStageView(StageView):
stage_type=self.__class__.__name__, method="get_challenge" stage_type=self.__class__.__name__, method="get_challenge"
).time(), ).time(),
): ):
challenge = self.get_challenge(*args, **kwargs) try:
challenge = self.get_challenge(*args, **kwargs)
except StageInvalidException as exc:
self.logger.debug("Got StageInvalidException", exc=exc)
return self.executor.stage_invalid()
with start_span( with start_span(
op="authentik.flow.stage._get_challenge", op="authentik.flow.stage._get_challenge",
name=self.__class__.__name__, name=self.__class__.__name__,

View File

@ -22,12 +22,7 @@ from authentik.flows.models import (
FlowStageBinding, FlowStageBinding,
in_memory_stage, in_memory_stage,
) )
from authentik.flows.planner import ( from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
PLAN_CONTEXT_IS_REDIRECTED,
PLAN_CONTEXT_PENDING_USER,
FlowPlanner,
cache_key,
)
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.lib.tests.utils import dummy_get_response from authentik.lib.tests.utils import dummy_get_response
from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.apps import MANAGED_OUTPOST
@ -86,24 +81,6 @@ class TestFlowPlanner(TestCase):
planner.allow_empty_flows = True planner.allow_empty_flows = True
planner.plan(request) planner.plan(request)
def test_authentication_redirect_required(self):
"""Test flow authentication (redirect required)"""
flow = create_test_flow()
flow.authentication = FlowAuthenticationRequirement.REQUIRE_REDIRECT
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
request.user = AnonymousUser()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
with self.assertRaises(FlowNonApplicableException):
planner.plan(request)
context = {}
context[PLAN_CONTEXT_IS_REDIRECTED] = create_test_flow()
planner.plan(request, context)
@reconcile_app("authentik_outposts") @reconcile_app("authentik_outposts")
def test_authentication_outpost(self): def test_authentication_outpost(self):
"""Test flow authentication (outpost)""" """Test flow authentication (outpost)"""

View File

@ -171,8 +171,7 @@ class FlowExecutorView(APIView):
# Existing plan is deleted from session and instance # Existing plan is deleted from session and instance
self.plan = None self.plan = None
self.cancel() self.cancel()
else: self._logger.debug("f(exec): Continuing existing plan")
self._logger.debug("f(exec): Continuing existing plan")
# Initial flow request, check if we have an upstream query string passed in # Initial flow request, check if we have an upstream query string passed in
request.session[SESSION_KEY_GET] = get_params request.session[SESSION_KEY_GET] = get_params

View File

@ -5,7 +5,6 @@ import json
import os import os
from collections.abc import Mapping from collections.abc import Mapping
from contextlib import contextmanager from contextlib import contextmanager
from copy import deepcopy
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from glob import glob from glob import glob
@ -337,58 +336,6 @@ def redis_url(db: int) -> str:
return _redis_url return _redis_url
def django_db_config(config: ConfigLoader | None = None) -> dict:
if not config:
config = CONFIG
db = {
"default": {
"ENGINE": "authentik.root.db",
"HOST": config.get("postgresql.host"),
"NAME": config.get("postgresql.name"),
"USER": config.get("postgresql.user"),
"PASSWORD": config.get("postgresql.password"),
"PORT": config.get("postgresql.port"),
"OPTIONS": {
"sslmode": config.get("postgresql.sslmode"),
"sslrootcert": config.get("postgresql.sslrootcert"),
"sslcert": config.get("postgresql.sslcert"),
"sslkey": config.get("postgresql.sslkey"),
},
"TEST": {
"NAME": config.get("postgresql.test.name"),
},
}
}
if config.get_bool("postgresql.use_pgpool", False):
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
if config.get_bool("postgresql.use_pgbouncer", False):
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
db["default"]["CONN_MAX_AGE"] = None # persistent
for replica in config.get_keys("postgresql.read_replicas"):
_database = deepcopy(db["default"])
for setting, current_value in db["default"].items():
if isinstance(current_value, dict):
continue
override = config.get(
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
)
if override is not UNSET:
_database[setting] = override
for setting in db["default"]["OPTIONS"].keys():
override = config.get(
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
)
if override is not UNSET:
_database["OPTIONS"][setting] = override
db[f"replica_{replica}"] = _database
return db
if __name__ == "__main__": if __name__ == "__main__":
if len(argv) < 2: # noqa: PLR2004 if len(argv) < 2: # noqa: PLR2004
print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder)) print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder))

View File

@ -9,14 +9,7 @@ from unittest import mock
from django.conf import ImproperlyConfigured from django.conf import ImproperlyConfigured
from django.test import TestCase from django.test import TestCase
from authentik.lib.config import ( from authentik.lib.config import ENV_PREFIX, UNSET, Attr, AttrEncoder, ConfigLoader
ENV_PREFIX,
UNSET,
Attr,
AttrEncoder,
ConfigLoader,
django_db_config,
)
class TestConfig(TestCase): class TestConfig(TestCase):
@ -182,201 +175,3 @@ class TestConfig(TestCase):
config = ConfigLoader() config = ConfigLoader()
config.set("foo.bar", "baz") config.set("foo.bar", "baz")
self.assertEqual(list(config.get_keys("foo")), ["bar"]) self.assertEqual(list(config.get_keys("foo")), ["bar"])
def test_db_default(self):
"""Test default DB Config"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
}
},
)
def test_db_read_replicas(self):
"""Test read replicas"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
# Read replica
config.set("postgresql.read_replicas.0.host", "bar")
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
"replica_0": {
"ENGINE": "authentik.root.db",
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
},
)
def test_db_read_replicas_pgpool(self):
"""Test read replicas"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
config.set("postgresql.use_pgpool", True)
# Read replica
config.set("postgresql.read_replicas.0.host", "bar")
# This isn't supported
config.set("postgresql.read_replicas.0.use_pgpool", False)
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"DISABLE_SERVER_SIDE_CURSORS": True,
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
"replica_0": {
"DISABLE_SERVER_SIDE_CURSORS": True,
"ENGINE": "authentik.root.db",
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
},
)
def test_db_read_replicas_diff_ssl(self):
"""Test read replicas (with different SSL Settings)"""
"""Test read replicas"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.sslmode", "foo")
config.set("postgresql.sslrootcert", "foo")
config.set("postgresql.sslcert", "foo")
config.set("postgresql.sslkey", "foo")
config.set("postgresql.test.name", "foo")
# Read replica
config.set("postgresql.read_replicas.0.host", "bar")
config.set("postgresql.read_replicas.0.sslcert", "bar")
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
"replica_0": {
"ENGINE": "authentik.root.db",
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"sslcert": "bar",
"sslkey": "foo",
"sslmode": "foo",
"sslrootcert": "foo",
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
},
},
)

View File

@ -84,17 +84,19 @@ 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."""
target: PolicyBindingModel = attrs.get("target") count = sum(
supported = target.supported_policy_binding_targets() [
supported.sort() bool(attrs.get("policy", None)),
count = sum([bool(attrs.get(x, None)) for x in supported]) bool(attrs.get("group", None)),
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(f"Only one of {warning} can be set.") raise ValidationError("Only one of 'policy', 'group' or 'user' can be set.")
if empty: if empty:
raise ValidationError(f"One of {warning} must be set.") raise ValidationError("One of 'policy', 'group' or 'user' must be set.")
return attrs return attrs

View File

@ -1,6 +1,4 @@
# 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
@ -25,13 +23,4 @@ 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",
),
),
] ]

View File

@ -47,10 +47,6 @@ 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."""
@ -85,9 +81,7 @@ class PolicyBinding(SerializerModel):
blank=True, blank=True,
) )
target = InheritanceForeignKey( target = InheritanceForeignKey(PolicyBindingModel, on_delete=models.CASCADE, related_name="+")
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."),

View File

@ -38,7 +38,7 @@ class TestBindingsAPI(APITestCase):
) )
self.assertJSONEqual( self.assertJSONEqual(
response.content.decode(), response.content.decode(),
{"non_field_errors": ["Only one of 'group', 'policy', 'user' can be set."]}, {"non_field_errors": ["Only one of 'policy', 'group' or '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 'group', 'policy', 'user' must be set."]}, {"non_field_errors": ["One of 'policy', 'group' or 'user' must be set."]},
) )

View File

@ -1,38 +0,0 @@
# Generated by Django 5.0.10 on 2024-12-12 17:16
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_core", "0040_provider_invalidation_flow"),
(
"authentik_providers_oauth2",
"0025_rename_jwks_sources_oauth2provider_jwt_federation_sources_and_more",
),
]
operations = [
migrations.AlterField(
model_name="accesstoken",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="authentik_core.authenticatedsession",
),
),
migrations.AlterField(
model_name="authorizationcode",
name="session",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="authentik_core.authenticatedsession",
),
),
]

View File

@ -396,7 +396,7 @@ class BaseGrantModel(models.Model):
_scope = models.TextField(default="", verbose_name=_("Scopes")) _scope = models.TextField(default="", verbose_name=_("Scopes"))
auth_time = models.DateTimeField(verbose_name="Authentication time") auth_time = models.DateTimeField(verbose_name="Authentication time")
session = models.ForeignKey( session = models.ForeignKey(
AuthenticatedSession, null=True, on_delete=models.CASCADE, default=None AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
) )
class Meta: class Meta:
@ -497,11 +497,6 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
token = models.TextField(default=generate_client_secret) token = models.TextField(default=generate_client_secret)
_id_token = models.TextField(verbose_name=_("ID Token")) _id_token = models.TextField(verbose_name=_("ID Token"))
# Shadow the `session` field from `BaseGrantModel` as we want refresh tokens to persist even
# when the session is terminated.
session = models.ForeignKey(
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
)
class Meta: class Meta:
indexes = [ indexes = [

View File

@ -127,7 +127,6 @@ 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",

View File

@ -147,7 +147,6 @@ 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",
] ]
) )

View File

@ -12,7 +12,7 @@ from sentry_sdk import set_tag
from xmlsec import enable_debug_trace from xmlsec import enable_debug_trace
from authentik import __version__ from authentik import __version__
from authentik.lib.config import CONFIG, django_db_config, redis_url from authentik.lib.config import CONFIG, redis_url
from authentik.lib.logging import get_logger_config, structlog_configure from authentik.lib.logging import get_logger_config, structlog_configure
from authentik.lib.sentry import sentry_init from authentik.lib.sentry import sentry_init
from authentik.lib.utils.reflection import get_env from authentik.lib.utils.reflection import get_env
@ -114,7 +114,6 @@ TENANT_APPS = [
"authentik.stages.invitation", "authentik.stages.invitation",
"authentik.stages.password", "authentik.stages.password",
"authentik.stages.prompt", "authentik.stages.prompt",
"authentik.stages.redirect",
"authentik.stages.user_delete", "authentik.stages.user_delete",
"authentik.stages.user_login", "authentik.stages.user_login",
"authentik.stages.user_logout", "authentik.stages.user_logout",
@ -298,7 +297,47 @@ CHANNEL_LAYERS = {
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql" ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
DATABASES = django_db_config() DATABASES = {
"default": {
"ENGINE": "authentik.root.db",
"HOST": CONFIG.get("postgresql.host"),
"NAME": CONFIG.get("postgresql.name"),
"USER": CONFIG.get("postgresql.user"),
"PASSWORD": CONFIG.get("postgresql.password"),
"PORT": CONFIG.get("postgresql.port"),
"OPTIONS": {
"sslmode": CONFIG.get("postgresql.sslmode"),
"sslrootcert": CONFIG.get("postgresql.sslrootcert"),
"sslcert": CONFIG.get("postgresql.sslcert"),
"sslkey": CONFIG.get("postgresql.sslkey"),
},
"TEST": {
"NAME": CONFIG.get("postgresql.test.name"),
},
}
}
if CONFIG.get_bool("postgresql.use_pgpool", False):
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
if CONFIG.get_bool("postgresql.use_pgbouncer", False):
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
DATABASES["default"]["CONN_MAX_AGE"] = None # persistent
for replica in CONFIG.get_keys("postgresql.read_replicas"):
_database = DATABASES["default"].copy()
for setting in DATABASES["default"].keys():
default = object()
if setting in ("TEST",):
continue
override = CONFIG.get(
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=default
)
if override is not default:
_database[setting] = override
DATABASES[f"replica_{replica}"] = _database
DATABASE_ROUTERS = ( DATABASE_ROUTERS = (
"authentik.tenants.db.FailoverRouter", "authentik.tenants.db.FailoverRouter",

View File

@ -32,7 +32,6 @@ class KerberosSourceSerializer(SourceSerializer):
"group_matching_mode", "group_matching_mode",
"realm", "realm",
"krb5_conf", "krb5_conf",
"kadmin_type",
"sync_users", "sync_users",
"sync_users_password", "sync_users_password",
"sync_principal", "sync_principal",
@ -70,7 +69,6 @@ class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
"slug", "slug",
"enabled", "enabled",
"realm", "realm",
"kadmin_type",
"sync_users", "sync_users",
"sync_users_password", "sync_users_password",
"sync_principal", "sync_principal",

View File

@ -1,22 +0,0 @@
# Generated by Django 5.0.10 on 2024-12-06 19:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_kerberos", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="kerberossource",
name="kadmin_type",
field=models.TextField(
choices=[("MIT", "Mit"), ("Heimdal", "Heimdal"), ("other", "Other")],
default="other",
help_text="KAdmin server type",
),
),
]

View File

@ -13,7 +13,7 @@ from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from kadmin import KAdmin, KAdminApiVersion from kadmin import KAdmin
from kadmin.exceptions import PyKAdminException from kadmin.exceptions import PyKAdminException
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
@ -36,12 +36,6 @@ LOGGER = get_logger()
_kadmin_connections: dict[str, Any] = {} _kadmin_connections: dict[str, Any] = {}
class KAdminType(models.TextChoices):
MIT = "MIT"
HEIMDAL = "Heimdal"
OTHER = "other"
class KerberosSource(Source): class KerberosSource(Source):
"""Federate Kerberos realm with authentik""" """Federate Kerberos realm with authentik"""
@ -50,9 +44,6 @@ class KerberosSource(Source):
blank=True, blank=True,
help_text=_("Custom krb5.conf to use. Uses the system one by default"), help_text=_("Custom krb5.conf to use. Uses the system one by default"),
) )
kadmin_type = models.TextField(
choices=KAdminType.choices, default=KAdminType.OTHER, help_text=_("KAdmin server type")
)
sync_users = models.BooleanField( sync_users = models.BooleanField(
default=False, help_text=_("Sync users from Kerberos into authentik"), db_index=True default=False, help_text=_("Sync users from Kerberos into authentik"), db_index=True
@ -208,14 +199,6 @@ class KerberosSource(Source):
return str(conf_path) return str(conf_path)
def _kadmin_init(self) -> KAdmin | None: def _kadmin_init(self) -> KAdmin | None:
api_version = None
match self.kadmin_type:
case KAdminType.MIT:
api_version = KAdminApiVersion.Version4
case KAdminType.HEIMDAL:
api_version = KAdminApiVersion.Version2
case KAdminType.OTHER:
api_version = KAdminApiVersion.Version2
# kadmin doesn't use a ccache for its connection # kadmin doesn't use a ccache for its connection
# as such, we don't need to create a separate ccache for each source # as such, we don't need to create a separate ccache for each source
if not self.sync_principal: if not self.sync_principal:
@ -224,7 +207,6 @@ class KerberosSource(Source):
return KAdmin.with_password( return KAdmin.with_password(
self.sync_principal, self.sync_principal,
self.sync_password, self.sync_password,
api_version=api_version,
) )
if self.sync_keytab: if self.sync_keytab:
keytab = self.sync_keytab keytab = self.sync_keytab
@ -236,13 +218,11 @@ class KerberosSource(Source):
return KAdmin.with_keytab( return KAdmin.with_keytab(
self.sync_principal, self.sync_principal,
keytab, keytab,
api_version=api_version,
) )
if self.sync_ccache: if self.sync_ccache:
return KAdmin.with_ccache( return KAdmin.with_ccache(
self.sync_principal, self.sync_principal,
self.sync_ccache, self.sync_ccache,
api_version=api_version,
) )
return None return None

View File

@ -43,10 +43,8 @@ class KerberosSync:
self._messages = [] self._messages = []
self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__) self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
self.mapper = SourceMapper(self._source) self.mapper = SourceMapper(self._source)
self.user_manager = self.mapper.get_manager(User, ["principal", "principal_obj"]) self.user_manager = self.mapper.get_manager(User, ["principal"])
self.group_manager = self.mapper.get_manager( self.group_manager = self.mapper.get_manager(Group, ["group_id", "principal"])
Group, ["group_id", "principal", "principal_obj"]
)
self.matcher = SourceMatcher( self.matcher = SourceMatcher(
self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
) )
@ -69,16 +67,12 @@ class KerberosSync:
def _handle_principal(self, principal: str) -> bool: def _handle_principal(self, principal: str) -> bool:
try: try:
# TODO: handle permission error
principal_obj = self._connection.get_principal(principal)
defaults = self.mapper.build_object_properties( defaults = self.mapper.build_object_properties(
object_type=User, object_type=User,
manager=self.user_manager, manager=self.user_manager,
user=None, user=None,
request=None, request=None,
principal=principal, principal=principal,
principal_obj=principal_obj,
) )
self._logger.debug("Writing user with attributes", **defaults) self._logger.debug("Writing user with attributes", **defaults)
if "username" not in defaults: if "username" not in defaults:
@ -97,7 +91,6 @@ class KerberosSync:
request=None, request=None,
group_id=group_id, group_id=group_id,
principal=principal, principal=principal,
principal_obj=principal_obj,
) )
for group_id in defaults.pop("groups", []) for group_id in defaults.pop("groups", [])
} }

View File

@ -17,7 +17,6 @@ class TestMetadataProcessor(TestCase):
def setUp(self): def setUp(self):
self.factory = RequestFactory() self.factory = RequestFactory()
self.source = SAMLSource.objects.create( self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(), slug=generate_id(),
issuer="authentik", issuer="authentik",
signing_kp=create_test_cert(), signing_kp=create_test_cert(),

View File

@ -28,7 +28,6 @@ class TestPropertyMappings(TestCase):
def setUp(self): def setUp(self):
self.factory = RequestFactory() self.factory = RequestFactory()
self.source = SAMLSource.objects.create( self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(), slug=generate_id(),
issuer="authentik", issuer="authentik",
allow_idp_initiated=True, allow_idp_initiated=True,

View File

@ -20,7 +20,6 @@ class TestResponseProcessor(TestCase):
def setUp(self): def setUp(self):
self.factory = RequestFactory() self.factory = RequestFactory()
self.source = SAMLSource.objects.create( self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(), slug=generate_id(),
issuer="authentik", issuer="authentik",
allow_idp_initiated=True, allow_idp_initiated=True,

View File

@ -1,88 +0,0 @@
"""SAML Source tests"""
from base64 import b64encode
from django.test import RequestFactory, TestCase
from django.urls import reverse
from authentik.core.tests.utils import create_test_flow
from authentik.flows.planner import PLAN_CONTEXT_REDIRECT, FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import load_fixture
from authentik.sources.saml.models import SAMLSource
class TestViews(TestCase):
"""Test SAML Views"""
def setUp(self):
self.factory = RequestFactory()
self.source = SAMLSource.objects.create(
name=generate_id(),
slug=generate_id(),
issuer="authentik",
allow_idp_initiated=True,
pre_authentication_flow=create_test_flow(),
)
def test_enroll(self):
"""Enroll"""
flow = create_test_flow()
self.source.enrollment_flow = flow
self.source.save()
response = self.client.post(
reverse(
"authentik_sources_saml:acs",
kwargs={
"source_slug": self.source.slug,
},
),
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_success.xml").encode()
).decode()
},
)
self.assertEqual(response.status_code, 302)
self.assertRedirects(
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
)
plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN)
self.assertIsNotNone(plan)
def test_enroll_redirect(self):
"""Enroll when attempting to access a provider"""
initial_redirect = f"http://{generate_id()}"
session = self.client.session
old_plan = FlowPlan(generate_id())
old_plan.context[PLAN_CONTEXT_REDIRECT] = initial_redirect
session[SESSION_KEY_PLAN] = old_plan
session.save()
flow = create_test_flow()
self.source.enrollment_flow = flow
self.source.save()
response = self.client.post(
reverse(
"authentik_sources_saml:acs",
kwargs={
"source_slug": self.source.slug,
},
),
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_success.xml").encode()
).decode()
},
)
self.assertEqual(response.status_code, 302)
self.assertRedirects(
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
)
plan: FlowPlan = self.client.session.get(SESSION_KEY_PLAN)
self.assertIsNotNone(plan)
self.assertEqual(plan.context.get(PLAN_CONTEXT_REDIRECT), initial_redirect)

View File

@ -28,11 +28,10 @@ from authentik.flows.planner import (
PLAN_CONTEXT_REDIRECT, PLAN_CONTEXT_REDIRECT,
PLAN_CONTEXT_SOURCE, PLAN_CONTEXT_SOURCE,
PLAN_CONTEXT_SSO, PLAN_CONTEXT_SSO,
FlowPlan,
FlowPlanner, FlowPlanner,
) )
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET
from authentik.lib.views import bad_request_message from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import nice64 from authentik.providers.saml.utils.encoding import nice64
from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat
@ -149,15 +148,12 @@ class ACSView(View):
processor = ResponseProcessor(source, request) processor = ResponseProcessor(source, request)
try: try:
processor.parse() processor.parse()
except (MissingSAMLResponse, VerificationError) as exc: except MissingSAMLResponse as exc:
return bad_request_message(request, str(exc))
except VerificationError as exc:
return bad_request_message(request, str(exc)) return bad_request_message(request, str(exc))
try: try:
if SESSION_KEY_PLAN in request.session:
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
plan_redirect = plan.context.get(PLAN_CONTEXT_REDIRECT)
if plan_redirect:
self.request.session[SESSION_KEY_GET] = {NEXT_ARG_NAME: plan_redirect}
return processor.prepare_flow_manager().get_flow() return processor.prepare_flow_manager().get_flow()
except (UnsupportedNameIDFormat, ValueError) as exc: except (UnsupportedNameIDFormat, ValueError) as exc:
return bad_request_message(request, str(exc)) return bad_request_message(request, str(exc))

View File

@ -141,10 +141,5 @@
"name": "Devolutions", "name": "Devolutions",
"icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==", "icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==",
"icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==" "icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg=="
}, }
"22248c4c-7a12-46e2-9a41-44291b373a4d": { }
"name": "LogMeOnce",
"icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMjA0OCAyMDQ4IiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTSAwIDAgTCAtNSAxIEwgLTE1IDQgTCAtMzggMTEgTCAtNTcgMTggTCAtNzYgMjkgTCAtOTAgMzggTCAtMTA5IDUyIEwgLTExNiA1OSBMIC0xMjcgNjggTCAtMTQwIDgxIEwgLTE0OCA4OCBMIC0xNDkgOTEgTCAtMTUxIDkxIEwgLTE2MSAxMDEgTCAtMTY5IDEwOCBMIC0xNjkgMTEwIEwgLTE3MSAxMTAgTCAtMTgwIDExOSBMIC0xODggMTI2IEwgLTIwMSAxMzcgTCAtMjEyIDE0NiBMIC0yMjYgMTU3IEwgLTIzOSAxNjcgTCAtMjUwIDE3NSBMIC0yNzAgMTg4IEwgLTI5MSAyMDIgTCAtMzA5IDIxMiBMIC0zMzAgMjI0IEwgLTM1MyAyMzUgTCAtMzg2IDI1MCBMIC00MDcgMjU5IEwgLTQ0OSAyNzMgTCAtNDcyIDI4MSBMIC01MDIgMjg5IEwgLTU1MSAyOTggTCAtNjE1IDMwOSBMIC02NDcgMzE2IEwgLTY4MSAzMjQgTCAtNjk4IDMzMCBMIC03MTQgMzM4IEwgLTczNSAzNTEgTCAtNzQ1IDM1OCBMIC03NTYgMzY3IEwgLTc2NCAzNzUgTCAtNzY2IDM3OSBMIC03NjggMzc5IEwgLTc3MyAzODUgTCAtNzgxIDM5NSBMIC03OTEgNDEwIEwgLTgwMSA0MjggTCAtODEwIDQ0NyBMIC04MTYgNDY1IEwgLTgyMiA1MDAgTCAtODI3IDU0NSBMIC04MzAgNTkyIEwgLTgzMiA2NTMgTCAtODMyIDcxMiBMIC04MzEgNzQyIEwgLTgyOCA3OTQgTCAtODIyIDg2NiBMIC04MTYgOTI4IEwgLTgxMiA5NTcgTCAtODAwIDEwMjAgTCAtNzg5IDEwNzIgTCAtNzgzIDEwOTggTCAtNzY1IDExNjIgTCAtNzUxIDEyMDcgTCAtNzQ0IDEyMjUgTCAtNzM0IDEyNTQgTCAtNzIwIDEyOTAgTCAtNzA0IDEzMjggTCAtNjg5IDEzNjEgTCAtNjY4IDE0MDMgTCAtNjUzIDE0MzEgTCAtNjM5IDE0NTYgTCAtNjIwIDE0ODggTCAtNjA3IDE1MDkgTCAtNTk0IDE1MjkgTCAtNTg0IDE1NDQgTCAtNTcxIDE1NjMgTCAtNTU4IDE1ODEgTCAtNTQ0IDE2MDAgTCAtNTMzIDE2MTUgTCAtNTIwIDE2MzIgTCAtNTA3IDE2NDggTCAtNDc5IDE2ODIgTCAtNDcwIDE2OTMgTCAtNDYwIDE3MDQgTCAtNDQ5IDE3MTYgTCAtNDQyIDE3MjQgTCAtNDMyIDE3MzQgTCAtNDI1IDE3NDIgTCAtMzY4IDE3OTkgTCAtMzUxIDE4MTUgTCAtMzM1IDE4MzAgTCAtMzI3IDE4MzcgTCAtMzE0IDE4NDkgTCAtMjg4IDE4NzEgTCAtMjcxIDE4ODUgTCAtMjYxIDE4OTMgTCAtMjQ0IDE5MDcgTCAtMjI4IDE5MTkgTCAtMjE1IDE5MjkgTCAtMTk2IDE5NDMgTCAtMTg0IDE5NTIgTCAtMTY4IDE5NjMgTCAtMTQ4IDE5NzcgTCAtMTMzIDE5ODcgTCAtMTEyIDIwMDAgTCAtODggMjAxMyBMIC03MiAyMDIxIEwgLTUyIDIwMzAgTCAtMzIgMjAzNyBMIDEyIDIwNDggTCA2MCAyMDQ4IEwgNjEgMjA0NyBMIDczIDIwNDQgTCAxMDAgMjAzNyBMIDEyMSAyMDI5IEwgMTQ5IDIwMTUgTCAxNjcgMjAwNSBMIDE4NiAxOTk0IEwgMjEwIDE5NzkgTCAyMzEgMTk2NSBMIDI2NiAxOTQxIEwgMjg0IDE5MjggTCAyOTUgMTkyMCBMIDMwNiAxOTExIEwgMzI0IDE4OTggTCAzMzggMTg4NyBMIDM0OSAxODc3IEwgMzU4IDE4NzAgTCAzNjkgMTg2MCBMIDM4MCAxODUxIEwgMzgwIDE4NDkgTCAzODIgMTg0OSBMIDM4NSAxODQ2IEwgMzk2IDE4MzcgTCA0MDMgMTgzMCBMIDQxMSAxODIzIEwgNDE4IDE4MTYgTCA0MjYgMTgwOSBMIDQ3NSAxNzYwIEwgNDc3IDE3NTYgTCA0NzkgMTc1NiBMIDQ4NyAxNzQ3IEwgNTAzIDE3MzAgTCA1MTAgMTcyMiBMIDUxOSAxNzEyIEwgNTI2IDE3MDQgTCA1MzggMTY5MSBMIDU0NyAxNjgwIEwgNTU4IDE2NjcgTCA1NjYgMTY1NiBMIDU3OSAxNjQwIEwgNTkwIDE2MjYgTCA2MDYgMTYwNSBMIDYyMSAxNTgzIEwgNjMxIDE1NjkgTCA2NDUgMTU0OSBMIDY1NSAxNTMzIEwgNjcwIDE1MTAgTCA2ODUgMTQ4NSBMIDY5NiAxNDY3IEwgNzA2IDE0NDkgTCA3MzAgMTQwNSBMIDc0NSAxMzc0IEwgNzYyIDEzMzggTCA3NzQgMTMxMCBMIDc5MiAxMjY3IEwgODExIDEyMTYgTCA4MjcgMTE2NiBMIDg0MiAxMTE3IEwgODU4IDEwNTEgTCA4NzAgOTk2IEwgODc1IDk2NyBMIDg4MSA5MTcgTCA4ODYgODgyIEwgODkxIDg0NiBMIDg5NCA4MTIgTCA4OTYgNzc0IEwgODk2IDY1NSBMIDg5NSA2MzAgTCA4OTIgNTkzIEwgODg4IDU1OSBMIDg3NyA0NzAgTCA4NzIgNDQ1IEwgODY1IDQyNiBMIDg1NyA0MTEgTCA4NDcgMzk1IEwgODM0IDM3OCBMIDgxNCAzNTggTCA4MDAgMzQ3IEwgNzg1IDMzNyBMIDc2NiAzMjcgTCA3NTAgMzIxIEwgNzI3IDMxNSBMIDY5MyAzMDggTCA2NTAgMzAxIEwgNTk3IDI5MiBMIDU3MCAyODYgTCA1NDggMjgwIEwgNTE1IDI3MCBMIDQ4NyAyNjEgTCA0NTkgMjUwIEwgNDM4IDI0MSBMIDQxNCAyMzAgTCAzODIgMjEzIEwgMzU4IDE5OSBMIDM0NyAxOTIgTCAzMzAgMTgwIEwgMzEyIDE2NyBMIDMwMCAxNTcgTCAyODYgMTQ2IEwgMjcxIDEzMyBMIDI2MyAxMjYgTCAyNTEgMTE1IEwgMjQwIDEwNiBMIDIyOCA5NSBMIDIwMCA3MSBMIDE4OSA2MiBMIDE3NCA1MCBMIDE1OCAzOCBMIDE0MiAyNyBMIDEyNyAxOSBMIDExNSAxNCBMIDk1IDggTCA3MSAwIEwgMCAwIHogTSAzMCA2MjIgTCA1MSA2MjMgTCA3OSA2MjcgTCAxMDAgNjMzIEwgMTIxIDY0MSBMIDE0MSA2NTIgTCAxNTcgNjYzIEwgMTcyIDY3NiBMIDE4NiA2OTAgTCAxODYgNjkyIEwgMTg4IDY5MiBMIDIwNiA3MTYgTCAyMTYgNzM0IEwgMjIxIDc0NSBMIDIyNiA3NTggTCAyMzMgNzg0IEwgMjM2IDc5OCBMIDIzNyA4MDggTCAyMzcgODQwIEwgMjMzIDg2NSBMIDIyNiA4OTEgTCAyMTkgOTA5IEwgMjA5IDkyOCBMIDE5NSA5NDkgTCAxODYgOTU5IEwgMTc5IDk2NyBMIDE2NiA5ODAgTCAxNTUgOTg5IEwgMTQyIDk5OCBMIDEyNiAxMDA3IEwgMTEzIDEwMTIgTCAxMTQgMTAyNCBMIDEyMiAxMDYwIEwgMTM3IDExMjMgTCAxNTYgMTIwOCBMIDE3MCAxMjcxIEwgMTc0IDEyOTYgTCAxNzYgMTMxMCBMIDE3NiAxMzM1IEwgMTczIDEzNDkgTCAxNjYgMTM1OSBMIDE1NyAxMzY3IEwgMTQ2IDEzNzIgTCAxNDAgMTM3NCBMIDExOSAxMzc2IEwgNTEgMTM3NiBMIC0zMiAxMzc3IEwgLTY0IDEzNzcgTCAtNzggMTM3NCBMIC04NSAxMzcwIEwgLTk4IDEzNTkgTCAtMTA2IDEzNDkgTCAtMTEwIDEzNDEgTCAtMTEyIDEzMzMgTCAtMTEyIDEzMTggTCAtMTA3IDEyODggTCAtMTAwIDEyNTYgTCAtODUgMTE5NiBMIC02OSAxMTI0IEwgLTU3IDEwNzIgTCAtNTAgMTAzNiBMIC00NyAxMDExIEwgLTUzIDEwMDkgTCAtNjkgMTAwMSBMIC04MSA5OTQgTCAtOTMgOTg1IEwgLTEwMyA5NzYgTCAtMTExIDk2OSBMIC0xMjAgOTYwIEwgLTEzMSA5NDYgTCAtMTQyIDkyOSBMIC0xNTIgOTEwIEwgLTE2MCA4OTAgTCAtMTY2IDg3MCBMIC0xNjkgODUzIEwgLTE3MCA4NDQgTCAtMTcwIDgxNCBMIC0xNjcgNzk0IEwgLTE2MSA3NjcgTCAtMTU0IDc0NiBMIC0xMzcgNzEzIEwgLTEyNiA2OTggTCAtMTE5IDY5MCBMIC0xMDggNjc4IEwgLTkzIDY2NSBMIC03NyA2NTMgTCAtNTYgNjQxIEwgLTM4IDYzMyBMIC0xNiA2MjcgTCAzIDYyNCBMIDMwIDYyMiB6ICIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTkxKSIgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgLz4KPC9zdmc+Cg==",
"icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyMDQ4IDIwNDgiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5OTEpIiBkPSJtMCAwaDcxbDI0IDggMjAgNiAxMiA1IDE1IDggMTYgMTEgMTYgMTIgMTUgMTIgMTEgOSAyOCAyNCAxMiAxMSAxMSA5IDEyIDExIDggNyAxNSAxMyAxNCAxMSAxMiAxMCAxOCAxMyAxNyAxMiAxMSA3IDI0IDE0IDMyIDE3IDI0IDExIDIxIDkgMjggMTEgMjggOSAzMyAxMCAyMiA2IDI3IDYgNTMgOSA0MyA3IDM0IDcgMjMgNiAxNiA2IDE5IDEwIDE1IDEwIDE0IDExIDIwIDIwIDEzIDE3IDEwIDE2IDggMTUgNyAxOSA1IDI1IDExIDg5IDQgMzQgMyAzNyAxIDI1djExOWwtMiAzOC0zIDM0LTUgMzYtNSAzNS02IDUwLTUgMjktMTIgNTUtMTYgNjYtMTUgNDktMTYgNTAtMTkgNTEtMTggNDMtMTIgMjgtMTcgMzYtMTUgMzEtMjQgNDQtMTAgMTgtMTEgMTgtMTUgMjUtMTUgMjMtMTAgMTYtMTQgMjAtMTAgMTQtMTUgMjItMTYgMjEtMTEgMTQtMTMgMTYtOCAxMS0xMSAxMy05IDExLTEyIDEzLTcgOC05IDEwLTcgOC0xNiAxNy04IDloLTJsLTIgNC00OSA0OS04IDctNyA3LTggNy03IDctMTEgOS0zIDNoLTJ2MmwtMTEgOS0xMSAxMC05IDctMTEgMTAtMTQgMTEtMTggMTMtMTEgOS0xMSA4LTE4IDEzLTM1IDI0LTIxIDE0LTI0IDE1LTE5IDExLTE4IDEwLTI4IDE0LTIxIDgtMjcgNy0xMiAzLTEgMWgtNDhsLTQ0LTExLTIwLTctMjAtOS0xNi04LTI0LTEzLTIxLTEzLTE1LTEwLTIwLTE0LTE2LTExLTEyLTktMTktMTQtMTMtMTAtMTYtMTItMTctMTQtMTAtOC0xNy0xNC0yNi0yMi0xMy0xMi04LTctMTYtMTUtMTctMTYtNTctNTctNy04LTEwLTEwLTctOC0xMS0xMi0xMC0xMS05LTExLTI4LTM0LTEzLTE2LTEzLTE3LTExLTE1LTE0LTE5LTEzLTE4LTEzLTE5LTEwLTE1LTEzLTIwLTEzLTIxLTE5LTMyLTE0LTI1LTE1LTI4LTIxLTQyLTE1LTMzLTE2LTM4LTE0LTM2LTEwLTI5LTctMTgtMTQtNDUtMTgtNjQtNi0yNi0xMS01Mi0xMi02My00LTI5LTYtNjItNi03Mi0zLTUyLTEtMzB2LTU5bDItNjEgMy00NyA1LTQ1IDYtMzUgNi0xOCA5LTE5IDEwLTE4IDEwLTE1IDgtMTAgNS02aDJsMi00IDgtOCAxMS05IDEwLTcgMjEtMTMgMTYtOCAxNy02IDM0LTggMzItNyA2NC0xMSA0OS05IDMwLTggMjMtOCA0Mi0xNCAyMS05IDMzLTE1IDIzLTExIDIxLTEyIDE4LTEwIDIxLTE0IDIwLTEzIDExLTggMTMtMTAgMTQtMTEgMTEtOSAxMy0xMSA4LTcgOS05aDJ2LTJsOC03IDEwLTEwaDJsMS0zIDgtNyAxMy0xMyAxMS05IDctNyAxOS0xNCAxNC05IDE5LTExIDE5LTcgMjMtNyAxMC0zeiIgZmlsbD0iI0YxODQyOSIvPgo8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDIxLDYyMikiIGQ9Im0wIDAgMjEgMSAyOCA0IDIxIDYgMjEgOCAyMCAxMSAxNiAxMSAxNSAxMyAxNCAxNHYyaDJsMTggMjQgMTAgMTggNSAxMSA1IDEzIDcgMjYgMyAxNCAxIDEwdjMybC00IDI1LTcgMjYtNyAxOC0xMCAxOS0xNCAyMS05IDEwLTcgOC0xMyAxMy0xMSA5LTEzIDktMTYgOS0xMyA1IDEgMTIgOCAzNiAxNSA2MyAxOSA4NSAxNCA2MyA0IDI1IDIgMTR2MjVsLTMgMTQtNyAxMC05IDgtMTEgNS02IDItMjEgMmgtNjhsLTgzIDFoLTMybC0xNC0zLTctNC0xMy0xMS04LTEwLTQtOC0yLTh2LTE1bDUtMzAgNy0zMiAxNS02MCAxNi03MiAxMi01MiA3LTM2IDMtMjUtNi0yLTE2LTgtMTItNy0xMi05LTEwLTktOC03LTktOS0xMS0xNC0xMS0xNy0xMC0xOS04LTIwLTYtMjAtMy0xNy0xLTl2LTMwbDMtMjAgNi0yNyA3LTIxIDE3LTMzIDExLTE1IDctOCAxMS0xMiAxNS0xMyAxNi0xMiAyMS0xMiAxOC04IDIyLTYgMTktM3oiIGZpbGw9IiNGRUZGRkUiLz4KPC9zdmc+Cg=="
}
}

View File

@ -37,7 +37,7 @@
<tr> <tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center"> <td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktrans with expires=expires|naturaltime %} {% blocktrans with expires=expires|naturaltime %}
If you did not request a password change, please ignore this email. The link above is valid for {{ expires }}. If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
{% endblocktrans %} {% endblocktrans %}
</td> </td>
</tr> </tr>

View File

@ -5,7 +5,7 @@ You recently requested to change your password for your authentik account. Use t
{% endblocktrans %} {% endblocktrans %}
{{ url }} {{ url }}
{% blocktrans with expires=expires|naturaltime %} {% blocktrans with expires=expires|naturaltime %}
If you did not request a password change, please ignore this email. The link above is valid for {{ expires }}. If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
{% endblocktrans %} {% endblocktrans %}
-- --

View File

@ -1,42 +0,0 @@
"""RedirectStage API Views"""
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.redirect.models import RedirectMode, RedirectStage
class RedirectStageSerializer(StageSerializer):
"""RedirectStage Serializer"""
def validate(self, attrs):
mode = attrs.get("mode")
target_static = attrs.get("target_static")
target_flow = attrs.get("target_flow")
if mode == RedirectMode.STATIC and not target_static:
raise ValidationError(_("Target URL should be present when mode is Static."))
if mode == RedirectMode.FLOW and not target_flow:
raise ValidationError(_("Target Flow should be present when mode is Flow."))
return attrs
class Meta:
model = RedirectStage
fields = StageSerializer.Meta.fields + [
"keep_context",
"mode",
"target_static",
"target_flow",
]
class RedirectStageViewSet(UsedByMixin, ModelViewSet):
"""RedirectStage Viewset"""
queryset = RedirectStage.objects.all()
serializer_class = RedirectStageSerializer
filterset_fields = ["name"]
search_fields = ["name"]
ordering = ["name"]

View File

@ -1,11 +0,0 @@
"""authentik redirect app"""
from django.apps import AppConfig
class AuthentikStageRedirectConfig(AppConfig):
"""authentik redirect app"""
name = "authentik.stages.redirect"
label = "authentik_stages_redirect"
verbose_name = "authentik Stages.Redirect"

View File

@ -1,49 +0,0 @@
# Generated by Django 5.0.10 on 2024-12-11 14:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_flows", "0027_auto_20231028_1424"),
]
operations = [
migrations.CreateModel(
name="RedirectStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
("keep_context", models.BooleanField(default=True)),
("mode", models.TextField(choices=[("static", "Static"), ("flow", "Flow")])),
("target_static", models.CharField(blank=True, default="")),
(
"target_flow",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_flows.flow",
),
),
],
options={
"verbose_name": "Redirect Stage",
"verbose_name_plural": "Redirect Stages",
},
bases=("authentik_flows.stage",),
),
]

View File

@ -1,49 +0,0 @@
"""authentik redirect stage"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from authentik.flows.models import Flow, Stage
class RedirectMode(models.TextChoices):
"""Mode a Redirect stage can operate in"""
STATIC = "static"
FLOW = "flow"
class RedirectStage(Stage):
"""Redirect the user to another flow, potentially with all gathered context"""
keep_context = models.BooleanField(default=True)
mode = models.TextField(choices=RedirectMode.choices)
target_static = models.CharField(blank=True, default="")
target_flow = models.ForeignKey(
Flow,
null=True,
blank=True,
on_delete=models.SET_NULL,
)
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.stages.redirect.api import RedirectStageSerializer
return RedirectStageSerializer
@property
def view(self) -> type[View]:
from authentik.stages.redirect.stage import RedirectStageView
return RedirectStageView
@property
def component(self) -> str:
return "ak-stage-redirect-form"
class Meta:
verbose_name = _("Redirect Stage")
verbose_name_plural = _("Redirect Stages")

View File

@ -1,110 +0,0 @@
"""authentik redirect stage"""
from urllib.parse import urlsplit
from django.http.response import HttpResponse
from rest_framework.fields import CharField
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
RedirectChallenge,
)
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import (
Flow,
)
from authentik.flows.planner import (
PLAN_CONTEXT_IS_REDIRECTED,
PLAN_CONTEXT_REDIRECT_STAGE_TARGET,
FlowPlanner,
)
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import SESSION_KEY_PLAN, InvalidStageError
from authentik.lib.utils.urls import reverse_with_qs
from authentik.stages.redirect.models import RedirectMode, RedirectStage
URL_SCHEME_FLOW = "ak-flow"
class RedirectChallengeResponse(ChallengeResponse):
"""Redirect challenge response"""
component = CharField(default="xak-flow-redirect")
to = CharField()
class RedirectStageView(ChallengeStageView):
"""Redirect stage to redirect to other Flows with context"""
response_class = RedirectChallengeResponse
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()
def parse_target(self, target: str) -> str | Flow:
parsed_target = urlsplit(target)
if parsed_target.scheme != URL_SCHEME_FLOW:
return target
flow = Flow.objects.filter(slug=parsed_target.netloc).first()
if not flow:
self.logger.warning(
f"Flow set by {PLAN_CONTEXT_REDIRECT_STAGE_TARGET} does not exist",
flow_slug=parsed_target.path,
)
return flow
def switch_flow_with_context(self, flow: Flow, keep_context=True) -> str:
"""Switch to another flow, optionally keeping all context"""
self.logger.info(
"f(exec): Switching to new flow", new_flow=flow.slug, keep_context=keep_context
)
planner = FlowPlanner(flow)
planner.use_cache = False
default_context = self.executor.plan.context if keep_context else {}
try:
default_context[PLAN_CONTEXT_IS_REDIRECTED] = self.executor.flow
plan = planner.plan(self.request, default_context)
except FlowNonApplicableException as exc:
raise InvalidStageError() from exc
self.request.session[SESSION_KEY_PLAN] = plan
kwargs = self.executor.kwargs
kwargs.update({"flow_slug": flow.slug})
return reverse_with_qs("authentik_core:if-flow", self.request.GET, kwargs=kwargs)
def get_challenge(self, *args, **kwargs) -> Challenge:
"""Get the redirect target. Prioritize `redirect_stage_target` if present."""
current_stage: RedirectStage = self.executor.current_stage
target: str | Flow = ""
target_url_override = self.executor.plan.context.get(PLAN_CONTEXT_REDIRECT_STAGE_TARGET, "")
if target_url_override:
target = self.parse_target(target_url_override)
# `target` is falsy if the override was to a Flow but that Flow doesn't exist.
if not target:
if current_stage.mode == RedirectMode.STATIC:
target = current_stage.target_static
if current_stage.mode == RedirectMode.FLOW:
target = current_stage.target_flow
if isinstance(target, str):
redirect_to = target
else:
redirect_to = self.switch_flow_with_context(
target, keep_context=current_stage.keep_context
)
if not redirect_to:
raise InvalidStageError(
"No target found for Redirect stage. The stage's target_flow may have been deleted."
)
return RedirectChallenge(
data={
"component": "xak-flow-redirect",
"to": redirect_to,
}
)

View File

@ -1,172 +0,0 @@
"""Test Redirect stage"""
from django.urls.base import reverse
from rest_framework.exceptions import ValidationError
from authentik.core.tests.utils import create_test_flow
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.stages.dummy.models import DummyStage
from authentik.stages.redirect.api import RedirectStageSerializer
from authentik.stages.redirect.models import RedirectMode, RedirectStage
URL = "https://url.test/"
URL_OVERRIDE = "https://urloverride.test/"
class TestRedirectStage(FlowTestCase):
"""Test Redirect stage API"""
def setUp(self):
super().setUp()
self.target_flow = create_test_flow(FlowDesignation.AUTHENTICATION)
self.dummy_stage = DummyStage.objects.create(name="dummy")
FlowStageBinding.objects.create(target=self.target_flow, stage=self.dummy_stage, order=0)
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
self.stage = RedirectStage.objects.create(
name="redirect",
keep_context=True,
mode=RedirectMode.STATIC,
target_static=URL,
target_flow=self.target_flow,
)
self.binding = FlowStageBinding.objects.create(
target=self.flow,
stage=self.stage,
order=0,
)
def test_static(self):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(response, URL)
def test_flow(self):
self.stage.mode = RedirectMode.FLOW
self.stage.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": self.target_flow.slug})
)
def test_override_static(self):
policy = ExpressionPolicy.objects.create(
name=generate_id(),
expression=f"context['flow_plan'].context['redirect_stage_target'] = "
f"'{URL_OVERRIDE}'; return True",
)
PolicyBinding.objects.create(policy=policy, target=self.binding, order=0)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(response, URL_OVERRIDE)
def test_override_flow(self):
target_flow_override = create_test_flow(FlowDesignation.AUTHENTICATION)
dummy_stage_override = DummyStage.objects.create(name="dummy_override")
FlowStageBinding.objects.create(
target=target_flow_override, stage=dummy_stage_override, order=0
)
policy = ExpressionPolicy.objects.create(
name=generate_id(),
expression=f"context['flow_plan'].context['redirect_stage_target'] = "
f"'ak-flow://{target_flow_override.slug}'; return True",
)
PolicyBinding.objects.create(policy=policy, target=self.binding, order=0)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(
response,
reverse("authentik_core:if-flow", kwargs={"flow_slug": target_flow_override.slug}),
)
def test_override_nonexistant_flow(self):
policy = ExpressionPolicy.objects.create(
name=generate_id(),
expression="context['flow_plan'].context['redirect_stage_target'] = "
"'ak-flow://nonexistent'; return True",
)
PolicyBinding.objects.create(policy=policy, target=self.binding, order=0)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(response, URL)
def test_target_flow_requires_redirect(self):
self.target_flow.authentication = FlowAuthenticationRequirement.REQUIRE_REDIRECT
self.target_flow.save()
self.stage.mode = RedirectMode.FLOW
self.stage.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageRedirects(
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": self.target_flow.slug})
)
def test_target_flow_non_applicable(self):
self.target_flow.authentication = FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
self.target_flow.save()
self.stage.mode = RedirectMode.FLOW
self.stage.save()
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertStageResponse(response, component="ak-stage-access-denied")
def test_serializer(self):
with self.assertRaises(ValidationError):
RedirectStageSerializer(
data={
"name": generate_id(20),
"mode": RedirectMode.STATIC,
}
).is_valid(raise_exception=True)
self.assertTrue(
RedirectStageSerializer(
data={
"name": generate_id(20),
"mode": RedirectMode.STATIC,
"target_static": URL,
}
).is_valid(raise_exception=True)
)
with self.assertRaises(ValidationError):
RedirectStageSerializer(
data={
"name": generate_id(20),
"mode": RedirectMode.FLOW,
}
).is_valid(raise_exception=True)
self.assertTrue(
RedirectStageSerializer(
data={
"name": generate_id(20),
"mode": RedirectMode.FLOW,
"target_flow": create_test_flow().flow_uuid,
}
).is_valid(raise_exception=True)
)

View File

@ -1,5 +0,0 @@
"""API URLs"""
from authentik.stages.redirect.api import RedirectStageViewSet
api_urlpatterns = [("stages/redirect", RedirectStageViewSet)]

View File

@ -2801,46 +2801,6 @@
} }
} }
}, },
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_stages_redirect.redirectstage"
},
"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_stages_redirect.redirectstage_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_stages_redirect.redirectstage"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_stages_redirect.redirectstage"
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -3201,46 +3161,6 @@
} }
} }
}, },
{
"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": [
@ -4103,7 +4023,6 @@
"require_authenticated", "require_authenticated",
"require_unauthenticated", "require_unauthenticated",
"require_superuser", "require_superuser",
"require_redirect",
"require_outpost" "require_outpost"
], ],
"title": "Authentication", "title": "Authentication",
@ -4574,7 +4493,6 @@
"authentik.stages.invitation", "authentik.stages.invitation",
"authentik.stages.password", "authentik.stages.password",
"authentik.stages.prompt", "authentik.stages.prompt",
"authentik.stages.redirect",
"authentik.stages.user_delete", "authentik.stages.user_delete",
"authentik.stages.user_login", "authentik.stages.user_login",
"authentik.stages.user_logout", "authentik.stages.user_logout",
@ -4670,7 +4588,6 @@
"authentik_stages_password.passwordstage", "authentik_stages_password.passwordstage",
"authentik_stages_prompt.prompt", "authentik_stages_prompt.prompt",
"authentik_stages_prompt.promptstage", "authentik_stages_prompt.promptstage",
"authentik_stages_redirect.redirectstage",
"authentik_stages_user_delete.userdeletestage", "authentik_stages_user_delete.userdeletestage",
"authentik_stages_user_login.userloginstage", "authentik_stages_user_login.userloginstage",
"authentik_stages_user_logout.userlogoutstage", "authentik_stages_user_logout.userlogoutstage",
@ -4680,7 +4597,6 @@
"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",
@ -6410,7 +6326,6 @@
"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",
@ -6423,7 +6338,6 @@
"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",
@ -6434,7 +6348,6 @@
"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",
@ -6450,7 +6363,6 @@
"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",
@ -6901,10 +6813,6 @@
"authentik_stages_prompt.delete_promptstage", "authentik_stages_prompt.delete_promptstage",
"authentik_stages_prompt.view_prompt", "authentik_stages_prompt.view_prompt",
"authentik_stages_prompt.view_promptstage", "authentik_stages_prompt.view_promptstage",
"authentik_stages_redirect.add_redirectstage",
"authentik_stages_redirect.change_redirectstage",
"authentik_stages_redirect.delete_redirectstage",
"authentik_stages_redirect.view_redirectstage",
"authentik_stages_source.add_sourcestage", "authentik_stages_source.add_sourcestage",
"authentik_stages_source.change_sourcestage", "authentik_stages_source.change_sourcestage",
"authentik_stages_source.delete_sourcestage", "authentik_stages_source.delete_sourcestage",
@ -7068,16 +6976,6 @@
"title": "Krb5 conf", "title": "Krb5 conf",
"description": "Custom krb5.conf to use. Uses the system one by default" "description": "Custom krb5.conf to use. Uses the system one by default"
}, },
"kadmin_type": {
"type": "string",
"enum": [
"MIT",
"Heimdal",
"other"
],
"title": "Kadmin type",
"description": "KAdmin server type"
},
"sync_users": { "sync_users": {
"type": "boolean", "type": "boolean",
"title": "Sync users", "title": "Sync users",
@ -7215,10 +7113,6 @@
"type": "integer", "type": "integer",
"title": "User" "title": "User"
}, },
"source": {
"type": "integer",
"title": "Source"
},
"identifier": { "identifier": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -7261,20 +7155,6 @@
"model_authentik_sources_kerberos.groupkerberossourceconnection": { "model_authentik_sources_kerberos.groupkerberossourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"group": {
"type": "string",
"format": "uuid",
"title": "Group"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": {
"type": "string",
"minLength": 1,
"title": "Identifier"
},
"icon": { "icon": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -7818,14 +7698,6 @@
"model_authentik_sources_oauth.useroauthsourceconnection": { "model_authentik_sources_oauth.useroauthsourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"user": {
"type": "integer",
"title": "User"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": { "identifier": {
"type": "string", "type": "string",
"maxLength": 255, "maxLength": 255,
@ -7876,20 +7748,6 @@
"model_authentik_sources_oauth.groupoauthsourceconnection": { "model_authentik_sources_oauth.groupoauthsourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"group": {
"type": "string",
"format": "uuid",
"title": "Group"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": {
"type": "string",
"minLength": 1,
"title": "Identifier"
},
"icon": { "icon": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -8123,14 +7981,6 @@
"model_authentik_sources_plex.userplexsourceconnection": { "model_authentik_sources_plex.userplexsourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"user": {
"type": "integer",
"title": "User"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": { "identifier": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -8178,20 +8028,6 @@
"model_authentik_sources_plex.groupplexsourceconnection": { "model_authentik_sources_plex.groupplexsourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"group": {
"type": "string",
"format": "uuid",
"title": "Group"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": {
"type": "string",
"minLength": 1,
"title": "Identifier"
},
"icon": { "icon": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -8502,14 +8338,6 @@
"model_authentik_sources_saml.usersamlsourceconnection": { "model_authentik_sources_saml.usersamlsourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"user": {
"type": "integer",
"title": "User"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": { "identifier": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -8552,20 +8380,6 @@
"model_authentik_sources_saml.groupsamlsourceconnection": { "model_authentik_sources_saml.groupsamlsourceconnection": {
"type": "object", "type": "object",
"properties": { "properties": {
"group": {
"type": "string",
"format": "uuid",
"title": "Group"
},
"source": {
"type": "integer",
"title": "Source"
},
"identifier": {
"type": "string",
"minLength": 1,
"title": "Identifier"
},
"icon": { "icon": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -11661,146 +11475,6 @@
} }
} }
}, },
"model_authentik_stages_redirect.redirectstage": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"flow_set": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"slug": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "^[-a-zA-Z0-9_]+$",
"title": "Slug",
"description": "Visible in the URL."
},
"title": {
"type": "string",
"minLength": 1,
"title": "Title",
"description": "Shown as the Title in Flow pages."
},
"designation": {
"type": "string",
"enum": [
"authentication",
"authorization",
"invalidation",
"enrollment",
"unenrollment",
"recovery",
"stage_configuration"
],
"title": "Designation",
"description": "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
},
"policy_engine_mode": {
"type": "string",
"enum": [
"all",
"any"
],
"title": "Policy engine mode"
},
"compatibility_mode": {
"type": "boolean",
"title": "Compatibility mode",
"description": "Enable compatibility mode, increases compatibility with password managers on mobile devices."
},
"layout": {
"type": "string",
"enum": [
"stacked",
"content_left",
"content_right",
"sidebar_left",
"sidebar_right"
],
"title": "Layout"
},
"denied_action": {
"type": "string",
"enum": [
"message_continue",
"message",
"continue"
],
"title": "Denied action",
"description": "Configure what should happen when a flow denies access to a user."
}
},
"required": [
"name",
"slug",
"title",
"designation"
]
},
"title": "Flow set"
},
"keep_context": {
"type": "boolean",
"title": "Keep context"
},
"mode": {
"type": "string",
"enum": [
"static",
"flow"
],
"title": "Mode"
},
"target_static": {
"type": "string",
"title": "Target static"
},
"target_flow": {
"type": "string",
"format": "uuid",
"title": "Target flow"
}
},
"required": []
},
"model_authentik_stages_redirect.redirectstage_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_redirectstage",
"change_redirectstage",
"delete_redirectstage",
"view_redirectstage"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_stages_user_delete.userdeletestage": { "model_authentik_stages_user_delete.userdeletestage": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -12659,7 +12333,6 @@
"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",
@ -12672,7 +12345,6 @@
"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",
@ -12683,7 +12355,6 @@
"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",
@ -12699,7 +12370,6 @@
"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",
@ -13150,10 +12820,6 @@
"authentik_stages_prompt.delete_promptstage", "authentik_stages_prompt.delete_promptstage",
"authentik_stages_prompt.view_prompt", "authentik_stages_prompt.view_prompt",
"authentik_stages_prompt.view_promptstage", "authentik_stages_prompt.view_promptstage",
"authentik_stages_redirect.add_redirectstage",
"authentik_stages_redirect.change_redirectstage",
"authentik_stages_redirect.delete_redirectstage",
"authentik_stages_redirect.view_redirectstage",
"authentik_stages_source.add_sourcestage", "authentik_stages_source.add_sourcestage",
"authentik_stages_source.change_sourcestage", "authentik_stages_source.change_sourcestage",
"authentik_stages_source.delete_sourcestage", "authentik_stages_source.delete_sourcestage",
@ -13312,52 +12978,6 @@
} }
} }
}, },
"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": {

View File

@ -42,21 +42,9 @@ 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

12
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-oidc/v3 v3.11.0
github.com/getsentry/sentry-go v0.30.0 github.com/getsentry/sentry-go v0.30.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.9 github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/runtime v0.28.0
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2 github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024105.3 goauthentik.io/api/v3 v3.2024105.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.24.0 golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.10.0 golang.org/x/sync v0.10.0
@ -45,7 +45,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@ -76,9 +76,9 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

53
go.sum
View File

@ -71,8 +71,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo= github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA= github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -86,8 +86,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-ldap/ldap/v3 v3.4.9 h1:KxX9eO44/MpqPXVVMPJDB+k/35GEePHE/Jfvl7oRMUo= github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
github.com/go-ldap/ldap/v3 v3.4.9/go.mod h1:+CE/4PPOOdEPGTi2B7qXKQOq+pNBvXZtlBNcVZY0AWI= github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2024105.3 h1:Vl1vwPkCtA8hChsxwO3NUI8nupFC7r93jUHvqM+kYVw= goauthentik.io/api/v3 v3.2024105.1 h1:PxOlStLdM+L80ciVJUWZRhf2VQrDVnNMcv+exeQ/qUA=
goauthentik.io/api/v3 v3.2024105.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= goauthentik.io/api/v3 v3.2024105.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -309,12 +309,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -349,9 +347,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -383,11 +378,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -406,9 +400,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -444,20 +435,16 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -466,11 +453,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -516,8 +501,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -14,7 +14,6 @@ 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"`

View File

@ -41,7 +41,6 @@ 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)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-18 13:31+0000\n" "POT-Creation-Date: 2024-11-26 00:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -1873,10 +1873,6 @@ msgstr ""
msgid "Custom krb5.conf to use. Uses the system one by default" msgid "Custom krb5.conf to use. Uses the system one by default"
msgstr "" msgstr ""
#: authentik/sources/kerberos/models.py
msgid "KAdmin server type"
msgstr ""
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Sync users from Kerberos into authentik" msgid "Sync users from Kerberos into authentik"
msgstr "" msgstr ""
@ -2816,7 +2812,7 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" If you did not request a password change, please ignore this email. The " " If you did not request a password change, please ignore this Email. The "
"link above is valid for %(expires)s.\n" "link above is valid for %(expires)s.\n"
" " " "
msgstr "" msgstr ""
@ -2837,7 +2833,7 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
"If you did not request a password change, please ignore this email. The link " "If you did not request a password change, please ignore this Email. The link "
"above is valid for %(expires)s.\n" "above is valid for %(expires)s.\n"
msgstr "" msgstr ""
@ -3102,22 +3098,6 @@ msgstr ""
msgid "Passwords don't match." msgid "Passwords don't match."
msgstr "" msgstr ""
#: authentik/stages/redirect/api.py
msgid "Target URL should be present when mode is Static."
msgstr ""
#: authentik/stages/redirect/api.py
msgid "Target Flow should be present when mode is Flow."
msgstr ""
#: authentik/stages/redirect/models.py
msgid "Redirect Stage"
msgstr ""
#: authentik/stages/redirect/models.py
msgid "Redirect Stages"
msgstr ""
#: authentik/stages/user_delete/models.py #: authentik/stages/user_delete/models.py
msgid "User Delete Stage" msgid "User Delete Stage"
msgstr "" msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-18 13:31+0000\n" "POT-Creation-Date: 2024-11-26 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n" "Last-Translator: Marc Schmitt, 2024\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -2084,10 +2084,6 @@ msgid "Custom krb5.conf to use. Uses the system one by default"
msgstr "" msgstr ""
"krb5.conf personnalisé à utiliser. Utilise celui du système par défault" "krb5.conf personnalisé à utiliser. Utilise celui du système par défault"
#: authentik/sources/kerberos/models.py
msgid "KAdmin server type"
msgstr "Type de serveur KAdmin"
#: authentik/sources/kerberos/models.py #: authentik/sources/kerberos/models.py
msgid "Sync users from Kerberos into authentik" msgid "Sync users from Kerberos into authentik"
msgstr "Synchroniser les utilisateurs Kerberos dans authentik" msgstr "Synchroniser les utilisateurs Kerberos dans authentik"
@ -3109,7 +3105,7 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
" If you did not request a password change, please ignore this email. The link above is valid for %(expires)s.\n" " If you did not request a password change, please ignore this Email. The link above is valid for %(expires)s.\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@ -3133,7 +3129,7 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
"If you did not request a password change, please ignore this email. The link above is valid for %(expires)s.\n" "If you did not request a password change, please ignore this Email. The link above is valid for %(expires)s.\n"
msgstr "" msgstr ""
"\n" "\n"
"Si vous n'avez pas requis de changement de mot de passe, veuillez ignorer cet e-mail. Le lien ci-dessus est valide pendant %(expires)s.\n" "Si vous n'avez pas requis de changement de mot de passe, veuillez ignorer cet e-mail. Le lien ci-dessus est valide pendant %(expires)s.\n"
@ -3438,22 +3434,6 @@ msgstr "Étapes invite"
msgid "Passwords don't match." msgid "Passwords don't match."
msgstr "Les mots de passe ne correspondent pas." msgstr "Les mots de passe ne correspondent pas."
#: authentik/stages/redirect/api.py
msgid "Target URL should be present when mode is Static."
msgstr "L'URL destination doit être présente lorsque le mode est Statique."
#: authentik/stages/redirect/api.py
msgid "Target Flow should be present when mode is Flow."
msgstr "Le flux destination doit être présent lorsque le mode est Flux."
#: authentik/stages/redirect/models.py
msgid "Redirect Stage"
msgstr "Étape de redirection"
#: authentik/stages/redirect/models.py
msgid "Redirect Stages"
msgstr "Étapes de redirection"
#: authentik/stages/user_delete/models.py #: authentik/stages/user_delete/models.py
msgid "User Delete Stage" msgid "User Delete Stage"
msgstr "Étape de suppression utilisateur" msgstr "Étape de suppression utilisateur"

View File

@ -13,16 +13,15 @@
# albanobattistella <albanobattistella@gmail.com>, 2024 # albanobattistella <albanobattistella@gmail.com>, 2024
# Nicola Mersi, 2024 # Nicola Mersi, 2024
# tom max, 2024 # tom max, 2024
# Marc Schmitt, 2024
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-26 00:09+0000\n" "POT-Creation-Date: 2024-11-18 00:09+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n" "Last-Translator: tom max, 2024\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n" "Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -90,9 +89,9 @@ msgid "authentik Export - {date}"
msgstr "Esportazione authentik - {date}" msgstr "Esportazione authentik - {date}"
#: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py #: authentik/blueprints/v1/tasks.py authentik/crypto/tasks.py
#, python-brace-format #, python-format
msgid "Successfully imported {count} files." msgid "Successfully imported %(count)d files."
msgstr "Importato con successo {count} file." msgstr "Importato con successo %(count)d file."
#: authentik/brands/models.py #: authentik/brands/models.py
msgid "" msgid ""
@ -636,7 +635,7 @@ msgstr "Fasi Sorgenti"
#: authentik/events/api/tasks.py #: authentik/events/api/tasks.py
#, python-brace-format #, python-brace-format
msgid "Successfully started task {name}." msgid "Successfully started task {name}."
msgstr "Attività {name} avviata correttamente." msgstr "Attività {nome} avviata correttamente."
#: authentik/events/models.py #: authentik/events/models.py
msgid "Event" msgid "Event"
@ -938,14 +937,14 @@ msgid "Starting full provider sync"
msgstr "Avvio della sincronizzazione completa del provider" msgstr "Avvio della sincronizzazione completa del provider"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of users" msgid "Syncing page %(page)d of users"
msgstr "Sincronizzando pagina {page} degli utenti" msgstr "Sincronizzando pagina %(page)d degli utenti"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-format
msgid "Syncing page {page} of groups" msgid "Syncing page %(page)d of groups"
msgstr "Sincronizzando pagina {page} dei gruppi" msgstr "Sincronizzando pagina %(page)d dei gruppi"
#: authentik/lib/sync/outgoing/tasks.py #: authentik/lib/sync/outgoing/tasks.py
#, python-brace-format #, python-brace-format
@ -1118,10 +1117,10 @@ msgid "Event Matcher Policies"
msgstr "Criteri Corrispondenza Evento" msgstr "Criteri Corrispondenza Evento"
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
#, python-brace-format #, python-format
msgid "Password expired {days} days ago. Please update your password." msgid "Password expired %(days)d days ago. Please update your password."
msgstr "" msgstr ""
"Password scaduta {days} giorni fa. Si prega di aggiornare la password." "Password scaduta %(days)d giorni fa. Si prega di aggiornare la password."
#: authentik/policies/expiry/models.py #: authentik/policies/expiry/models.py
msgid "Password has expired." msgid "Password has expired."
@ -1255,9 +1254,9 @@ msgid "Invalid password."
msgstr "Password invalida." msgstr "Password invalida."
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
#, python-brace-format #, python-format
msgid "Password exists on {count} online lists." msgid "Password exists on %(count)d online lists."
msgstr "Password esistente in {count} lite online." msgstr "Password esistente in %(count)d lite online."
#: authentik/policies/password/models.py #: authentik/policies/password/models.py
msgid "Password is too weak." msgid "Password is too weak."
@ -1384,11 +1383,6 @@ msgstr "Providers LDAP"
msgid "Search full LDAP directory" msgid "Search full LDAP directory"
msgstr "Ricerca completa nella directory LDAP" msgstr "Ricerca completa nella directory LDAP"
#: authentik/providers/oauth2/api/providers.py
#, python-brace-format
msgid "Invalid Regex Pattern: {url}"
msgstr "Modello Regex non valido: {url}"
#: authentik/providers/oauth2/id_token.py #: authentik/providers/oauth2/id_token.py
msgid "Based on the Hashed User ID" msgid "Based on the Hashed User ID"
msgstr "Basato sull'ID utente hashato" msgstr "Basato sull'ID utente hashato"
@ -1434,14 +1428,6 @@ msgid "Each provider has a different issuer, based on the application slug."
msgstr "" msgstr ""
"Ogni provider ha un issuer differente, basato sullo slug dell'applicazione." "Ogni provider ha un issuer differente, basato sullo slug dell'applicazione."
#: authentik/providers/oauth2/models.py
msgid "Strict URL comparison"
msgstr "Confronto URL rigoroso"
#: authentik/providers/oauth2/models.py
msgid "Regular Expression URL matching"
msgstr "Corrispondenza URL espressione regolare"
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "code (Authorization Code Flow)" msgid "code (Authorization Code Flow)"
msgstr "code (Flusso di autorizzazione del codice)" msgstr "code (Flusso di autorizzazione del codice)"
@ -1522,6 +1508,10 @@ msgstr "Client Secret"
msgid "Redirect URIs" msgid "Redirect URIs"
msgstr "URL di reindirizzamento" msgstr "URL di reindirizzamento"
#: authentik/providers/oauth2/models.py
msgid "Enter each URI on a new line."
msgstr "Inserisci ogni URI su una nuova riga."
#: authentik/providers/oauth2/models.py #: authentik/providers/oauth2/models.py
msgid "Include claims in id_token" msgid "Include claims in id_token"
msgstr "Includere le richieste in id_token" msgstr "Includere le richieste in id_token"

188
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]] [[package]]
name = "aiohappyeyeballs" name = "aiohappyeyeballs"
@ -408,13 +408,13 @@ typeguard = ">=2.13.3,<5.0.0"
[[package]] [[package]]
name = "aws-cdk-lib" name = "aws-cdk-lib"
version = "2.173.2" version = "2.172.0"
description = "Version 2 of the AWS Cloud Development Kit library" description = "Version 2 of the AWS Cloud Development Kit library"
optional = false optional = false
python-versions = "~=3.8" python-versions = "~=3.8"
files = [ files = [
{file = "aws_cdk_lib-2.173.2-py3-none-any.whl", hash = "sha256:1b76846669de83e6572e9c46f5708f6ac045d8e710bafb044230f24e722601ef"}, {file = "aws_cdk_lib-2.172.0-py3-none-any.whl", hash = "sha256:960b64af8eb272d2bc80d42dab4748863c2021c39dbc543bb6e7bec0fdafa099"},
{file = "aws_cdk_lib-2.173.2.tar.gz", hash = "sha256:9eb355c4fd5c1aa56317549600baf88dd4d3b520e2081132119b51349ead8c03"}, {file = "aws_cdk_lib-2.172.0.tar.gz", hash = "sha256:4e8cb368256024e2d35874d7ab2e68812177d7990a27b2ceb50c454e8a018533"},
] ]
[package.dependencies] [package.dependencies]
@ -1201,6 +1201,7 @@ files = [
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
@ -1211,6 +1212,7 @@ files = [
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
@ -1271,37 +1273,13 @@ tests = ["django", "hypothesis", "pytest", "pytest-asyncio"]
[[package]] [[package]]
name = "debugpy" name = "debugpy"
version = "1.8.11" version = "1.8.10"
description = "An implementation of the Debug Adapter Protocol for Python" description = "An implementation of the Debug Adapter Protocol for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "debugpy-1.8.11-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd"}, {file = "debugpy-1.8.10-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97aa00af95983887806e06f37e144909d35215d66db74f8b0e9799b4eef40cfd"},
{file = "debugpy-1.8.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f"}, {file = "debugpy-1.8.10.tar.gz", hash = "sha256:ee4ed903cbeb14ee1839549f953af519ffa512598ec987b2051f9c868e2249a8"},
{file = "debugpy-1.8.11-cp310-cp310-win32.whl", hash = "sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737"},
{file = "debugpy-1.8.11-cp310-cp310-win_amd64.whl", hash = "sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1"},
{file = "debugpy-1.8.11-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296"},
{file = "debugpy-1.8.11-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1"},
{file = "debugpy-1.8.11-cp311-cp311-win32.whl", hash = "sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9"},
{file = "debugpy-1.8.11-cp311-cp311-win_amd64.whl", hash = "sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e"},
{file = "debugpy-1.8.11-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308"},
{file = "debugpy-1.8.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768"},
{file = "debugpy-1.8.11-cp312-cp312-win32.whl", hash = "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b"},
{file = "debugpy-1.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1"},
{file = "debugpy-1.8.11-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3"},
{file = "debugpy-1.8.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e"},
{file = "debugpy-1.8.11-cp313-cp313-win32.whl", hash = "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28"},
{file = "debugpy-1.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1"},
{file = "debugpy-1.8.11-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db"},
{file = "debugpy-1.8.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0"},
{file = "debugpy-1.8.11-cp38-cp38-win32.whl", hash = "sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280"},
{file = "debugpy-1.8.11-cp38-cp38-win_amd64.whl", hash = "sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5"},
{file = "debugpy-1.8.11-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458"},
{file = "debugpy-1.8.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851"},
{file = "debugpy-1.8.11-cp39-cp39-win32.whl", hash = "sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7"},
{file = "debugpy-1.8.11-cp39-cp39-win_amd64.whl", hash = "sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0"},
{file = "debugpy-1.8.11-py2.py3-none-any.whl", hash = "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920"},
{file = "debugpy-1.8.11.tar.gz", hash = "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57"},
] ]
[[package]] [[package]]
@ -1467,13 +1445,13 @@ django = ">=3"
[[package]] [[package]]
name = "django-pglock" name = "django-pglock"
version = "1.7.1" version = "1.7.0"
description = "Postgres locking routines and lock table access." description = "Postgres locking routines and lock table access."
optional = false optional = false
python-versions = "<4,>=3.9.0" python-versions = "<4,>=3.9.0"
files = [ files = [
{file = "django_pglock-1.7.1-py3-none-any.whl", hash = "sha256:15db418fb56bee37fc8707038495b5085af9b8c203ebfa300202572127bdb3f0"}, {file = "django_pglock-1.7.0-py3-none-any.whl", hash = "sha256:4e28fa19cae96f072f3b74a368519442c5413b1ce72f75f816b77dd567d456df"},
{file = "django_pglock-1.7.1.tar.gz", hash = "sha256:69050bdb522fd34585d49bb8a4798dbfbab9ec4754dd1927b1b9eef2ec0edadf"}, {file = "django_pglock-1.7.0.tar.gz", hash = "sha256:180da6d3067b1dcb46b5e37745ee32fe0d8d5976c53bc8912dcf2a46e5000b6a"},
] ]
[package.dependencies] [package.dependencies]
@ -1922,13 +1900,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]] [[package]]
name = "google-api-python-client" name = "google-api-python-client"
version = "2.155.0" version = "2.154.0"
description = "Google API Client Library for Python" description = "Google API Client Library for Python"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "google_api_python_client-2.155.0-py2.py3-none-any.whl", hash = "sha256:83fe9b5aa4160899079d7c93a37be306546a17e6686e2549bcc9584f1a229747"}, {file = "google_api_python_client-2.154.0-py2.py3-none-any.whl", hash = "sha256:a521bbbb2ec0ba9d6f307cdd64ed6e21eeac372d1bd7493a4ab5022941f784ad"},
{file = "google_api_python_client-2.155.0.tar.gz", hash = "sha256:25529f89f0d13abcf3c05c089c423fb2858ac16e0b3727543393468d0d7af67c"}, {file = "google_api_python_client-2.154.0.tar.gz", hash = "sha256:1b420062e03bfcaa1c79e2e00a612d29a6a934151ceb3d272fe150a656dc8f17"},
] ]
[package.dependencies] [package.dependencies]
@ -3463,13 +3441,13 @@ files = [
[[package]] [[package]]
name = "pdoc" name = "pdoc"
version = "15.0.1" version = "15.0.0"
description = "API Documentation for Python Projects" description = "API Documentation for Python Projects"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
files = [ files = [
{file = "pdoc-15.0.1-py3-none-any.whl", hash = "sha256:fd437ab8eb55f9b942226af7865a3801e2fb731665199b74fd9a44737dbe20f9"}, {file = "pdoc-15.0.0-py3-none-any.whl", hash = "sha256:151b0187a25eaf827099e981d6dbe3a4f68aeb18d0d637c24edcab788d5540f1"},
{file = "pdoc-15.0.1.tar.gz", hash = "sha256:3b08382c9d312243ee6c2a1813d0ff517a6ab84d596fa2c6c6b5255b17c3d666"}, {file = "pdoc-15.0.0.tar.gz", hash = "sha256:b761220d3ba129cd87e6da1bb7b62c8e799973ab9c595de7ba1a514850d86da5"},
] ]
[package.dependencies] [package.dependencies]
@ -4258,48 +4236,48 @@ cli = ["click (>=5.0)"]
[[package]] [[package]]
name = "python-kadmin-rs" name = "python-kadmin-rs"
version = "0.5.2" version = "0.4.0"
description = "Python interface to the Kerberos administration interface (kadm5)" description = "Python interface to the Kerberos administration interface (kadm5)"
optional = false optional = false
python-versions = "<3.14,>=3.9" python-versions = "<3.14,>=3.9"
files = [ files = [
{file = "python_kadmin_rs-0.5.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1399e507467881882275eb822caee73f7eb509d25c25af406e91a75221a08ec9"}, {file = "python_kadmin_rs-0.4.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b5a5abda2c60961c1d456c920dd3a3053e615a6f1f5703606953be8dfdddef2a"},
{file = "python_kadmin_rs-0.5.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:86c5f0c799ea903fcc7d67ed47ce9080ea639c8468483c4d6e3a854ab268c959"}, {file = "python_kadmin_rs-0.4.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:57004e7aa52d95a76b0c6d920526f68b45206c51d8d8520d94511727c7ccbad0"},
{file = "python_kadmin_rs-0.5.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:67e8805cbdc75e9d0a88378f30acf0bed34fcca5d2130c4d6a613e57676123a7"}, {file = "python_kadmin_rs-0.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:66f01443b6376494f67d727663600a413a701852a60c724a3cd728758455f59c"},
{file = "python_kadmin_rs-0.5.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:244fca7d8ca7793729b8a01ae9f2a3c5931fca6bc11d7f3b67fa95297146cd8e"}, {file = "python_kadmin_rs-0.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:2af5a50554753ba62ebc979b7767b43e072cff5b56dc0a1f09970fa9105cf55a"},
{file = "python_kadmin_rs-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:034bed577e20cdf4682f4d591ec68d51a44e85a101f2d905c3728143390d93f1"}, {file = "python_kadmin_rs-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1720b3b9dc156be08e36b7f3492431d2b475b3ecbfa403d73d6e1fcc5ac70bc4"},
{file = "python_kadmin_rs-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:048e73490278f74510ac7f19a11ca7860c88863f55f2c79a47c875fc174bb2aa"}, {file = "python_kadmin_rs-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66a64d615d28dbf17ad8822d75f6a4685f7db7ddef9ad9d69053dcfab592e4ec"},
{file = "python_kadmin_rs-0.5.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:dc580a38397dcdd2021127861c0d35a0c85e556644673387e40331f3fb402dc6"}, {file = "python_kadmin_rs-0.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:56ce2b57fbb3b0e7d0e69bd9ce3e7a165ed018ac4c4d60b259f50e68a6a3bb00"},
{file = "python_kadmin_rs-0.5.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:abb4df1a35bb177a7a9d2aee82d99d2285240368e6a1784c5066003872374679"}, {file = "python_kadmin_rs-0.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:1aabecd407afd70fca21208f35ea6d2101fb27922e96c5ceed7fcaa6c44359b0"},
{file = "python_kadmin_rs-0.5.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:22fd617f126f5dae2e17c4770cea8ffeca7196885508d922798bbdc9368606f1"}, {file = "python_kadmin_rs-0.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e53eb9914eb6542618ec5da67c51e943eb724f76f186d88ae591bd8fde01345a"},
{file = "python_kadmin_rs-0.5.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:83574caf964140e87df04a1d97d84b1dd1d60395cae430429b8c1b78a1f5e6de"}, {file = "python_kadmin_rs-0.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c51115155ff1001ab3a0826a3de753927ea1373828e5432bc0eede4ec88c5c72"},
{file = "python_kadmin_rs-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01fc8c3cf707bbe011610107a6803ea2cb9025f4152931f40a39dc8b8d29d42a"}, {file = "python_kadmin_rs-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:547223f156baa8ef1236c7b3a55bc13506beada6147679f4a73dd1de5e809d30"},
{file = "python_kadmin_rs-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bb5091dbeb0159f95292768b5dc7cce057a29339d5f9c085921a8f16baa3cb32"}, {file = "python_kadmin_rs-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:951ca2b9b3019cf82c5e1882d1cec6e28bbf2d900d2b8022aac23a3e65a4ca7d"},
{file = "python_kadmin_rs-0.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:0ece4d210b70f7810a8d909f32872bb47602f8c9ca00289fb8d34a6ee79f5b19"}, {file = "python_kadmin_rs-0.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b1c1a0b63ec3bd1a023feb094e1c6a93202237416d0783d4677be2b858fe6154"},
{file = "python_kadmin_rs-0.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:d351b5793d8340e9101bdd2684dc6e84156e37af910140530e762d2d92905819"}, {file = "python_kadmin_rs-0.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b27c16187dd24b820c966f03f889c140d0a55f547158fdc5bc2ecb4eb7e94fbe"},
{file = "python_kadmin_rs-0.5.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:13e13c0487dfb9f6986fc6a11e8526875c935aa9bbdf9514049f2c5b5b5cdae7"}, {file = "python_kadmin_rs-0.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fab810574fd54b715806104400a5c105879005597bc043469d506cb8e1e633c4"},
{file = "python_kadmin_rs-0.5.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:40fd1663c47bcada61e0bb7c681a1518b9fd1d17f03e3193bdfb6313e5afa6d0"}, {file = "python_kadmin_rs-0.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:377ffa81264b115fafd2b4a83aab990a138a3684b90a133bc3a6c4081829c358"},
{file = "python_kadmin_rs-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:945a9314e47d930825e46f532341ea1f595a7a78a9d75866e5564bd28cd4b6af"}, {file = "python_kadmin_rs-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b9a3909592404ac0483b3a5d584466198b5e17e370be3e221ff19c4cec97ce4"},
{file = "python_kadmin_rs-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:40cc14b24028a23a796fa5a53e6236c72c90247be803c6a8976f6e758b377f67"}, {file = "python_kadmin_rs-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e6050fdaa638585046b8579867d3540f99efbf24dc10715ac05bae6ca9bbbffd"},
{file = "python_kadmin_rs-0.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:cd5b032fb5c8d609d38bc417e1e5405885d153d39742bbac6514af28b8930a74"}, {file = "python_kadmin_rs-0.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e36c868960619ed4df0e69f53ff9458f661c1a5fbc627554cc7777231e9e69bd"},
{file = "python_kadmin_rs-0.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6f904a912ea04cd285b0d33107d6e68c904b046fa5bd7555c48986ee4ef139f7"}, {file = "python_kadmin_rs-0.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3f7692eb90ddacc353a5ed3d53fe0bc62df4132b30158e1c9a2bf24340a6929a"},
{file = "python_kadmin_rs-0.5.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4234bc17dff770cbc32c14b22659651f4c9a882086cc19be7467f4755357f756"}, {file = "python_kadmin_rs-0.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7a551f8010e47413513cc19e0001dfed9178f5de509c4590b02584e0387df55c"},
{file = "python_kadmin_rs-0.5.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bb3abbf9a0a91a9205cef8ff4fb45bdeb7ee773d2eda67e3a8c01a2f9f561b7f"}, {file = "python_kadmin_rs-0.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e8623866a0324823af5edc2da6a6e90cb8a0d2ecbeb80f9a04014cc18f1c182f"},
{file = "python_kadmin_rs-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6503feef30cb59fd79b573cde5a2e9f892e5b89ffdb78e78db21815f67a14b80"}, {file = "python_kadmin_rs-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:223807b9108723d4f47d3243f6256f4026be0ea7ccbb356807d97a469a8bc628"},
{file = "python_kadmin_rs-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6bfe54524573ccf4424344af88e57804399061f16aaf2db1965cafce79f3c76"}, {file = "python_kadmin_rs-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e7b8f6a2b183c862b94462251537d508332c82d2c4dec1699875245041c4a684"},
{file = "python_kadmin_rs-0.5.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:c953f2cdfd92217d8ae4d3dc0374305ed0bd21cbfa7de50c5f7dfc53c44eaa7a"}, {file = "python_kadmin_rs-0.4.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:dee6325628edc33eaf217268b521b0923f519fdb7f5ac81dcfb97c9574fb3599"},
{file = "python_kadmin_rs-0.5.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:2632b02116651a23e3b5b7fce87f939067918f41b9d542af21ee09d964d41bfd"}, {file = "python_kadmin_rs-0.4.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:59b5db1d0381fbfb0b9ff2f79949abae6c645ccbd7c8c72a9b932fc0eab1d9b0"},
{file = "python_kadmin_rs-0.5.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8b8a4a042179e3682a826a5c4bc6ee39055c6133d13d5415d6be2bb0e1d79e4a"}, {file = "python_kadmin_rs-0.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:45fd65c49e0c64968d11eb7f6b93a9a09788967ca667e554f35fea467ea67f1e"},
{file = "python_kadmin_rs-0.5.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ed6eafd0f9606d1d554aae7b9f5ebae681ef0dc33b08b0affb363fa65b367ad6"}, {file = "python_kadmin_rs-0.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:fc49b75be7d032f5a37a53b777267b81070220b9d14777374f159c5b1f64686a"},
{file = "python_kadmin_rs-0.5.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:81df51e55e45fee08890f85230a33ddb066a7116ef8bdbe9ce854f3b95ed4c2d"}, {file = "python_kadmin_rs-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fafb2e57fbc82e27c26c5450669846e02afbf6b4065127c4396fa2c21ec31c42"},
{file = "python_kadmin_rs-0.5.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb0954ff796e2cb5813665575ecd8f51df28dfeb52a81601516b056288418a94"}, {file = "python_kadmin_rs-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:59b622b7396922748b2463ad0a682b6f6a6887f3eee720eeda8a57bed6370555"},
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:6fcb5f5c49e96e8ec6c5096c701871978bd2a3a7ef4ebdcbc3abb6a05aa8a5b7"}, {file = "python_kadmin_rs-0.4.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:40ce8ff6dcc1bd82f34e7aca611e299a4ca51f28e5cb8772ba7d38532c9564d9"},
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8437ccce96206c26eb877ddfd7f14c8d8fec0a7cc9344e7dbf982637cd4c28ac"}, {file = "python_kadmin_rs-0.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ef8030f282bdcbb2a771699e238b0f555336ddd626d4562ef3e2e17abc31c3b"},
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:beb2619c27b2f079d7d0c67f3e998712f236808f0c2c0a5389f07d1977246762"}, {file = "python_kadmin_rs-0.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:617292305b3fde5e6b009b70ae8fd6cc5c7a962732558cffbdc27fca157ce574"},
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cecaeebe7acf78e17730b1fa8e5be7aae0e9052c347fc35b1a2d3f77fd69bfe1"}, {file = "python_kadmin_rs-0.4.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:6fe1d0a03d0a0a75296902ad95b6639372eed93d16422f33572d23b0b144ce64"},
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e14f3ba4017b8266f6db31aba4bf931593373b9ea8a17b5f9cc05cd2e3674a8b"}, {file = "python_kadmin_rs-0.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7fef94fe96687b9c6eb9bf670afda91f24c62fb2bc2f80394e0f2f31474494a5"},
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9b2692f6e07461703ac1d20c590ffd5e980d918cdb19c95d875e5f1cf1df397"}, {file = "python_kadmin_rs-0.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:627dff4aa5c222fc83cbb5312362bf80c68b515afd7027b27d763916e0adeb39"},
{file = "python_kadmin_rs-0.5.2.tar.gz", hash = "sha256:8ff0c8cc8f2a10ce20ae0cf1dd5b2d5569e47d1d54cf53c4fbc95f9120e91bd8"}, {file = "python_kadmin_rs-0.4.0.tar.gz", hash = "sha256:d32befeaa68dbaac077b565f5a47a23cba6e142190c0d521e595b30de8587efa"},
] ]
[[package]] [[package]]
@ -4630,29 +4608,29 @@ pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.8.3" version = "0.8.2"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, {file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"},
{file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, {file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"},
{file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, {file = "ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, {file = "ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, {file = "ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, {file = "ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, {file = "ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, {file = "ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22"},
{file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, {file = "ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1"},
{file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, {file = "ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea"},
{file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, {file = "ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8"},
{file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, {file = "ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5"},
] ]
[[package]] [[package]]
@ -5087,13 +5065,13 @@ wsproto = ">=0.14"
[[package]] [[package]]
name = "twilio" name = "twilio"
version = "9.4.1" version = "9.3.8"
description = "Twilio API client and TwiML generator" description = "Twilio API client and TwiML generator"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
{file = "twilio-9.4.1-py2.py3-none-any.whl", hash = "sha256:2447e041cec11167d7765aaa62ab1dae3b82b712245ca9a966096acd8b9f426f"}, {file = "twilio-9.3.8-py2.py3-none-any.whl", hash = "sha256:fce1f629295285d583dbe1d615f114a77aab25a654ba569bb18d304d31e9ca3b"},
{file = "twilio-9.4.1.tar.gz", hash = "sha256:e24c640696ccc726bba14160951da3cfc6b4bcb772fdcb3e8c16dc3cc851ef12"}, {file = "twilio-9.3.8.tar.gz", hash = "sha256:93a80639db711e58915cfdf772da6274b005ef86f5d2f6092433cb3d53a25303"},
] ]
[package.dependencies] [package.dependencies]
@ -5258,13 +5236,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.34.0" version = "0.32.1"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.8"
files = [ files = [
{file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, {file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
{file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, {file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
] ]
[package.dependencies] [package.dependencies]
@ -5934,4 +5912,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = "~3.12"
content-hash = "38089ad25be7638c118f4b503ad2f8495c941667f5485efe60b2bbdb14d6f44c" content-hash = "f6e8316415a23b165130d63a7ea311b257f65f5478ad85b0d38ac72fb89bc1c4"

View File

@ -131,7 +131,7 @@ pydantic-scim = "*"
pyjwt = "*" pyjwt = "*"
pyrad = "*" pyrad = "*"
python = "~3.12" python = "~3.12"
python-kadmin-rs = "0.5.2" python-kadmin-rs = "0.4.0"
pyyaml = "*" pyyaml = "*"
requests-oauthlib = "*" requests-oauthlib = "*"
scim2-filter-parser = "*" scim2-filter-parser = "*"

1071
schema.yml

File diff suppressed because it is too large Load Diff

View File

@ -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-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 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
# 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

View File

@ -23,14 +23,12 @@ 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;

View File

@ -26,7 +26,6 @@ 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

View File

@ -192,5 +192,5 @@ class TestSourceOAuth2(SeleniumTestCase):
results = body_json["results"] results = body_json["results"]
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)
connection = results[0] connection = results[0]
self.assertEqual(connection["source_obj"]["slug"], self.slug) self.assertEqual(connection["source"]["slug"], self.slug)
self.assertEqual(connection["user"], self.user.pk) self.assertEqual(connection["user"], self.user.pk)

8
web/package-lock.json generated
View File

@ -23,7 +23,7 @@
"@floating-ui/dom": "^1.6.11", "@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7", "@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.10.5-1734528783", "@goauthentik/api": "^2024.10.5-1733854821",
"@lit-labs/ssr": "^3.2.2", "@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2", "@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2", "@lit/localize": "^0.12.2",
@ -1775,9 +1775,9 @@
} }
}, },
"node_modules/@goauthentik/api": { "node_modules/@goauthentik/api": {
"version": "2024.10.5-1734528783", "version": "2024.10.5-1733854821",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.10.5-1734528783.tgz", "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.10.5-1733854821.tgz",
"integrity": "sha512-jYo1hpBcsmyL3r8lnjWX1BVQUtVnwEM9c/TjNyxQX47TEdYEdXnyO037KGJxnGNQS5Dm+gCsKaExI/a18bNZeQ==" "integrity": "sha512-WLZxl56j0b007b/Mv3xhWZS5oWCOeCDnn1fq4I3xN/Esm76uuAOizXN/Cpv6QMLzcwoMKTE6d5L4GUDIy3jwZQ=="
}, },
"node_modules/@goauthentik/web": { "node_modules/@goauthentik/web": {
"resolved": "", "resolved": "",

View File

@ -11,7 +11,7 @@
"@floating-ui/dom": "^1.6.11", "@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7", "@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2024.10.5-1734528783", "@goauthentik/api": "^2024.10.5-1733854821",
"@lit-labs/ssr": "^3.2.2", "@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2", "@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2", "@lit/localize": "^0.12.2",

View File

@ -1,7 +1,6 @@
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";
@ -302,28 +301,6 @@ 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")}"

View File

@ -1,89 +0,0 @@
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;
}
}

View File

@ -1,152 +0,0 @@
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;
}
}

View File

@ -189,28 +189,21 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
?selected=${this.instance?.authentication === ?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireUnauthenticated} AuthenticationEnum.RequireUnauthenticated}
> >
${msg("Require no authentication")} ${msg("Require no authentication.")}
</option> </option>
<option <option
value=${AuthenticationEnum.RequireSuperuser} value=${AuthenticationEnum.RequireSuperuser}
?selected=${this.instance?.authentication === ?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireSuperuser} AuthenticationEnum.RequireSuperuser}
> >
${msg("Require superuser")} ${msg("Require superuser.")}
</option>
<option
value=${AuthenticationEnum.RequireRedirect}
?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireRedirect}
>
${msg("Require being redirected from another flow")}
</option> </option>
<option <option
value=${AuthenticationEnum.RequireOutpost} value=${AuthenticationEnum.RequireOutpost}
?selected=${this.instance?.authentication === ?selected=${this.instance?.authentication ===
AuthenticationEnum.RequireOutpost} AuthenticationEnum.RequireOutpost}
> >
${msg("Require Outpost (flow can only be executed from an outpost)")} ${msg("Require Outpost (flow can only be executed from an outpost).")}
</option> </option>
</select> </select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">

View File

@ -1,11 +1,6 @@
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";
@ -18,7 +13,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, nothing } from "lit"; import { TemplateResult, html } 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";
@ -29,25 +24,14 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
@property() @property()
target?: string; target?: string;
@property({ type: Array }) @property({ type: Boolean })
allowedTypes: PolicyBindingCheckTarget[] = [ policyOnly = false;
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()),
@ -58,7 +42,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(this.allowedTypesLabel), new TableColumn(msg("Policy / User / Group")),
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")),
@ -137,7 +121,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
return [ return [
{ key: msg("Order"), value: item.order.toString() }, { key: msg("Order"), value: item.order.toString() },
{ {
key: this.allowedTypesLabel, key: msg("Policy / User / Group"),
value: this.getPolicyUserGroupRowLabel(item), value: this.getPolicyUserGroupRowLabel(item),
}, },
]; ];
@ -172,9 +156,8 @@ 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">
@ -200,8 +183,7 @@ 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)}
.allowedTypes=${this.allowedTypes} ?policyOnly=${this.policyOnly}
.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">
@ -214,25 +196,22 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
} }
renderToolbar(): TemplateResult { renderToolbar(): TemplateResult {
return html`${this.allowedTypes.includes(PolicyBindingCheckTarget.policy) return html`<ak-policy-wizard
? html`<ak-policy-wizard createText=${msg("Create and bind Policy")}
createText=${msg("Create and bind Policy")} ?showBindingPage=${true}
?showBindingPage=${true} bindingTarget=${ifDefined(this.target)}
bindingTarget=${ifDefined(this.target)} ></ak-policy-wizard>
></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)}
.allowedTypes=${this.allowedTypes} ?policyOnly=${this.policyOnly}
.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(str`Bind existing ${this.allowedTypesLabel}`)} ${msg("Bind existing policy/group/user")}
</button> </button>
</ak-forms-modal> `; </ak-forms-modal> `;
} }

View File

@ -1,7 +1,3 @@
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";
@ -11,7 +7,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, nothing } from "lit"; import { CSSResult } 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";
@ -29,7 +25,11 @@ import {
User, User,
} from "@goauthentik/api"; } from "@goauthentik/api";
export type PolicyBindingNotice = { type: PolicyBindingCheckTarget; notice: string }; enum target {
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 = PolicyBindingCheckTarget.policy; this.policyGroupUser = target.policy;
} }
if (binding?.groupObj) { if (binding?.groupObj) {
this.policyGroupUser = PolicyBindingCheckTarget.group; this.policyGroupUser = target.group;
} }
if (binding?.userObj) { if (binding?.userObj) {
this.policyGroupUser = PolicyBindingCheckTarget.user; this.policyGroupUser = target.user;
} }
this.defaultOrder = await this.getOrder(); this.defaultOrder = await this.getOrder();
return binding; return binding;
@ -54,17 +54,10 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
targetPk?: string; targetPk?: string;
@state() @state()
policyGroupUser: PolicyBindingCheckTarget = PolicyBindingCheckTarget.policy; policyGroupUser: target = target.policy;
@property({ type: Array }) @property({ type: Boolean })
allowedTypes: PolicyBindingCheckTarget[] = [ policyOnly = false;
PolicyBindingCheckTarget.group,
PolicyBindingCheckTarget.user,
PolicyBindingCheckTarget.policy,
];
@property({ type: Array })
typeNotices: PolicyBindingNotice[] = [];
@state() @state()
defaultOrder = 0; defaultOrder = 0;
@ -81,26 +74,20 @@ 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 PolicyBindingCheckTarget.policy: case target.policy:
data.user = null; data.user = null;
data.group = null; data.group = null;
break; break;
case PolicyBindingCheckTarget.group: case target.group:
data.policy = null; data.policy = null;
data.user = null; data.user = null;
break; break;
case PolicyBindingCheckTarget.user: case target.user:
data.policy = null; data.policy = null;
data.group = null; data.group = null;
break; break;
@ -135,18 +122,13 @@ 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: PolicyBindingCheckTarget }>) => { @ak-toggle=${(ev: CustomEvent<{ value: target }>) => {
this.policyGroupUser = ev.detail.value; this.policyGroupUser = ev.detail.value;
}} }}
> >
${Object.keys(PolicyBindingCheckTarget).map((ct) => { <option value=${target.policy}>${msg("Policy")}</option>
if (this.allowedTypes.includes(ct as PolicyBindingCheckTarget)) { <option value=${target.group}>${msg("Group")}</option>
return html`<option value=${ct}> <option value=${target.user}>${msg("User")}</option>
${PolicyBindingCheckTargetToLabel(ct as PolicyBindingCheckTarget)}
</option>`;
}
return nothing;
})}
</ak-toggle-group>`; </ak-toggle-group>`;
} }
@ -157,7 +139,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 !== PolicyBindingCheckTarget.policy} ?hidden=${this.policyGroupUser !== target.policy}
> >
<ak-search-select <ak-search-select
.groupBy=${(items: Policy[]) => { .groupBy=${(items: Policy[]) => {
@ -187,16 +169,11 @@ 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 !== PolicyBindingCheckTarget.group} ?hidden=${this.policyGroupUser !== target.group}
> >
<ak-search-select <ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => { .fetchObjects=${async (query?: string): Promise<Group[]> => {
@ -224,16 +201,18 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
?blankable=${true} ?blankable=${true}
> >
</ak-search-select> </ak-search-select>
${this.typeNotices ${this.policyOnly
.filter(({ type }) => type === PolicyBindingCheckTarget.group) ? html`<p class="pf-c-form__helper-text">
.map((msg) => { ${msg(
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`; "Group mappings can only be checked if a user is already logged in when trying to access this source.",
})} )}
</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 !== PolicyBindingCheckTarget.user} ?hidden=${this.policyGroupUser !== target.user}
> >
<ak-search-select <ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => { .fetchObjects=${async (query?: string): Promise<User[]> => {
@ -261,11 +240,13 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
?blankable=${true} ?blankable=${true}
> >
</ak-search-select> </ak-search-select>
${this.typeNotices ${this.policyOnly
.filter(({ type }) => type === PolicyBindingCheckTarget.user) ? html`<p class="pf-c-form__helper-text">
.map((msg) => { ${msg(
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`; "User mappings can only be checked if a user is already logged in when trying to access this source.",
})} )}
</p>`
: html``}
</ak-form-element-horizontal> </ak-form-element-horizontal>
</div> </div>
</div> </div>

View File

@ -1,18 +0,0 @@
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");
}
}

View File

@ -27,7 +27,6 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { import {
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum, GroupMatchingModeEnum,
KadminTypeEnum,
KerberosSource, KerberosSource,
KerberosSourceRequest, KerberosSourceRequest,
SourcesApi, SourcesApi,
@ -216,34 +215,6 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
<ak-form-group .expanded=${false}> <ak-form-group .expanded=${false}>
<span slot="header"> ${msg("Sync connection settings")} </span> <span slot="header"> ${msg("Sync connection settings")} </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("KAdmin type")}
?required=${true}
name="kadminType"
>
<ak-radio
.options=${[
{
label: "MIT",
value: KadminTypeEnum.Mit,
default: true,
description: html`${msg("MIT krb5 kadmin")}`,
},
{
label: "Heimdal",
value: KadminTypeEnum.Heimdal,
description: html`${msg("Heimdal kadmin")}`,
},
{
label: msg("Other"),
value: KadminTypeEnum.Other,
description: html`${msg("Other type of kadmin")}`,
},
]}
.value=${this.instance?.kadminType}
>
</ak-radio>
</ak-form-element-horizontal>
<ak-text-input <ak-text-input
name="syncPrincipal" name="syncPrincipal"
label=${msg("Sync principal")} label=${msg("Sync principal")}

View File

@ -2,7 +2,6 @@ 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";
@ -241,10 +240,7 @@ export class OAuthSourceViewPage extends AKElement {
)} )}
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<ak-bound-policies-list <ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
.target=${this.source.pk}
.typeNotices=${sourceBindingTypeNotices()}
>
</ak-bound-policies-list> </ak-bound-policies-list>
</div> </div>
</div> </div>

View File

@ -1,7 +1,6 @@
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";
@ -131,10 +130,7 @@ export class PlexSourceViewPage extends AKElement {
)} )}
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<ak-bound-policies-list <ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
.target=${this.source.pk}
.typeNotices=${sourceBindingTypeNotices()}
>
</ak-bound-policies-list> </ak-bound-policies-list>
</div> </div>
</div> </div>

View File

@ -1,7 +1,6 @@
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";
@ -208,10 +207,7 @@ export class SAMLSourceViewPage extends AKElement {
)} )}
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<ak-bound-policies-list <ak-bound-policies-list .target=${this.source.pk} ?policyOnly=${true}>
.target=${this.source.pk}
.typeNotices=${sourceBindingTypeNotices()}
>
</ak-bound-policies-list> </ak-bound-policies-list>
</div> </div>
</div> </div>

View File

@ -1,6 +1,3 @@
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 {
@ -14,20 +11,3 @@ 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.",
),
},
];
}

View File

@ -17,7 +17,6 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm"; import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm"; import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm"; import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/redirect/RedirectStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm"; import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; import "@goauthentik/admin/stages/user_login/UserLoginStageForm";

View File

@ -15,7 +15,6 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm"; import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm"; import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm"; import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/redirect/RedirectStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm"; import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; import "@goauthentik/admin/stages/user_login/UserLoginStageForm";

View File

@ -83,13 +83,13 @@ export class ConsentStageForm extends BaseStageForm<ConsentStage> {
value=${ConsentStageModeEnum.Permanent} value=${ConsentStageModeEnum.Permanent}
?selected=${this.instance?.mode === ConsentStageModeEnum.Permanent} ?selected=${this.instance?.mode === ConsentStageModeEnum.Permanent}
> >
${msg("Consent given lasts indefinitely")} ${msg("Consent given last indefinitely")}
</option> </option>
<option <option
value=${ConsentStageModeEnum.Expiring} value=${ConsentStageModeEnum.Expiring}
?selected=${this.instance?.mode === ConsentStageModeEnum.Expiring} ?selected=${this.instance?.mode === ConsentStageModeEnum.Expiring}
> >
${msg("Consent expires")} ${msg("Consent expires.")}
</option> </option>
</select> </select>
</ak-form-element-horizontal> </ak-form-element-horizontal>

View File

@ -92,8 +92,8 @@ export class PromptStageForm extends BaseStageForm<PromptStage> {
<ak-dual-select-dynamic-selected <ak-dual-select-dynamic-selected
.provider=${policiesProvider} .provider=${policiesProvider}
.selector=${policiesSelector(this.instance?.validationPolicies)} .selector=${policiesSelector(this.instance?.validationPolicies)}
available-label="${msg("Available Policies")}" available-label="${msg("Available Fields")}"
selected-label="${msg("Selected Policies")}" selected-label="${msg("Selected Fields")}"
></ak-dual-select-dynamic-selected> ></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg(

View File

@ -1,145 +0,0 @@
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import {
Flow,
FlowsApi,
FlowsInstancesListRequest,
RedirectStage,
RedirectStageModeEnum,
StagesApi,
} from "@goauthentik/api";
@customElement("ak-stage-redirect-form")
export class RedirectStageForm extends BaseStageForm<RedirectStage> {
@property({ type: String })
mode: string = RedirectStageModeEnum.Static;
loadInstance(pk: string): Promise<RedirectStage> {
return new StagesApi(DEFAULT_CONFIG)
.stagesRedirectRetrieve({
stageUuid: pk,
})
.then((stage) => {
this.mode = stage.mode ?? RedirectStageModeEnum.Static;
return stage;
});
}
async send(data: RedirectStage): Promise<RedirectStage> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesRedirectUpdate({
stageUuid: this.instance.pk || "",
redirectStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesRedirectCreate({
redirectStageRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`<span>
${msg("Redirect the user to another flow, potentially with all gathered context")}
</span>
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${this.instance?.name ?? ""}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group expanded>
<span slot="header"> ${msg("Stage-specific settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
<select
class="pf-c-form-control"
@change=${(ev: Event) => {
const target = ev.target as HTMLSelectElement;
this.mode = target.selectedOptions[0].value;
}}
>
<option
value=${RedirectStageModeEnum.Static}
?selected=${this.instance?.mode === RedirectStageModeEnum.Static}
>
${msg("Static")}
</option>
<option
value=${RedirectStageModeEnum.Flow}
?selected=${this.instance?.mode === RedirectStageModeEnum.Flow}
>
${msg("Flow")}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${this.mode !== RedirectStageModeEnum.Static}
label=${msg("Target URL")}
name="targetStatic"
required
>
<input
type="text"
value="${this.instance?.targetStatic ?? ""}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${msg("Redirect the user to a static URL.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
?hidden=${this.mode !== RedirectStageModeEnum.Flow}
label=${msg("Target Flow")}
name="targetFlow"
required
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
const args: FlowsInstancesListRequest = {
ordering: "slug",
};
if (query !== undefined) {
args.search = query;
}
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(
args,
);
return flows.results;
}}
.renderElement=${(flow: Flow): string => RenderFlowOption(flow)}
.renderDescription=${(flow: Flow): TemplateResult => html`${flow.name}`}
.value=${(flow: Flow | undefined): string | undefined => flow?.pk}
.selected=${(flow: Flow): boolean =>
this.instance?.targetFlow === flow.pk}
blankable
>
</ak-search-select>
<p class="pf-c-form__helper-text">${msg("Redirect the user to a Flow.")}</p>
</ak-form-element-horizontal>
<ak-switch-input
?hidden=${this.mode !== RedirectStageModeEnum.Flow}
name="keepContext"
label=${msg("Keep flow context")}
?checked="${this.instance?.keepContext ?? true}"
>
</ak-switch-input>
</div>
</ak-form-group>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-stage-redirect-form": RedirectStageForm;
}
}

View File

@ -377,7 +377,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
` `
: html` <p> : html` <p>
${msg( ${msg(
"To let a user directly reset their password, configure a recovery flow on the currently active brand.", "To let a user directly reset a their password, configure a recovery flow on the currently active brand.",
)} )}
</p>`} </p>`}
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 KiB

After

Width:  |  Height:  |  Size: 671 KiB

View File

@ -70,7 +70,7 @@ export class UserSourceSettingsPage extends AKElement {
let connectionPk = -1; let connectionPk = -1;
if (this.connections) { if (this.connections) {
const connections = this.connections.results.filter( const connections = this.connections.results.filter(
(con) => con.sourceObj.slug === source.objectUid, (con) => con.source.slug === source.objectUid,
); );
if (connections.length > 0) { if (connections.length > 0) {
connectionPk = connections[0].pk; connectionPk = connections[0].pk;

View File

@ -3795,6 +3795,14 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Always require consent</source> <source>Always require consent</source>
<target>Immer nach Zustimmung fragen</target> <target>Immer nach Zustimmung fragen</target>
</trans-unit> </trans-unit>
<trans-unit id="s8ce8bdc9cc9c8604">
<source>Consent given last indefinitely</source>
<target>Einwilligung gilt unbegrenzt</target>
</trans-unit>
<trans-unit id="sb986f15fa9b17805">
<source>Consent expires.</source>
<target>Einwilligung erlischt.</target>
</trans-unit>
<trans-unit id="s6f328f2d8382d998"> <trans-unit id="s6f328f2d8382d998">
<source>Consent expires in</source> <source>Consent expires in</source>
<target>Einwilligung erlischt in</target> <target>Einwilligung erlischt in</target>
@ -4169,6 +4177,12 @@ doesn't pass when either or both of the selected options are equal or above the
<trans-unit id="s7b105164d209f670"> <trans-unit id="s7b105164d209f670">
<source>Require authentication</source> <source>Require authentication</source>
</trans-unit> </trans-unit>
<trans-unit id="s239c2a351cde6d39">
<source>Require no authentication.</source>
</trans-unit>
<trans-unit id="s98beadfeeb3acb66">
<source>Require superuser.</source>
</trans-unit>
<trans-unit id="sfad9279cc42c6b61"> <trans-unit id="sfad9279cc42c6b61">
<source>Required authentication level for this flow.</source> <source>Required authentication level for this flow.</source>
</trans-unit> </trans-unit>
@ -5893,6 +5907,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s7513372fe60f6387"> <trans-unit id="s7513372fe60f6387">
<source>Event volume</source> <source>Event volume</source>
</trans-unit> </trans-unit>
<trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source>
</trans-unit>
<trans-unit id="s3271da6c18c25b18"> <trans-unit id="s3271da6c18c25b18">
<source>Connection settings.</source> <source>Connection settings.</source>
</trans-unit> </trans-unit>
@ -7055,69 +7072,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s4f8a3f7792e6b940"> <trans-unit id="s4f8a3f7792e6b940">
<source>JWTs signed by the selected providers can be used to authenticate to this provider.</source> <source>JWTs signed by the selected providers can be used to authenticate to this provider.</source>
</trans-unit>
<trans-unit id="s3cc2b33d2a8000d3">
<source>KAdmin type</source>
</trans-unit>
<trans-unit id="s624e1c8739507529">
<source>MIT krb5 kadmin</source>
</trans-unit>
<trans-unit id="s6d225d9e74dfff6f">
<source>Heimdal kadmin</source>
</trans-unit>
<trans-unit id="sc9e494c8346b7cb5">
<source>Other</source>
</trans-unit>
<trans-unit id="sbf6c78047e8ec8f8">
<source>Other type of kadmin</source>
</trans-unit>
<trans-unit id="sb53d0b77abef2316">
<source>To let a user directly reset their password, configure a recovery flow on the currently active brand.</source>
</trans-unit>
<trans-unit id="s2e5226fcf269689b">
<source>Consent given lasts indefinitely</source>
</trans-unit>
<trans-unit id="s7eff620292ed9349">
<source>Consent expires</source>
</trans-unit>
<trans-unit id="s1cc032bcc50b2942">
<source>Available Policies</source>
</trans-unit>
<trans-unit id="s3ad64193ad5f4a5e">
<source>Selected Policies</source>
</trans-unit>
<trans-unit id="sc487e11d5987dbb4">
<source>Redirect the user to another flow, potentially with all gathered context</source>
</trans-unit>
<trans-unit id="sad9d5481474d4f5b">
<source>Static</source>
</trans-unit>
<trans-unit id="se87a96950464bc89">
<source>Target URL</source>
</trans-unit>
<trans-unit id="s7f3097955b19736a">
<source>Redirect the user to a static URL.</source>
</trans-unit>
<trans-unit id="s9bdee1c5130c8240">
<source>Target Flow</source>
</trans-unit>
<trans-unit id="sa5d1405b8d6529c7">
<source>Redirect the user to a Flow.</source>
</trans-unit>
<trans-unit id="s7c9db337d14d42b3">
<source>Keep flow context</source>
</trans-unit>
<trans-unit id="s0d7dea184036a74d">
<source>Require no authentication</source>
</trans-unit>
<trans-unit id="s66f533986ba6182c">
<source>Require superuser</source>
</trans-unit>
<trans-unit id="s26c0a8789930b5fd">
<source>Require being redirected from another flow</source>
</trans-unit>
<trans-unit id="sbfaee8cfbf4e44e8">
<source>Require Outpost (flow can only be executed from an outpost)</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -3974,6 +3974,14 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Always require consent</source> <source>Always require consent</source>
<target>Always require consent</target> <target>Always require consent</target>
</trans-unit> </trans-unit>
<trans-unit id="s8ce8bdc9cc9c8604">
<source>Consent given last indefinitely</source>
<target>Consent given last indefinitely</target>
</trans-unit>
<trans-unit id="sb986f15fa9b17805">
<source>Consent expires.</source>
<target>Consent expires.</target>
</trans-unit>
<trans-unit id="s6f328f2d8382d998"> <trans-unit id="s6f328f2d8382d998">
<source>Consent expires in</source> <source>Consent expires in</source>
<target>Consent expires in</target> <target>Consent expires in</target>
@ -4374,6 +4382,14 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Require authentication</source> <source>Require authentication</source>
<target>Require authentication</target> <target>Require authentication</target>
</trans-unit> </trans-unit>
<trans-unit id="s239c2a351cde6d39">
<source>Require no authentication.</source>
<target>Require no authentication.</target>
</trans-unit>
<trans-unit id="s98beadfeeb3acb66">
<source>Require superuser.</source>
<target>Require superuser.</target>
</trans-unit>
<trans-unit id="sfad9279cc42c6b61"> <trans-unit id="sfad9279cc42c6b61">
<source>Required authentication level for this flow.</source> <source>Required authentication level for this flow.</source>
<target>Required authentication level for this flow.</target> <target>Required authentication level for this flow.</target>
@ -6155,6 +6171,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s7513372fe60f6387"> <trans-unit id="s7513372fe60f6387">
<source>Event volume</source> <source>Event volume</source>
</trans-unit> </trans-unit>
<trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source>
</trans-unit>
<trans-unit id="s3271da6c18c25b18"> <trans-unit id="s3271da6c18c25b18">
<source>Connection settings.</source> <source>Connection settings.</source>
</trans-unit> </trans-unit>
@ -7317,69 +7336,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s4f8a3f7792e6b940"> <trans-unit id="s4f8a3f7792e6b940">
<source>JWTs signed by the selected providers can be used to authenticate to this provider.</source> <source>JWTs signed by the selected providers can be used to authenticate to this provider.</source>
</trans-unit>
<trans-unit id="s3cc2b33d2a8000d3">
<source>KAdmin type</source>
</trans-unit>
<trans-unit id="s624e1c8739507529">
<source>MIT krb5 kadmin</source>
</trans-unit>
<trans-unit id="s6d225d9e74dfff6f">
<source>Heimdal kadmin</source>
</trans-unit>
<trans-unit id="sc9e494c8346b7cb5">
<source>Other</source>
</trans-unit>
<trans-unit id="sbf6c78047e8ec8f8">
<source>Other type of kadmin</source>
</trans-unit>
<trans-unit id="sb53d0b77abef2316">
<source>To let a user directly reset their password, configure a recovery flow on the currently active brand.</source>
</trans-unit>
<trans-unit id="s2e5226fcf269689b">
<source>Consent given lasts indefinitely</source>
</trans-unit>
<trans-unit id="s7eff620292ed9349">
<source>Consent expires</source>
</trans-unit>
<trans-unit id="s1cc032bcc50b2942">
<source>Available Policies</source>
</trans-unit>
<trans-unit id="s3ad64193ad5f4a5e">
<source>Selected Policies</source>
</trans-unit>
<trans-unit id="sc487e11d5987dbb4">
<source>Redirect the user to another flow, potentially with all gathered context</source>
</trans-unit>
<trans-unit id="sad9d5481474d4f5b">
<source>Static</source>
</trans-unit>
<trans-unit id="se87a96950464bc89">
<source>Target URL</source>
</trans-unit>
<trans-unit id="s7f3097955b19736a">
<source>Redirect the user to a static URL.</source>
</trans-unit>
<trans-unit id="s9bdee1c5130c8240">
<source>Target Flow</source>
</trans-unit>
<trans-unit id="sa5d1405b8d6529c7">
<source>Redirect the user to a Flow.</source>
</trans-unit>
<trans-unit id="s7c9db337d14d42b3">
<source>Keep flow context</source>
</trans-unit>
<trans-unit id="s0d7dea184036a74d">
<source>Require no authentication</source>
</trans-unit>
<trans-unit id="s66f533986ba6182c">
<source>Require superuser</source>
</trans-unit>
<trans-unit id="s26c0a8789930b5fd">
<source>Require being redirected from another flow</source>
</trans-unit>
<trans-unit id="sbfaee8cfbf4e44e8">
<source>Require Outpost (flow can only be executed from an outpost)</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -3731,6 +3731,14 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Always require consent</source> <source>Always require consent</source>
<target>Exigir siempre el consentimiento</target> <target>Exigir siempre el consentimiento</target>
</trans-unit> </trans-unit>
<trans-unit id="s8ce8bdc9cc9c8604">
<source>Consent given last indefinitely</source>
<target>El último consentimiento otorgado indefinidamente</target>
</trans-unit>
<trans-unit id="sb986f15fa9b17805">
<source>Consent expires.</source>
<target>El consentimiento caduca.</target>
</trans-unit>
<trans-unit id="s6f328f2d8382d998"> <trans-unit id="s6f328f2d8382d998">
<source>Consent expires in</source> <source>Consent expires in</source>
<target>El consentimiento vence en</target> <target>El consentimiento vence en</target>
@ -4100,6 +4108,12 @@ doesn't pass when either or both of the selected options are equal or above the
<trans-unit id="s7b105164d209f670"> <trans-unit id="s7b105164d209f670">
<source>Require authentication</source> <source>Require authentication</source>
</trans-unit> </trans-unit>
<trans-unit id="s239c2a351cde6d39">
<source>Require no authentication.</source>
</trans-unit>
<trans-unit id="s98beadfeeb3acb66">
<source>Require superuser.</source>
</trans-unit>
<trans-unit id="sfad9279cc42c6b61"> <trans-unit id="sfad9279cc42c6b61">
<source>Required authentication level for this flow.</source> <source>Required authentication level for this flow.</source>
</trans-unit> </trans-unit>
@ -5811,6 +5825,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s7513372fe60f6387"> <trans-unit id="s7513372fe60f6387">
<source>Event volume</source> <source>Event volume</source>
</trans-unit> </trans-unit>
<trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source>
</trans-unit>
<trans-unit id="s3271da6c18c25b18"> <trans-unit id="s3271da6c18c25b18">
<source>Connection settings.</source> <source>Connection settings.</source>
</trans-unit> </trans-unit>
@ -6973,69 +6990,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s4f8a3f7792e6b940"> <trans-unit id="s4f8a3f7792e6b940">
<source>JWTs signed by the selected providers can be used to authenticate to this provider.</source> <source>JWTs signed by the selected providers can be used to authenticate to this provider.</source>
</trans-unit>
<trans-unit id="s3cc2b33d2a8000d3">
<source>KAdmin type</source>
</trans-unit>
<trans-unit id="s624e1c8739507529">
<source>MIT krb5 kadmin</source>
</trans-unit>
<trans-unit id="s6d225d9e74dfff6f">
<source>Heimdal kadmin</source>
</trans-unit>
<trans-unit id="sc9e494c8346b7cb5">
<source>Other</source>
</trans-unit>
<trans-unit id="sbf6c78047e8ec8f8">
<source>Other type of kadmin</source>
</trans-unit>
<trans-unit id="sb53d0b77abef2316">
<source>To let a user directly reset their password, configure a recovery flow on the currently active brand.</source>
</trans-unit>
<trans-unit id="s2e5226fcf269689b">
<source>Consent given lasts indefinitely</source>
</trans-unit>
<trans-unit id="s7eff620292ed9349">
<source>Consent expires</source>
</trans-unit>
<trans-unit id="s1cc032bcc50b2942">
<source>Available Policies</source>
</trans-unit>
<trans-unit id="s3ad64193ad5f4a5e">
<source>Selected Policies</source>
</trans-unit>
<trans-unit id="sc487e11d5987dbb4">
<source>Redirect the user to another flow, potentially with all gathered context</source>
</trans-unit>
<trans-unit id="sad9d5481474d4f5b">
<source>Static</source>
</trans-unit>
<trans-unit id="se87a96950464bc89">
<source>Target URL</source>
</trans-unit>
<trans-unit id="s7f3097955b19736a">
<source>Redirect the user to a static URL.</source>
</trans-unit>
<trans-unit id="s9bdee1c5130c8240">
<source>Target Flow</source>
</trans-unit>
<trans-unit id="sa5d1405b8d6529c7">
<source>Redirect the user to a Flow.</source>
</trans-unit>
<trans-unit id="s7c9db337d14d42b3">
<source>Keep flow context</source>
</trans-unit>
<trans-unit id="s0d7dea184036a74d">
<source>Require no authentication</source>
</trans-unit>
<trans-unit id="s66f533986ba6182c">
<source>Require superuser</source>
</trans-unit>
<trans-unit id="s26c0a8789930b5fd">
<source>Require being redirected from another flow</source>
</trans-unit>
<trans-unit id="sbfaee8cfbf4e44e8">
<source>Require Outpost (flow can only be executed from an outpost)</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2"> <?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="fr" source-language="en" original="lit-localize-inputs" datatype="plaintext"> <file target-language="fr" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body> <body>
<trans-unit id="s4caed5b7a7e5d89b"> <trans-unit id="s4caed5b7a7e5d89b">
@ -596,9 +596,9 @@
</trans-unit> </trans-unit>
<trans-unit id="saa0e2675da69651b"> <trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source> <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>L'URL &quot; <target>L'URL "
<x id="0" equiv-text="${this.url}"/>&quot; n'a pas été trouvée.</target> <x id="0" equiv-text="${this.url}"/>" n'a pas été trouvée.</target>
</trans-unit> </trans-unit>
<trans-unit id="s58cd9c2fe836d9c6"> <trans-unit id="s58cd9c2fe836d9c6">
@ -1568,7 +1568,7 @@
</trans-unit> </trans-unit>
<trans-unit id="s33ed903c210a6209"> <trans-unit id="s33ed903c210a6209">
<source>Token to authenticate with. Currently only bearer authentication is supported.</source> <source>Token to authenticate with. Currently only bearer authentication is supported.</source>
<target>Jeton d'authentification à utiliser. Actuellement, seule l'authentification &quot;bearer authentication&quot; est prise en charge.</target> <target>Jeton d'authentification à utiliser. Actuellement, seule l'authentification "bearer authentication" est prise en charge.</target>
</trans-unit> </trans-unit>
<trans-unit id="sfc8bb104e2c05af8"> <trans-unit id="sfc8bb104e2c05af8">
@ -1736,8 +1736,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa90b7809586c35ce"> <trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source> <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>Entrez une URL complète, un chemin relatif ou utilisez 'fa://fa-test' pour utiliser l'icône Font Awesome &quot;fa-test&quot;.</target> <target>Entrez une URL complète, un chemin relatif ou utilisez 'fa://fa-test' pour utiliser l'icône Font Awesome "fa-test".</target>
</trans-unit> </trans-unit>
<trans-unit id="s0410779cb47de312"> <trans-unit id="s0410779cb47de312">
@ -2815,7 +2815,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s33683c3b1dbaf264"> <trans-unit id="s33683c3b1dbaf264">
<source>To use SSL instead, use 'ldaps://' and disable this option.</source> <source>To use SSL instead, use 'ldaps://' and disable this option.</source>
<target>Pour utiliser SSL à la base, utilisez &quot;ldaps://&quot; et désactviez cette option.</target> <target>Pour utiliser SSL à la base, utilisez "ldaps://" et désactviez cette option.</target>
</trans-unit> </trans-unit>
<trans-unit id="s2221fef80f4753a2"> <trans-unit id="s2221fef80f4753a2">
@ -2899,8 +2899,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s76768bebabb7d543"> <trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ &quot;memberUid&quot;, la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'</target> <target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ "memberUid", la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'</target>
</trans-unit> </trans-unit>
<trans-unit id="s026555347e589f0e"> <trans-unit id="s026555347e589f0e">
@ -3195,7 +3195,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s3198c384c2f68b08"> <trans-unit id="s3198c384c2f68b08">
<source>Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually.</source> <source>Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually.</source>
<target>Moment où les utilisateurs temporaires doivent être supprimés. Cela ne s'applique que si votre IDP utilise le format NameID &quot;transient&quot; et que l'utilisateur ne se déconnecte pas manuellement.</target> <target>Moment où les utilisateurs temporaires doivent être supprimés. Cela ne s'applique que si votre IDP utilise le format NameID "transient" et que l'utilisateur ne se déconnecte pas manuellement.</target>
</trans-unit> </trans-unit>
<trans-unit id="sb32e9c1faa0b8673"> <trans-unit id="sb32e9c1faa0b8673">
@ -3337,7 +3337,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s9f8aac89fe318acc"> <trans-unit id="s9f8aac89fe318acc">
<source>Optionally set the 'FriendlyName' value of the Assertion attribute.</source> <source>Optionally set the 'FriendlyName' value of the Assertion attribute.</source>
<target>Indiquer la valeur &quot;FriendlyName&quot; de l'attribut d'assertion (optionnel)</target> <target>Indiquer la valeur "FriendlyName" de l'attribut d'assertion (optionnel)</target>
</trans-unit> </trans-unit>
<trans-unit id="s851c108679653d2a"> <trans-unit id="s851c108679653d2a">
@ -3646,8 +3646,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s7b1fba26d245cb1c"> <trans-unit id="s7b1fba26d245cb1c">
<source>When using an external logging solution for archiving, this can be set to &quot;minutes=5&quot;.</source> <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
<target>En cas d'utilisation d'une solution de journalisation externe pour l'archivage, cette valeur peut être fixée à &quot;minutes=5&quot;.</target> <target>En cas d'utilisation d'une solution de journalisation externe pour l'archivage, cette valeur peut être fixée à "minutes=5".</target>
</trans-unit> </trans-unit>
<trans-unit id="s44536d20bb5c8257"> <trans-unit id="s44536d20bb5c8257">
@ -3823,10 +3823,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sa95a538bfbb86111"> <trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source> <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>Êtes-vous sûr de vouloir mettre à jour <target>Êtes-vous sûr de vouloir mettre à jour
<x id="0" equiv-text="${this.objectLabel}"/>&quot; <x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</target> <x id="1" equiv-text="${this.obj?.name}"/>"?</target>
</trans-unit> </trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6"> <trans-unit id="sc92d7cfb6ee1fec6">
@ -4902,8 +4902,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sdf1d8edef27236f0"> <trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source> <source>A "roaming" authenticator, like a YubiKey</source>
<target>Un authentificateur &quot;itinérant&quot;, comme une YubiKey</target> <target>Un authentificateur "itinérant", comme une YubiKey</target>
</trans-unit> </trans-unit>
<trans-unit id="sfffba7b23d8fb40c"> <trans-unit id="sfffba7b23d8fb40c">
@ -4965,6 +4965,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Always require consent</source> <source>Always require consent</source>
<target>Toujours exiger l'approbation</target> <target>Toujours exiger l'approbation</target>
</trans-unit>
<trans-unit id="s8ce8bdc9cc9c8604">
<source>Consent given last indefinitely</source>
<target>L'approbation dure indéfiniment</target>
</trans-unit>
<trans-unit id="sb986f15fa9b17805">
<source>Consent expires.</source>
<target>L'approbation expire.</target>
</trans-unit> </trans-unit>
<trans-unit id="s6f328f2d8382d998"> <trans-unit id="s6f328f2d8382d998">
<source>Consent expires in</source> <source>Consent expires in</source>
@ -5218,7 +5228,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s5170f9ef331949c0"> <trans-unit id="s5170f9ef331949c0">
<source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source> <source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source>
<target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable &quot;prompt_data&quot;.</target> <target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable "prompt_data".</target>
</trans-unit> </trans-unit>
<trans-unit id="s36cb242ac90353bc"> <trans-unit id="s36cb242ac90353bc">
@ -5271,8 +5281,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s1608b2f94fa0dbd4"> <trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source> <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>Si défini à une durée supérieure à 0, l'utilisateur aura la possibilité de choisir de &quot;rester connecté&quot;, ce qui prolongera sa session jusqu'à la durée spécifiée ici.</target> <target>Si défini à une durée supérieure à 0, l'utilisateur aura la possibilité de choisir de "rester connecté", ce qui prolongera sa session jusqu'à la durée spécifiée ici.</target>
</trans-unit> </trans-unit>
<trans-unit id="s542a71bb8f41e057"> <trans-unit id="s542a71bb8f41e057">
@ -5466,6 +5476,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Require authentication</source> <source>Require authentication</source>
<target>Requiert une authentification</target> <target>Requiert une authentification</target>
</trans-unit>
<trans-unit id="s239c2a351cde6d39">
<source>Require no authentication.</source>
<target>Requiert l'absence d'authentification</target>
</trans-unit>
<trans-unit id="s98beadfeeb3acb66">
<source>Require superuser.</source>
<target>Requiert un super-utilisateur</target>
</trans-unit> </trans-unit>
<trans-unit id="sfad9279cc42c6b61"> <trans-unit id="sfad9279cc42c6b61">
<source>Required authentication level for this flow.</source> <source>Required authentication level for this flow.</source>
@ -6030,7 +6050,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit> </trans-unit>
<trans-unit id="sa7fcf026bd25f231"> <trans-unit id="sa7fcf026bd25f231">
<source>Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system.</source> <source>Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system.</source>
<target>Peut être au format &quot;unix://&quot; pour une connexion à un service docker local, &quot;ssh://&quot; pour une connexion via SSH, ou &quot;https://:2376&quot; pour une connexion à un système distant.</target> <target>Peut être au format "unix://" pour une connexion à un service docker local, "ssh://" pour une connexion via SSH, ou "https://:2376" pour une connexion à un système distant.</target>
</trans-unit> </trans-unit>
<trans-unit id="saf1d289e3137c2ea"> <trans-unit id="saf1d289e3137c2ea">
@ -7282,7 +7302,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit> </trans-unit>
<trans-unit id="sff0ac1ace2d90709"> <trans-unit id="sff0ac1ace2d90709">
<source>Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).</source> <source>Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).</source>
<target>Utilisez ce fournisseur avec l'option &quot;auth_request&quot; de Nginx ou &quot;forwardAuth&quot; de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, &quot;/outpost.goauthentik.io&quot; doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous).</target> <target>Utilisez ce fournisseur avec l'option "auth_request" de Nginx ou "forwardAuth" de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, "/outpost.goauthentik.io" doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous).</target>
</trans-unit> </trans-unit>
<trans-unit id="scb58b8a60cad8762"> <trans-unit id="scb58b8a60cad8762">
<source>Default relay state</source> <source>Default relay state</source>
@ -7672,7 +7692,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<target>Utilisateur créé et ajouté au groupe <x id="0" equiv-text="${this.group.name}"/> avec succès</target> <target>Utilisateur créé et ajouté au groupe <x id="0" equiv-text="${this.group.name}"/> avec succès</target>
</trans-unit> </trans-unit>
<trans-unit id="s824e0943a7104668"> <trans-unit id="s824e0943a7104668">
<source>This user will be added to the group &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;.</source> <source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<target>Cet utilisateur sera ajouté au groupe &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot;.</target> <target>Cet utilisateur sera ajouté au groupe &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot;.</target>
</trans-unit> </trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca"> <trans-unit id="s62e7f6ed7d9cb3ca">
@ -7743,6 +7763,10 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<source>Event volume</source> <source>Event volume</source>
<target>Volume d'événements</target> <target>Volume d'événements</target>
</trans-unit> </trans-unit>
<trans-unit id="s047a5f0211fedc72">
<source>Require Outpost (flow can only be executed from an outpost).</source>
<target>Forcer l'utilisation d'un avant-poste (le flux ne pourrait être exécuter que depuis un outpost).</target>
</trans-unit>
<trans-unit id="s3271da6c18c25b18"> <trans-unit id="s3271da6c18c25b18">
<source>Connection settings.</source> <source>Connection settings.</source>
<target>Paramètres de connexion.</target> <target>Paramètres de connexion.</target>
@ -9018,7 +9042,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<target>Synchroniser le groupe</target> <target>Synchroniser le groupe</target>
</trans-unit> </trans-unit>
<trans-unit id="s2d5f69929bb7221d"> <trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${p.name}"/> (&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${p.type}"/>)</source> <source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, de type <x id="2" equiv-text="${p.type}"/>)</target> <target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, de type <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit> </trans-unit>
<trans-unit id="sa38c5a2731be3a46"> <trans-unit id="sa38c5a2731be3a46">
@ -9270,8 +9294,8 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<target>URLs de redirection autorisées après un flux d'autorisation réussi. Indiquez également toute origine ici pour les flux implicites.</target> <target>URLs de redirection autorisées après un flux d'autorisation réussi. Indiquez également toute origine ici pour les flux implicites.</target>
</trans-unit> </trans-unit>
<trans-unit id="s4c49d27de60a532b"> <trans-unit id="s4c49d27de60a532b">
<source>To allow any redirect URI, set the mode to Regex and the value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source> <source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
<target>Pour permettre n'importe quelle URI de redirection, définissez cette valeur sur &quot;.*&quot;. Soyez conscient des possibles implications de sécurité que cela peut avoir.</target> <target>Pour permettre n'importe quelle URI de redirection, définissez cette valeur sur ".*". Soyez conscient des possibles implications de sécurité que cela peut avoir.</target>
</trans-unit> </trans-unit>
<trans-unit id="s43f899a86c6a3484"> <trans-unit id="s43f899a86c6a3484">
<source>Redirect URIs/Origins</source> <source>Redirect URIs/Origins</source>
@ -9296,91 +9320,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s4f8a3f7792e6b940"> <trans-unit id="s4f8a3f7792e6b940">
<source>JWTs signed by the selected providers can be used to authenticate to this provider.</source> <source>JWTs signed by the selected providers can be used to authenticate to this provider.</source>
<target>Les JWTs signés par les fournisseurs sélectionnés peuvent être utilisés pour s'authentifier auprès de ce fournisseur.</target> <target>Les JWTs signés par les fournisseurs sélectionnés peuvent être utilisés pour s'authentifier auprès de ce fournisseur.</target>
</trans-unit>
<trans-unit id="s3cc2b33d2a8000d3">
<source>KAdmin type</source>
<target>Type KAdmin</target>
</trans-unit>
<trans-unit id="s624e1c8739507529">
<source>MIT krb5 kadmin</source>
<target>MIT krb5 kadmin</target>
</trans-unit>
<trans-unit id="s6d225d9e74dfff6f">
<source>Heimdal kadmin</source>
<target>Heimdal kadmin</target>
</trans-unit>
<trans-unit id="sc9e494c8346b7cb5">
<source>Other</source>
<target>Autre</target>
</trans-unit>
<trans-unit id="sbf6c78047e8ec8f8">
<source>Other type of kadmin</source>
<target>Autre type de kadmin</target>
</trans-unit>
<trans-unit id="sb53d0b77abef2316">
<source>To let a user directly reset their password, configure a recovery flow on the currently active brand.</source>
<target>Pour laisser les utilisateurs réinitialiser leur mot de passe, configurez un flux de récupération sur la marque actuelle.</target>
</trans-unit>
<trans-unit id="s2e5226fcf269689b">
<source>Consent given lasts indefinitely</source>
<target>L'approbation donnée dure indéfiniment</target>
</trans-unit>
<trans-unit id="s7eff620292ed9349">
<source>Consent expires</source>
<target>L'approbation expire</target>
</trans-unit>
<trans-unit id="s1cc032bcc50b2942">
<source>Available Policies</source>
<target>Politiques disponibles</target>
</trans-unit>
<trans-unit id="s3ad64193ad5f4a5e">
<source>Selected Policies</source>
<target>Politiques sélectionnées</target>
</trans-unit>
<trans-unit id="sc487e11d5987dbb4">
<source>Redirect the user to another flow, potentially with all gathered context</source>
<target>Rediriger l'utilisateur vers un autre flux, éventuellement avec le contexte</target>
</trans-unit>
<trans-unit id="sad9d5481474d4f5b">
<source>Static</source>
<target>Statique</target>
</trans-unit>
<trans-unit id="se87a96950464bc89">
<source>Target URL</source>
<target>URL destination</target>
</trans-unit>
<trans-unit id="s7f3097955b19736a">
<source>Redirect the user to a static URL.</source>
<target>Rediriger l'utilisateur vers une URL statique.</target>
</trans-unit>
<trans-unit id="s9bdee1c5130c8240">
<source>Target Flow</source>
<target>Flux destination</target>
</trans-unit>
<trans-unit id="sa5d1405b8d6529c7">
<source>Redirect the user to a Flow.</source>
<target>Rediriger l'utilisateur vers un flux.</target>
</trans-unit>
<trans-unit id="s7c9db337d14d42b3">
<source>Keep flow context</source>
<target>Conserver le contexte du flux</target>
</trans-unit>
<trans-unit id="s0d7dea184036a74d">
<source>Require no authentication</source>
<target>Requiert l'absence d'authentification</target>
</trans-unit>
<trans-unit id="s66f533986ba6182c">
<source>Require superuser</source>
<target>Requiert un super-utilisateur</target>
</trans-unit>
<trans-unit id="s26c0a8789930b5fd">
<source>Require being redirected from another flow</source>
<target>Requiert d'être redirigé depuis un autre flux</target>
</trans-unit>
<trans-unit id="sbfaee8cfbf4e44e8">
<source>Require Outpost (flow can only be executed from an outpost)</source>
<target>Forcer l'utilisation d'un avant-poste (le flux ne pourrait être exécuter que depuis un avant-poste)</target>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

Some files were not shown because too many files have changed in this diff Show More