Compare commits

..

23 Commits

Author SHA1 Message Date
Tana M Berry
b1cb339f3a tweak 2025-08-04 15:13:41 -05:00
Tana M Berry
e124e21119 Merge branch 'main' into docs-remove-phrase 2025-08-04 15:06:03 -05:00
Simonyi Gergő
213cf44928 root: enhance custom middleware experience (#15919)
* enable custom middleware positioning

Users can now set up their middleware to come before or after other
middleware.

Comes with the added benefit that prometheus middlewares are ensured
to be the very first and very last to run.

* stop treating authentik.enterprise exceptionally in settings

This is the singular case where more apps are added.

* stop treating authentik.core exceptionally in settings

Uhh, fingers crossed? This has history, it goes back to 80d90b91e8
2025-08-04 21:05:05 +02:00
Tana M Berry
dc0c7a858a tweak to bump build 2025-08-04 13:53:50 -05:00
Tana M Berry
3fddbb918e removed phrase 2025-08-04 13:32:08 -05:00
Timo Christeleit
3c97b081b0 website/integrations: add hass-openid instructions (#14672)
* add instructions

* Added tabs for each configuration method, changed some wording, and ran prettier.

* Changed proxy section formatting and some language

* Update website/integrations/services/home-assistant/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>

* Update website/integrations/services/home-assistant/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>

* Update website/integrations/services/home-assistant/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>

* Update website/integrations/services/home-assistant/index.mdx

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Timo Christeleit <timo.christeleit@cavefire.net>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-08-04 09:39:37 -05:00
Jose D. Gomez R.
ba725365ec core: add updated_at field to user (#15571)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-08-04 13:36:09 +00:00
Dominic R
e5e9708ec2 root: Add more opencontainer labels to Dockerfiles (#15923)
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-08-04 13:08:09 +00:00
dependabot[bot]
6a604e42ca core: bump goauthentik.io/api/v3 from 3.2025064.2 to 3.2025064.3 (#15949)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-04 12:55:18 +00:00
Daniel Adu-Gyan
ab1f87cfd6 core, providers/ldap: add parent/child groups to api and ldap results (#14974) 2025-08-04 14:29:16 +02:00
Teffen Ellis
de9b795c97 web: Make Webdriver optional during install. (#15952) 2025-08-04 12:24:34 +00:00
authentik-automation[bot]
0377e3593e core, web: update translations (#15945)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-08-04 13:22:17 +02:00
Simonyi Gergő
951c24dab5 packages/django-dramatiq-postgres: fix typo (#15932)
* fix typo

* fix typo
2025-08-04 13:05:57 +02:00
authentik-automation[bot]
707eca883e web: bump API Client version (#15942)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-08-03 18:29:37 +01:00
Jens L.
8bc64ea478 core: fix flow planner checking against wrong user when creating recovery link (#15390)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-08-03 18:24:37 +01:00
Jens L.
8b1240ff0b providers/saml: configuration for default NameID Policy (#15109)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-08-03 18:24:12 +01:00
Marcelo Elizeche Landó
56ff8b1f97 core: bump boto3 from 1.39.15 to v1.40.1 (#15926) 2025-08-03 12:22:54 +02:00
Marcelo Elizeche Landó
cf26aace7b core: bump jsii from 1.112.0 to v1.113.0 (#15927) 2025-08-03 12:22:39 +02:00
Marcelo Elizeche Landó
46021e904a core: bump argon2-cffi-bindings from 21.2.0 to v25.1.0 (#15925) 2025-08-03 12:22:24 +02:00
Marcelo Elizeche Landó
a47196776d core: bump aiohttp from 3.12.14 to v3.12.15 (#15924) 2025-08-03 12:22:12 +02:00
Marcelo Elizeche Landó
58ce20c840 core: bump opentelemetry-api from 1.35.0 to v1.36.0 (#15928) 2025-08-03 12:22:06 +02:00
Simonyi Gergő
29b0177235 web/admin: fix variable name (#15934)
fix variable name
2025-08-02 16:26:20 +01:00
Simonyi Gergő
f15ddfcccd policies: fix typo (#15933)
fix typo
2025-08-02 16:23:05 +01:00
71 changed files with 2554 additions and 2928 deletions

View File

@@ -134,11 +134,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io Main server image, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik server image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
WORKDIR /

View File

@@ -49,11 +49,28 @@ class GroupMemberSerializer(ModelSerializer):
]
class GroupChildSerializer(ModelSerializer):
"""Stripped down group serializer to show relevant children for groups"""
attributes = JSONDictField(required=False)
class Meta:
model = Group
fields = [
"pk",
"name",
"is_superuser",
"attributes",
"group_uuid",
]
class GroupSerializer(ModelSerializer):
"""Group Serializer"""
attributes = JSONDictField(required=False)
users_obj = SerializerMethodField(allow_null=True)
children_obj = SerializerMethodField(allow_null=True)
roles_obj = ListSerializer(
child=RoleSerializer(),
read_only=True,
@@ -61,7 +78,6 @@ class GroupSerializer(ModelSerializer):
required=False,
)
parent_name = CharField(source="parent.name", read_only=True, allow_null=True)
num_pk = IntegerField(read_only=True)
@property
@@ -71,12 +87,25 @@ class GroupSerializer(ModelSerializer):
return True
return str(request.query_params.get("include_users", "true")).lower() == "true"
@property
def _should_include_children(self) -> bool:
request: Request = self.context.get("request", None)
if not request:
return True
return str(request.query_params.get("include_children", "false")).lower() == "true"
@extend_schema_field(GroupMemberSerializer(many=True))
def get_users_obj(self, instance: Group) -> list[GroupMemberSerializer] | None:
if not self._should_include_users:
return None
return GroupMemberSerializer(instance.users, many=True).data
@extend_schema_field(GroupChildSerializer(many=True))
def get_children_obj(self, instance: Group) -> list[GroupChildSerializer] | None:
if not self._should_include_children:
return None
return GroupChildSerializer(instance.children, many=True).data
def validate_parent(self, parent: Group | None):
"""Validate group parent (if set), ensuring the parent isn't itself"""
if not self.instance or not parent:
@@ -126,11 +155,17 @@ class GroupSerializer(ModelSerializer):
"attributes",
"roles",
"roles_obj",
"children",
"children_obj",
]
extra_kwargs = {
"users": {
"default": list,
},
"children": {
"required": False,
"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())]},
@@ -203,11 +238,15 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
Prefetch("users", queryset=User.objects.all().only("id"))
)
if self.serializer_class(context={"request": self.request})._should_include_children:
base_qs = base_qs.prefetch_related("children")
return base_qs
@extend_schema(
parameters=[
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
]
)
def list(self, request, *args, **kwargs):
@@ -216,6 +255,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
parameters=[
OpenApiParameter("include_users", bool, default=True),
OpenApiParameter("include_children", bool, default=False),
]
)
def retrieve(self, request, *args, **kwargs):

View File

@@ -5,7 +5,7 @@ from json import loads
from typing import Any
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.models import Permission
from django.contrib.auth.models import AnonymousUser, Permission
from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django.urls import reverse_lazy
@@ -16,6 +16,7 @@ from django.utils.translation import gettext as _
from django_filters.filters import (
BooleanFilter,
CharFilter,
IsoDateTimeFilter,
ModelMultipleChoiceFilter,
MultipleChoiceFilter,
UUIDFilter,
@@ -241,6 +242,7 @@ class UserSerializer(ModelSerializer):
"type",
"uuid",
"password_change_date",
"last_updated",
]
extra_kwargs = {
"name": {"allow_blank": True},
@@ -331,6 +333,14 @@ class UsersFilter(FilterSet):
method="filter_attributes",
)
date_joined__lt = IsoDateTimeFilter(field_name="date_joined", lookup_expr="lt")
date_joined = IsoDateTimeFilter(field_name="date_joined")
date_joined__gt = IsoDateTimeFilter(field_name="date_joined", lookup_expr="gt")
last_updated__lt = IsoDateTimeFilter(field_name="last_updated", lookup_expr="lt")
last_updated = IsoDateTimeFilter(field_name="last_updated")
last_updated__gt = IsoDateTimeFilter(field_name="last_updated", lookup_expr="gt")
is_superuser = BooleanFilter(field_name="ak_groups", method="filter_is_superuser")
uuid = UUIDFilter(field_name="uuid")
@@ -376,6 +386,8 @@ class UsersFilter(FilterSet):
fields = [
"username",
"email",
"date_joined",
"last_updated",
"name",
"is_active",
"is_superuser",
@@ -390,10 +402,19 @@ class UserViewSet(UsedByMixin, ModelViewSet):
"""User Viewset"""
queryset = User.objects.none()
ordering = ["username"]
ordering = ["username", "date_joined", "last_updated"]
serializer_class = UserSerializer
filterset_class = UsersFilter
search_fields = ["username", "name", "is_active", "email", "uuid", "attributes"]
search_fields = [
"username",
"name",
"is_active",
"email",
"uuid",
"attributes",
"date_joined",
"last_updated",
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField
@@ -435,6 +456,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
user: User = self.get_object()
planner = FlowPlanner(flow)
planner.allow_empty_flows = True
self.request._request.user = AnonymousUser()
try:
plan = planner.plan(
self.request._request,

View File

@@ -0,0 +1,27 @@
# Generated by Django 5.1.11 on 2025-07-15 15:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("authentik_core", "0049_alter_token_options"),
]
operations = [
migrations.AddField(
model_name="user",
name="last_updated",
field=models.DateTimeField(auto_now=True),
),
migrations.AddIndex(
model_name="user",
index=models.Index(fields=["last_updated"], name="authentik_c_last_up_ed7486_idx"),
),
migrations.AddIndex(
model_name="user",
index=models.Index(fields=["date_joined"], name="authentik_c_date_jo_58c256_idx"),
),
]

View File

@@ -274,6 +274,8 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
ak_groups = models.ManyToManyField("Group", related_name="users")
password_change_date = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
objects = UserManager()
class Meta:
@@ -293,6 +295,8 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
models.Index(fields=["uuid"]),
models.Index(fields=["path"]),
models.Index(fields=["type"]),
models.Index(fields=["date_joined"]),
models.Index(fields=["last_updated"]),
]
def __str__(self):

View File

@@ -21,7 +21,7 @@ from authentik.core.tests.utils import (
create_test_flow,
create_test_user,
)
from authentik.flows.models import FlowDesignation
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation
from authentik.lib.generators import generate_id, generate_key
from authentik.stages.email.models import EmailStage
@@ -103,8 +103,11 @@ class TestUsersAPI(APITestCase):
self.assertTrue(self.admin.check_password(new_pw))
def test_recovery(self):
"""Test user recovery link (no recovery flow set)"""
flow = create_test_flow(FlowDesignation.RECOVERY)
"""Test user recovery link"""
flow = create_test_flow(
FlowDesignation.RECOVERY,
authentication=FlowAuthenticationRequirement.REQUIRE_UNAUTHENTICATED,
)
brand: Brand = create_test_brand()
brand.flow_recovery = flow
brand.save()
@@ -387,3 +390,72 @@ class TestUsersAPI(APITestCase):
self.assertFalse(
AuthenticatedSession.objects.filter(session__session_key=session_id).exists()
)
def test_sort_by_last_updated(self):
"""Test API sorting by last_updated"""
User.objects.all().delete()
admin = create_test_admin_user()
self.client.force_login(admin)
user = create_test_user()
admin.first_name = "Sample change"
admin.last_name = "To trigger an update"
admin.save()
# Ascending
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "last_updated",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], user.pk)
# Descending
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "-last_updated",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], admin.pk)
def test_sort_by_date_joined(self):
"""Test API sorting by date_joined"""
User.objects.all().delete()
admin = create_test_admin_user()
self.client.force_login(admin)
user = create_test_user()
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "date_joined",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], admin.pk)
response = self.client.get(
reverse("authentik_api:user-list"),
data={
"ordering": "-date_joined",
},
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(len(body["results"]), 2)
self.assertEqual(body["results"][0]["pk"], user.pk)

View File

@@ -55,6 +55,7 @@ class TestEnterpriseAudit(APITestCase):
self.assertIsNotNone(event)
self.assertIsNotNone(event.context["diff"])
diff = event.context["diff"]
diff.pop("last_updated")
self.assertEqual(
diff,
{
@@ -116,6 +117,7 @@ class TestEnterpriseAudit(APITestCase):
self.assertIsNotNone(event)
self.assertIsNotNone(event.context["diff"])
diff = event.context["diff"]
diff.pop("last_updated")
self.assertEqual(
diff,
{

View File

@@ -42,7 +42,7 @@ class TestBindingsAPI(APITestCase):
)
def test_invalid_too_little(self):
"""Test invvalid binding (too little)"""
"""Test invalid binding (too little)"""
response = self.client.post(
reverse("authentik_api:policybinding-list"),
data={"target": self.pbm.pk, "order": 0},

View File

@@ -190,6 +190,7 @@ class SAMLProviderSerializer(ProviderSerializer):
"sign_response",
"sp_binding",
"default_relay_state",
"default_name_id_policy",
"url_download_metadata",
"url_sso_post",
"url_sso_redirect",

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.1.11 on 2025-06-18 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_saml", "0018_alter_samlprovider_acs_url"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="default_name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"),
],
default="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
),
),
]

View File

@@ -12,6 +12,7 @@ from authentik.core.models import PropertyMapping, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.models import DomainlessURLValidator
from authentik.lib.utils.time import timedelta_string_validator
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
DSA_SHA1,
ECDSA_SHA1,
@@ -179,6 +180,9 @@ class SAMLProvider(Provider):
default_relay_state = models.TextField(
default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins")
)
default_name_id_policy = models.TextField(
choices=SAMLNameIDPolicy.choices, default=SAMLNameIDPolicy.UNSPECIFIED
)
sign_assertion = models.BooleanField(default=True)
sign_response = models.BooleanField(default=False)

View File

@@ -205,6 +205,13 @@ class AssertionProcessor:
def get_name_id(self) -> Element:
"""Get NameID Element"""
name_id = Element(f"{{{NS_SAML_ASSERTION}}}NameID")
# For requests that don't specify a NameIDPolicy, check if we
# can fall back to the provider default
if (
self.auth_n_request.name_id_policy == SAML_NAME_ID_FORMAT_UNSPECIFIED
and self.provider.default_name_id_policy != SAML_NAME_ID_FORMAT_UNSPECIFIED
):
self.auth_n_request.name_id_policy = self.provider.default_name_id_policy
name_id.attrib["Format"] = self.auth_n_request.name_id_policy
# persistent is used as a fallback, so always generate it
persistent = self.http_request.user.uid

View File

@@ -13,6 +13,7 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.exceptions import CannotHandleAssertion
from authentik.providers.saml.models import SAMLProvider
from authentik.providers.saml.utils.encoding import decode_base64_and_inflate
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
DSA_SHA1,
NS_MAP,
@@ -175,7 +176,9 @@ class AuthNRequestParser:
def idp_initiated(self) -> AuthNRequest:
"""Create IdP Initiated AuthNRequest"""
relay_state = None
request = AuthNRequest(relay_state=None)
if self.provider.default_relay_state != "":
relay_state = self.provider.default_relay_state
return AuthNRequest(relay_state=relay_state)
request.relay_state = self.provider.default_relay_state
if self.provider.default_name_id_policy != SAMLNameIDPolicy.UNSPECIFIED:
request.name_id_policy = self.provider.default_name_id_policy
return request

View File

@@ -13,6 +13,7 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.utils.encoding import PEM_FOOTER, PEM_HEADER
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import (
NS_MAP,
NS_SAML_METADATA,
@@ -46,6 +47,7 @@ class ServiceProviderMetadata:
auth_n_request_signed: bool
assertion_signed: bool
name_id_policy: SAMLNameIDPolicy
signing_keypair: CertificateKeyPair | None = None
@@ -60,6 +62,7 @@ class ServiceProviderMetadata:
provider.issuer = self.entity_id
provider.sp_binding = self.acs_binding
provider.acs_url = self.acs_location
provider.default_name_id_policy = self.name_id_policy
if self.signing_keypair and self.auth_n_request_signed:
self.signing_keypair.name = f"Provider {name} - SAML Signing Certificate"
self.signing_keypair.save()
@@ -148,6 +151,11 @@ class ServiceProviderMetadataParser:
if signing_keypair:
self.check_signature(root, signing_keypair)
name_id_format = descriptor.findall(f"{{{NS_SAML_METADATA}}}NameIDFormat")
name_id_policy = SAMLNameIDPolicy.UNSPECIFIED
if len(name_id_format) > 0:
name_id_policy = SAMLNameIDPolicy(name_id_format[0].text)
return ServiceProviderMetadata(
entity_id=entity_id,
acs_binding=acs_binding,
@@ -155,4 +163,5 @@ class ServiceProviderMetadataParser:
auth_n_request_signed=auth_n_request_signed,
assertion_signed=assertion_signed,
signing_keypair=signing_keypair,
name_id_policy=name_id_policy,
)

View File

@@ -4,7 +4,7 @@
cacheDuration="PT604800S"
entityID="http://localhost:8080/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8080/saml/acs"
index="1" />

View File

@@ -14,6 +14,7 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.metadata import MetadataProcessor
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
from authentik.sources.saml.models import SAMLNameIDPolicy
from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA
@@ -86,6 +87,7 @@ class TestServiceProviderMetadataParser(TestCase):
self.assertEqual(provider.acs_url, "http://localhost:8080/saml/acs")
self.assertEqual(provider.issuer, "http://localhost:8080/saml/metadata")
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
self.assertEqual(provider.default_name_id_policy, SAMLNameIDPolicy.EMAIL)
self.assertEqual(
len(provider.property_mappings.all()),
len(SAMLPropertyMapping.objects.exclude(managed__isnull=True)),

View File

@@ -75,7 +75,9 @@ TENANT_APPS = [
"pgtrigger",
"authentik.admin",
"authentik.api",
"authentik.core",
"authentik.crypto",
"authentik.enterprise",
"authentik.events",
"authentik.flows",
"authentik.outposts",
@@ -171,6 +173,7 @@ SPECTACULAR_SETTINGS = {
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
"TaskAggregatedStatusEnum": "authentik.tasks.models.TaskStatus",
"SAMLNameIDPolicyEnum": "authentik.sources.saml.models.SAMLNameIDPolicy",
"UserTypeEnum": "authentik.core.models.UserTypes",
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
},
@@ -245,10 +248,12 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
MIDDLEWARE_FIRST = [
"django_prometheus.middleware.PrometheusBeforeMiddleware",
]
MIDDLEWARE = [
"django_tenants.middleware.default.DefaultTenantMiddleware",
"authentik.root.middleware.LoggingMiddleware",
"django_prometheus.middleware.PrometheusBeforeMiddleware",
"authentik.root.middleware.ClientIPMiddleware",
"authentik.stages.user_login.middleware.BoundSessionMiddleware",
"authentik.core.middleware.AuthenticationMiddleware",
@@ -261,6 +266,8 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"authentik.core.middleware.ImpersonateMiddleware",
]
MIDDLEWARE_LAST = [
"django_prometheus.middleware.PrometheusAfterMiddleware",
]
@@ -496,7 +503,9 @@ _DISALLOWED_ITEMS = [
"SHARED_APPS",
"TENANT_APPS",
"INSTALLED_APPS",
"MIDDLEWARE_FIRST",
"MIDDLEWARE",
"MIDDLEWARE_LAST",
"AUTHENTICATION_BACKENDS",
"SPECTACULAR_SETTINGS",
"REST_FRAMEWORK",
@@ -514,16 +523,35 @@ SILENCED_SYSTEM_CHECKS = [
]
def _update_settings(app_path: str):
def subtract_list(a: list, b: list) -> list:
return [item for item in a if item not in b]
def _filter_and_update(apps: list[str]) -> None:
for _app in set(apps):
if not _app.startswith("authentik"):
continue
_update_settings(f"{_app}.settings")
def _update_settings(app_path: str) -> None:
try:
settings_module = importlib.import_module(app_path)
CONFIG.log("debug", "Loaded app settings", path=app_path)
SHARED_APPS.extend(getattr(settings_module, "SHARED_APPS", []))
TENANT_APPS.extend(getattr(settings_module, "TENANT_APPS", []))
new_shared_apps = subtract_list(getattr(settings_module, "SHARED_APPS", []), SHARED_APPS)
new_tenant_apps = subtract_list(getattr(settings_module, "TENANT_APPS", []), TENANT_APPS)
SHARED_APPS.extend(new_shared_apps)
TENANT_APPS.extend(new_tenant_apps)
_filter_and_update(new_shared_apps + new_tenant_apps)
MIDDLEWARE_FIRST.extend(getattr(settings_module, "MIDDLEWARE_FIRST", []))
MIDDLEWARE.extend(getattr(settings_module, "MIDDLEWARE", []))
AUTHENTICATION_BACKENDS.extend(getattr(settings_module, "AUTHENTICATION_BACKENDS", []))
SPECTACULAR_SETTINGS.update(getattr(settings_module, "SPECTACULAR_SETTINGS", {}))
REST_FRAMEWORK.update(getattr(settings_module, "REST_FRAMEWORK", {}))
for _attr in dir(settings_module):
if not _attr.startswith("__") and _attr not in _DISALLOWED_ITEMS:
globals()[_attr] = getattr(settings_module, _attr)
@@ -538,26 +566,13 @@ if DEBUG:
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
enable_debug_trace(True)
TENANT_APPS.append("authentik.core")
CONFIG.log("info", "Booting authentik", version=__version__)
# Attempt to load enterprise app, if available
try:
importlib.import_module("authentik.enterprise.apps")
CONFIG.log("info", "Enabled authentik enterprise")
TENANT_APPS.append("authentik.enterprise")
_update_settings("authentik.enterprise.settings")
except ImportError:
pass
# Load subapps's settings
for _app in set(SHARED_APPS + TENANT_APPS):
if not _app.startswith("authentik"):
continue
_update_settings(f"{_app}.settings")
_filter_and_update(SHARED_APPS + TENANT_APPS)
_update_settings("data.user_settings")
MIDDLEWARE = list(OrderedDict.fromkeys(MIDDLEWARE_FIRST + MIDDLEWARE + MIDDLEWARE_LAST))
SHARED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))

View File

@@ -0,0 +1,32 @@
# Generated by Django 5.1.11 on 2025-06-18 09:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_saml", "0019_migrate_usersamlsourceconnection_identifier"),
]
operations = [
migrations.AlterField(
model_name="samlsource",
name="name_id_policy",
field=models.TextField(
choices=[
("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "Email"),
("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", "Persistent"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", "X509"),
(
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"Windows",
),
("urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "Transient"),
("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", "Unspecified"),
],
default="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
help_text="NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent.",
),
),
]

View File

@@ -39,6 +39,7 @@ from authentik.sources.saml.processors.constants import (
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_UNSPECIFIED,
SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509,
SHA1,
@@ -73,6 +74,7 @@ class SAMLNameIDPolicy(models.TextChoices):
X509 = SAML_NAME_ID_FORMAT_X509
WINDOWS = SAML_NAME_ID_FORMAT_WINDOWS
TRANSIENT = SAML_NAME_ID_FORMAT_TRANSIENT
UNSPECIFIED = SAML_NAME_ID_FORMAT_UNSPECIFIED
class SAMLSource(Source):

View File

@@ -4689,6 +4689,14 @@
"format": "uuid"
},
"title": "Roles"
},
"children": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"title": "Children"
}
},
"required": []
@@ -9287,6 +9295,18 @@
"type": "string",
"title": "Default relay state",
"description": "Default relay_state value for IDP-initiated logins"
},
"default_name_id_policy": {
"type": "string",
"enum": [
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
],
"title": "Default name id policy"
}
},
"required": []
@@ -11714,7 +11734,8 @@
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName",
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
],
"title": "Name id policy",
"description": "NameID Policy sent to the IdP. Can be unset, in which case no Policy is sent."

2
go.mod
View File

@@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025064.2
goauthentik.io/api/v3 v3.2025064.3
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0

4
go.sum
View File

@@ -185,8 +185,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.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025064.2 h1:WFXe12hfsRe29EkLCxWCvrdK6peAkCA6ftdEh04hKLg=
goauthentik.io/api/v3 v3.2025064.2/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
goauthentik.io/api/v3 v3.2025064.3 h1:REfDBEjswP2id2WRRDUajRxX+6u+XZ7e/smYq7jw5Z0=
goauthentik.io/api/v3 v3.2025064.3/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=

View File

@@ -17,6 +17,7 @@ type LDAPGroup struct {
Uid string
GidNumber string
Member []string
MemberOf []string
IsSuperuser bool
IsVirtualGroup bool
Attributes map[string]interface{}
@@ -38,6 +39,7 @@ func (lg *LDAPGroup) Entry() *ldap.Entry {
"ak-superuser": {strconv.FormatBool(lg.IsSuperuser)},
"objectClass": objectClass,
"member": lg.Member,
"memberOf": lg.MemberOf,
"cn": {lg.CN},
"uid": {lg.Uid},
"sAMAccountName": {lg.CN},
@@ -52,7 +54,8 @@ func FromAPIGroup(g api.Group, si server.LDAPServerInstance) *LDAPGroup {
CN: g.Name,
Uid: string(g.Pk),
GidNumber: si.GetGroupGidNumber(g),
Member: si.UsersForGroup(g),
Member: si.MembersForGroup(g),
MemberOf: si.MemberOfForGroup(g),
IsVirtualGroup: false,
IsSuperuser: *g.IsSuperuser,
Attributes: g.Attributes,

View File

@@ -155,7 +155,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
if needGroups {
errs.Go(func() error {
gapisp := sentry.StartSpan(errCtx, "authentik.providers.ldap.search.api_group")
searchReq, skip := utils.ParseFilterForGroup(c.CoreApi.CoreGroupsList(gapisp.Context()).IncludeUsers(true), parsedFilter, false)
searchReq, skip := utils.ParseFilterForGroup(c.CoreApi.CoreGroupsList(gapisp.Context()).IncludeUsers(true).IncludeChildren(true), parsedFilter, false)
if skip {
req.Log().Trace("Skip backend request")
return nil

View File

@@ -165,7 +165,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
for _, u := range g.UsersObj {
if flag.UserPk == u.Pk {
// TODO: Is there a better way to clone this object?
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{})
fg := api.NewGroup(g.Pk, g.NumPk, g.Name, g.ParentName, []api.GroupMember{u}, []api.Role{}, []api.GroupChild{})
fg.SetUsers([]int32{flag.UserPk})
if g.Parent.IsSet() {
if p := g.Parent.Get(); p != nil {

View File

@@ -32,7 +32,8 @@ type LDAPServerInstance interface {
GetUserGidNumber(api.User) string
GetGroupGidNumber(api.Group) string
UsersForGroup(api.Group) []string
MembersForGroup(api.Group) []string
MemberOfForGroup(api.Group) []string
GetFlags(dn string) *flags.UserFlags
SetFlags(dn string, flags *flags.UserFlags)

View File

@@ -15,12 +15,27 @@ func (pi *ProviderInstance) GroupsForUser(user api.User) []string {
return groups
}
func (pi *ProviderInstance) UsersForGroup(group api.Group) []string {
func (pi *ProviderInstance) MembersForGroup(group api.Group) []string {
users := make([]string, len(group.UsersObj))
for i, user := range group.UsersObj {
users[i] = pi.GetUserDN(user.Username)
}
return users
children := make([]string, len(group.ChildrenObj))
for i, child := range group.ChildrenObj {
children[i] = pi.GetGroupDN(child.Name)
}
return append(users, children...)
}
func (pi *ProviderInstance) MemberOfForGroup(group api.Group) []string {
if group.ParentName.IsSet() {
parent := group.ParentName.Get()
if parent != nil {
return []string{pi.GetGroupDN(*group.ParentName.Get())}
}
}
return []string{}
}
func (pi *ProviderInstance) GetUserDN(user string) string {

View File

@@ -37,11 +37,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io LDAP outpost, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io LDAP outpost, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik LDAP outpost image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
RUN apt-get update && \
apt-get upgrade -y && \

View File

@@ -186,19 +186,19 @@ class MetricsMiddleware(Middleware):
"The total number of dead-lettered tasks.",
self.labels,
)
self.inprogress_messages = Gauge(
f"{self.prefix}_tasks_inprogress",
self.in_progress_messages = Gauge(
f"{self.prefix}_tasks_in_progress",
"The number of tasks in progress.",
self.labels,
multiprocess_mode="livesum",
)
self.inprogress_delayed_messages = Gauge(
f"{self.prefix}_tasks_delayed_inprogress",
self.in_progress_delayed_messages = Gauge(
f"{self.prefix}_tasks_delayed_in_progress",
"The number of delayed tasks in memory.",
self.labels,
)
self.messages_durations = Histogram(
f"{self.prefix}_tasks_duration_miliseconds",
f"{self.prefix}_tasks_duration_milliseconds",
"The time spent processing tasks.",
self.labels,
buckets=(
@@ -244,15 +244,15 @@ class MetricsMiddleware(Middleware):
def before_delay_message(self, broker: Broker, message: Message):
self.delayed_messages.add(message.message_id)
self.inprogress_delayed_messages.labels(*self._make_labels(message)).inc()
self.in_progress_delayed_messages.labels(*self._make_labels(message)).inc()
def before_process_message(self, broker: Broker, message: Message):
labels = self._make_labels(message)
if message.message_id in self.delayed_messages:
self.delayed_messages.remove(message.message_id)
self.inprogress_delayed_messages.labels(*labels).dec()
self.in_progress_delayed_messages.labels(*labels).dec()
self.inprogress_messages.labels(*labels).inc()
self.in_progress_messages.labels(*labels).inc()
self.message_start_times[message.message_id] = current_millis()
def after_process_message(
@@ -269,7 +269,7 @@ class MetricsMiddleware(Middleware):
message_duration = current_millis() - message_start_time
self.messages_durations.labels(*labels).observe(message_duration)
self.inprogress_messages.labels(*labels).dec()
self.in_progress_messages.labels(*labels).dec()
self.total_messages.labels(*labels).inc()
if exception is not None:
self.total_errored_messages.labels(*labels).inc()

View File

@@ -53,11 +53,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io Proxy outpost image, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io Proxy outpost image, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik proxy outpost image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
RUN apt-get update && \
apt-get upgrade -y && \

View File

@@ -37,11 +37,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io RAC outpost, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io RAC outpost, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik RAC outpost image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
USER root
RUN apt-get update && \

View File

@@ -37,11 +37,16 @@ ARG VERSION
ARG GIT_BUILD_HASH
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
LABEL org.opencontainers.image.url=https://goauthentik.io
LABEL org.opencontainers.image.description="goauthentik.io Radius outpost, see https://goauthentik.io for more info."
LABEL org.opencontainers.image.source=https://github.com/goauthentik/authentik
LABEL org.opencontainers.image.version=${VERSION}
LABEL org.opencontainers.image.revision=${GIT_BUILD_HASH}
LABEL org.opencontainers.image.authors="Authentik Security Inc." \
org.opencontainers.image.description="goauthentik.io Radius outpost, see https://goauthentik.io for more info." \
org.opencontainers.image.documentation="https://docs.goauthentik.io" \
org.opencontainers.image.licenses="https://github.com/goauthentik/authentik/blob/main/LICENSE" \
org.opencontainers.image.revision=${GIT_BUILD_HASH} \
org.opencontainers.image.source="https://github.com/goauthentik/authentik" \
org.opencontainers.image.title="authentik RADIUS outpost image" \
org.opencontainers.image.url="https://goauthentik.io" \
org.opencontainers.image.vendor="Authentik Security Inc." \
org.opencontainers.image.version=${VERSION}
RUN apt-get update && \
apt-get upgrade -y && \

View File

@@ -4718,6 +4718,11 @@ paths:
schema:
type: string
description: Attributes
- in: query
name: include_children
schema:
type: boolean
default: false
- in: query
name: include_users
schema:
@@ -4840,6 +4845,11 @@ paths:
format: uuid
description: A UUID string identifying this Group.
required: true
- in: query
name: include_children
schema:
type: boolean
default: false
- in: query
name: include_users
schema:
@@ -5654,6 +5664,21 @@ paths:
schema:
type: string
description: Attributes
- in: query
name: date_joined
schema:
type: string
format: date-time
- in: query
name: date_joined__gt
schema:
type: string
format: date-time
- in: query
name: date_joined__lt
schema:
type: string
format: date-time
- in: query
name: email
schema:
@@ -5688,6 +5713,21 @@ paths:
name: is_superuser
schema:
type: boolean
- in: query
name: last_updated
schema:
type: string
format: date-time
- in: query
name: last_updated__gt
schema:
type: string
format: date-time
- in: query
name: last_updated__lt
schema:
type: string
format: date-time
- in: query
name: name
schema:
@@ -22282,6 +22322,17 @@ paths:
schema:
type: string
format: uuid
- in: query
name: default_name_id_policy
schema:
type: string
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- in: query
name: default_relay_state
schema:
@@ -29498,6 +29549,7 @@ paths:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
@@ -46466,13 +46518,50 @@ components:
items:
$ref: '#/components/schemas/Role'
readOnly: true
children:
type: array
items:
type: string
format: uuid
children_obj:
type: array
items:
$ref: '#/components/schemas/GroupChild'
readOnly: true
nullable: true
required:
- children_obj
- name
- num_pk
- parent_name
- pk
- roles_obj
- users_obj
GroupChild:
type: object
description: Stripped down group serializer to show relevant children for groups
properties:
pk:
type: string
format: uuid
readOnly: true
title: Group uuid
name:
type: string
is_superuser:
type: boolean
description: Users added to this group will be superusers.
attributes:
type: object
additionalProperties: {}
group_uuid:
type: string
format: uuid
readOnly: true
required:
- group_uuid
- name
- pk
GroupKerberosSourceConnection:
type: object
description: Group Source Connection
@@ -46794,6 +46883,11 @@ components:
items:
type: string
format: uuid
children:
type: array
items:
type: string
format: uuid
required:
- name
GroupSAMLSourceConnection:
@@ -49063,14 +49157,6 @@ components:
- mode
- name
- user_attribute
NameIdPolicyEnum:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
type: string
NetworkBindingEnum:
enum:
- no_binding
@@ -53685,6 +53771,11 @@ components:
items:
type: string
format: uuid
children:
type: array
items:
type: string
format: uuid
PatchedGroupSAMLSourceConnectionRequest:
type: object
description: Group Source Connection
@@ -55289,6 +55380,8 @@ components:
default_relay_state:
type: string
description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
PatchedSAMLSourcePropertyMappingRequest:
type: object
description: SAMLSourcePropertyMapping Serializer
@@ -55382,7 +55475,7 @@ components:
be a security risk, as no validation of the request ID is done.
name_id_policy:
allOf:
- $ref: '#/components/schemas/NameIdPolicyEnum'
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent.
binding_type:
@@ -58130,6 +58223,15 @@ components:
required:
- download_url
- metadata
SAMLNameIDPolicyEnum:
enum:
- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
- urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
- urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
- urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName
- urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
type: string
SAMLPropertyMapping:
type: object
description: SAMLPropertyMapping Serializer
@@ -58347,6 +58449,8 @@ components:
default_relay_state:
type: string
description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
url_download_metadata:
type: string
description: Get metadata download URL
@@ -58519,6 +58623,8 @@ components:
default_relay_state:
type: string
description: Default relay_state value for IDP-initiated logins
default_name_id_policy:
$ref: '#/components/schemas/SAMLNameIDPolicyEnum'
required:
- acs_url
- authorization_flow
@@ -58627,7 +58733,7 @@ components:
be a security risk, as no validation of the request ID is done.
name_id_policy:
allOf:
- $ref: '#/components/schemas/NameIdPolicyEnum'
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent.
binding_type:
@@ -58817,7 +58923,7 @@ components:
be a security risk, as no validation of the request ID is done.
name_id_policy:
allOf:
- $ref: '#/components/schemas/NameIdPolicyEnum'
- $ref: '#/components/schemas/SAMLNameIDPolicyEnum'
description: NameID Policy sent to the IdP. Can be unset, in which case
no Policy is sent.
binding_type:
@@ -61075,11 +61181,16 @@ components:
type: string
format: date-time
readOnly: true
last_updated:
type: string
format: date-time
readOnly: true
required:
- avatar
- date_joined
- groups_obj
- is_superuser
- last_updated
- name
- password_change_date
- pk

98
uv.lock generated
View File

@@ -19,7 +19,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.12.14"
version = "3.12.15"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -30,25 +30,25 @@ dependencies = [
{ name = "propcache" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e39ad954107ebf213a2325038a3e7a506be3d98e1435e1f82086eec4cde2/aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2", size = 7822921, upload-time = "2025-07-10T13:05:33.968Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/06/48/e0d2fa8ac778008071e7b79b93ab31ef14ab88804d7ba71b5c964a7c844e/aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767", size = 695471, upload-time = "2025-07-10T13:04:20.124Z" },
{ url = "https://files.pythonhosted.org/packages/8d/e7/f73206afa33100804f790b71092888f47df65fd9a4cd0e6800d7c6826441/aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e", size = 473128, upload-time = "2025-07-10T13:04:21.928Z" },
{ url = "https://files.pythonhosted.org/packages/df/e2/4dd00180be551a6e7ee979c20fc7c32727f4889ee3fd5b0586e0d47f30e1/aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63", size = 465426, upload-time = "2025-07-10T13:04:24.071Z" },
{ url = "https://files.pythonhosted.org/packages/de/dd/525ed198a0bb674a323e93e4d928443a680860802c44fa7922d39436b48b/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d", size = 1704252, upload-time = "2025-07-10T13:04:26.049Z" },
{ url = "https://files.pythonhosted.org/packages/d8/b1/01e542aed560a968f692ab4fc4323286e8bc4daae83348cd63588e4f33e3/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab", size = 1685514, upload-time = "2025-07-10T13:04:28.186Z" },
{ url = "https://files.pythonhosted.org/packages/b3/06/93669694dc5fdabdc01338791e70452d60ce21ea0946a878715688d5a191/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4", size = 1737586, upload-time = "2025-07-10T13:04:30.195Z" },
{ url = "https://files.pythonhosted.org/packages/a5/3a/18991048ffc1407ca51efb49ba8bcc1645961f97f563a6c480cdf0286310/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026", size = 1786958, upload-time = "2025-07-10T13:04:32.482Z" },
{ url = "https://files.pythonhosted.org/packages/30/a8/81e237f89a32029f9b4a805af6dffc378f8459c7b9942712c809ff9e76e5/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd", size = 1709287, upload-time = "2025-07-10T13:04:34.493Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e3/bd67a11b0fe7fc12c6030473afd9e44223d456f500f7cf526dbaa259ae46/aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88", size = 1622990, upload-time = "2025-07-10T13:04:36.433Z" },
{ url = "https://files.pythonhosted.org/packages/83/ba/e0cc8e0f0d9ce0904e3cf2d6fa41904e379e718a013c721b781d53dcbcca/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086", size = 1676015, upload-time = "2025-07-10T13:04:38.958Z" },
{ url = "https://files.pythonhosted.org/packages/d8/b3/1e6c960520bda094c48b56de29a3d978254637ace7168dd97ddc273d0d6c/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933", size = 1707678, upload-time = "2025-07-10T13:04:41.275Z" },
{ url = "https://files.pythonhosted.org/packages/0a/19/929a3eb8c35b7f9f076a462eaa9830b32c7f27d3395397665caa5e975614/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151", size = 1650274, upload-time = "2025-07-10T13:04:43.483Z" },
{ url = "https://files.pythonhosted.org/packages/22/e5/81682a6f20dd1b18ce3d747de8eba11cbef9b270f567426ff7880b096b48/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8", size = 1726408, upload-time = "2025-07-10T13:04:45.577Z" },
{ url = "https://files.pythonhosted.org/packages/8c/17/884938dffaa4048302985483f77dfce5ac18339aad9b04ad4aaa5e32b028/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3", size = 1759879, upload-time = "2025-07-10T13:04:47.663Z" },
{ url = "https://files.pythonhosted.org/packages/95/78/53b081980f50b5cf874359bde707a6eacd6c4be3f5f5c93937e48c9d0025/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758", size = 1708770, upload-time = "2025-07-10T13:04:49.944Z" },
{ url = "https://files.pythonhosted.org/packages/ed/91/228eeddb008ecbe3ffa6c77b440597fdf640307162f0c6488e72c5a2d112/aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5", size = 421688, upload-time = "2025-07-10T13:04:51.993Z" },
{ url = "https://files.pythonhosted.org/packages/66/5f/8427618903343402fdafe2850738f735fd1d9409d2a8f9bcaae5e630d3ba/aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa", size = 448098, upload-time = "2025-07-10T13:04:53.999Z" },
{ url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" },
{ url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" },
{ url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" },
{ url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" },
{ url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" },
{ url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" },
{ url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" },
{ url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" },
{ url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" },
{ url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" },
{ url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" },
{ url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" },
{ url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" },
{ url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" },
{ url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" },
]
[[package]]
@@ -111,23 +111,23 @@ wheels = [
[[package]]
name = "argon2-cffi-bindings"
version = "21.2.0"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" },
{ url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" },
{ url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" },
{ url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" },
{ url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" },
{ url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" },
{ url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" },
{ url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" },
{ url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" },
{ url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
{ url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
{ url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
{ url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
{ url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
{ url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
{ url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
{ url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
{ url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
]
[[package]]
@@ -557,30 +557,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.39.15"
version = "1.40.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/63/65/ddd4f52d138e52c1345c2d2421281a98449a6e4365290477befe06fa649a/boto3-1.39.15.tar.gz", hash = "sha256:b4483625f0d8c35045254dee46cd3c851bbc0450814f20b9b25bee1b5c0d8409", size = 111856, upload-time = "2025-07-28T19:56:49.504Z" }
sdist = { url = "https://files.pythonhosted.org/packages/48/4d/70d209fdebf0377db233f80dfdf26ca2bc25d2b2e89d4882e0edccd2227f/boto3-1.40.1.tar.gz", hash = "sha256:985ed4bf64729807f870eadbc46ad98baf93096917f7194ec39d743ff75b3f1d", size = 111817, upload-time = "2025-08-01T19:24:18.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/c5/27f50a31317041dc3ad79d62f37d5fcfb3f349c2fba8ea3e81de169db870/boto3-1.39.15-py3-none-any.whl", hash = "sha256:38fc54576b925af0075636752de9974e172c8a2cf7133400e3e09b150d20fb6a", size = 139901, upload-time = "2025-07-28T19:56:47.381Z" },
{ url = "https://files.pythonhosted.org/packages/97/0e/f0cb4f71c40ba07e6ed5b47699a737a080d3c4f4b7b26657d5671de48621/boto3-1.40.1-py3-none-any.whl", hash = "sha256:7c007d5c8ee549e9fcad0927536502da199b27891006ef515330f429aca9671f", size = 139880, upload-time = "2025-08-01T19:24:16.581Z" },
]
[[package]]
name = "botocore"
version = "1.39.15"
version = "1.40.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/e2/8cd9560e7e44cf977dc0cc2e48da7634e78b7104ae6e47f4e1dfc1093965/botocore-1.39.15.tar.gz", hash = "sha256:2aa29a717f14f8c7ca058c2e297aaed0aa10ecea24b91514eee802814d1b7600", size = 14237556, upload-time = "2025-07-28T19:56:39.397Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/d2/d914999f4a128f0f840f2a9cc8327cd98aa661d6b33b331a81a8111ab970/botocore-1.40.1.tar.gz", hash = "sha256:bdf30e2c0e8cdb939d81fc243182a6d1dd39c416694b406c5f2ea079b1c2f3f5", size = 14280398, upload-time = "2025-08-01T19:24:08.599Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/6e/f25b8633e7ab2008de4c27466c9bc39e32dc73816619ffebbea12936135a/botocore-1.39.15-py3-none-any.whl", hash = "sha256:eb9cfe918ebfbfb8654e1b153b29f0c129d586d2c0d7fb4032731d49baf04cff", size = 13894884, upload-time = "2025-07-28T19:56:33.715Z" },
{ url = "https://files.pythonhosted.org/packages/d4/c1/aa7922c9bf74b6d6594d2430af6f854d234faff23187e269aaba89c326c8/botocore-1.40.1-py3-none-any.whl", hash = "sha256:e039774b55fbd6fe59f0f4fea51d156a2433bd4d8faa64fc1b87aee9a03f415d", size = 13940950, upload-time = "2025-08-01T19:24:03.889Z" },
]
[[package]]
@@ -1691,7 +1691,7 @@ wheels = [
[[package]]
name = "jsii"
version = "1.112.0"
version = "1.113.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -1702,9 +1702,9 @@ dependencies = [
{ name = "typeguard" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/3e/270b5236035fc7bb2cdd7f55ea25f85489d35d971870cbec32c3d9e99d7f/jsii-1.112.0.tar.gz", hash = "sha256:6b7d19f361c2565b76828ecbe8cbed8b8d6028a82aa98a46b206a4ee5083157e", size = 624533, upload-time = "2025-05-07T14:45:52.574Z" }
sdist = { url = "https://files.pythonhosted.org/packages/37/9b/ff11800e2edc2860c9eddd7ea7c7a8849f69cbb16b1aae803dae7dafa86e/jsii-1.113.0.tar.gz", hash = "sha256:2dedea9d6006af53467a7a67f1d35a56ab3f75a3d6ed4b4536fffc3e1d1fe476", size = 623541, upload-time = "2025-07-31T12:55:42.888Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/af/8554b632e2b82f37a7422782aba5db2a1fbff4887faa7ec850818def8407/jsii-1.112.0-py3-none-any.whl", hash = "sha256:6510c223074d9b206fd0570849a791e4d9ecfff7ffe68428de73870cea9f55a1", size = 600681, upload-time = "2025-05-07T14:45:51.136Z" },
{ url = "https://files.pythonhosted.org/packages/4f/59/bbbdcc7e0adc32e2362dbb2398949ac013f79dc3468cdf2b5ac411b0f5e8/jsii-1.113.0-py3-none-any.whl", hash = "sha256:62377c651554234ea945693f7c03cb96a969ba425a686950c88d43b0d4d76b07", size = 599669, upload-time = "2025-07-31T12:55:40.874Z" },
]
[[package]]
@@ -2154,42 +2154,42 @@ source = { git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d
[[package]]
name = "opentelemetry-api"
version = "1.35.0"
version = "1.36.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "importlib-metadata" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/99/c9/4509bfca6bb43220ce7f863c9f791e0d5001c2ec2b5867d48586008b3d96/opentelemetry_api-1.35.0.tar.gz", hash = "sha256:a111b959bcfa5b4d7dffc2fbd6a241aa72dd78dd8e79b5b1662bda896c5d2ffe", size = 64778, upload-time = "2025-07-11T12:23:28.804Z" }
sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/5a/3f8d078dbf55d18442f6a2ecedf6786d81d7245844b2b20ce2b8ad6f0307/opentelemetry_api-1.35.0-py3-none-any.whl", hash = "sha256:c4ea7e258a244858daf18474625e9cc0149b8ee354f37843415771a40c25ee06", size = 65566, upload-time = "2025-07-11T12:23:07.944Z" },
{ url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.35.0"
version = "1.36.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9a/cf/1eb2ed2ce55e0a9aa95b3007f26f55c7943aeef0a783bb006bdd92b3299e/opentelemetry_sdk-1.35.0.tar.gz", hash = "sha256:2a400b415ab68aaa6f04e8a6a9f6552908fb3090ae2ff78d6ae0c597ac581954", size = 160871, upload-time = "2025-07-11T12:23:39.566Z" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/4f/8e32b757ef3b660511b638ab52d1ed9259b666bdeeceba51a082ce3aea95/opentelemetry_sdk-1.35.0-py3-none-any.whl", hash = "sha256:223d9e5f5678518f4842311bb73966e0b6db5d1e0b74e35074c052cd2487f800", size = 119379, upload-time = "2025-07-11T12:23:24.521Z" },
{ url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.56b0"
version = "0.57b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/32/8e/214fa817f63b9f068519463d8ab46afd5d03b98930c39394a37ae3e741d0/opentelemetry_semantic_conventions-0.56b0.tar.gz", hash = "sha256:c114c2eacc8ff6d3908cb328c811eaf64e6d68623840be9224dc829c4fd6c2ea", size = 124221, upload-time = "2025-07-11T12:23:40.71Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/3f/e80c1b017066a9d999efffe88d1cce66116dcf5cb7f80c41040a83b6e03b/opentelemetry_semantic_conventions-0.56b0-py3-none-any.whl", hash = "sha256:df44492868fd6b482511cc43a942e7194be64e94945f572db24df2e279a001a2", size = 201625, upload-time = "2025-07-11T12:23:25.63Z" },
{ url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" },
]
[[package]]

View File

@@ -15,6 +15,7 @@ export function addCommands(browser) {
/**
* @this {HTMLElement}
*/
// @ts-ignore
function () {
this.focus();
@@ -28,6 +29,7 @@ export function addCommands(browser) {
/**
* @this {HTMLElement}
*/
// @ts-ignore
function () {
this.blur();

View File

@@ -1,175 +0,0 @@
import { PageFixture } from "#e2e/fixtures/PageFixture";
import type { LocatorContext } from "#e2e/selectors/types";
import { expect, Page } from "@playwright/test";
export class FormFixture extends PageFixture {
static fixtureName = "Form";
//#region Selector Methods
//#endregion
//#region Field Methods
/**
* Set the value of a text input.
*
* @param fieldName The name of the form element.
* @param value the value to set.
*/
public fill = async (
fieldName: string,
value: string,
parent: LocatorContext = this.page,
): Promise<void> => {
const control = parent
.getByRole("textbox", {
name: fieldName,
})
.or(
parent.getByRole("spinbutton", {
name: fieldName,
}),
)
.first();
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
await control.fill(value);
};
/**
* Set the value of a radio or checkbox input.
*
* @param fieldName The name of the form element.
* @param value the value to set.
*/
public setInputCheck = async (
fieldName: string,
value: boolean = true,
parent: LocatorContext = this.page,
): Promise<void> => {
const control = parent.locator("ak-switch-input", {
hasText: fieldName,
});
await control.scrollIntoViewIfNeeded();
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
const currentChecked = await control
.getAttribute("checked")
.then((value) => value !== null);
if (currentChecked === value) {
return;
}
await control.click();
};
/**
* Set the value of a radio or checkbox input.
*
* @param fieldName The name of the form element.
* @param pattern the value to set.
*/
public setRadio = async (
groupName: string,
fieldName: string,
parent: LocatorContext = this.page,
): Promise<void> => {
const group = parent.getByRole("group", { name: groupName });
await expect(group, `Field "${groupName}" should be visible`).toBeVisible();
const control = parent.getByRole("radio", { name: fieldName });
await control.setChecked(true, {
force: true,
});
};
/**
* Set the value of a search select input.
*
* @param fieldLabel The name of the search select element.
* @param pattern The text to match against the search select entry.
*/
public selectSearchValue = async (
fieldLabel: string,
pattern: string | RegExp,
parent: LocatorContext = this.page,
): Promise<void> => {
const control = parent.getByRole("textbox", { name: fieldLabel });
await expect(
control,
`Search select control (${fieldLabel}) should be visible`,
).toBeVisible();
const fieldName = await control.getAttribute("name");
if (!fieldName) {
throw new Error(`Unable to find name attribute on search select (${fieldLabel})`);
}
// Find the search select input control and activate it.
await control.click();
const button = this.page
// ---
.locator(`div[data-managed-for*="${fieldName}"] button`, {
hasText: pattern,
});
if (!button) {
throw new Error(
`Unable to find an ak-search-select entry matching ${fieldLabel}:${pattern.toString()}`,
);
}
await button.click();
await this.page.keyboard.press("Tab");
await control.blur();
};
public setFormGroup = async (
pattern: string | RegExp,
value: boolean = true,
parent: LocatorContext = this.page,
) => {
const control = parent
.locator("ak-form-group", {
hasText: pattern,
})
.first();
const currentOpen = await control.getAttribute("open").then((value) => value !== null);
if (currentOpen === value) {
this.logger.debug(`Form group ${pattern} is already ${value ? "open" : "closed"}`);
return;
}
this.logger.debug(`Toggling form group ${pattern} to ${value ? "open" : "closed"}`);
await control.click();
if (value) {
await expect(control).toHaveAttribute("open");
} else {
await expect(control).not.toHaveAttribute("open");
}
};
//#endregion
//#region Lifecycle
constructor(page: Page, testName: string) {
super({ page, testName });
}
//#endregion
}

View File

@@ -1,30 +0,0 @@
import { ConsoleLogger, FixtureLogger } from "#logger/node";
import { Page } from "@playwright/test";
export interface PageFixtureOptions {
page: Page;
testName: string;
}
export abstract class PageFixture {
/**
* The name of the fixture.
*
* Used for logging.
*/
static fixtureName: string;
protected readonly logger: FixtureLogger;
protected readonly page: Page;
protected readonly testName: string;
constructor({ page, testName }: PageFixtureOptions) {
this.page = page;
this.testName = testName;
const Constructor = this.constructor as typeof PageFixture;
this.logger = ConsoleLogger.fixture(Constructor.fixtureName, this.testName);
}
}

View File

@@ -1,42 +0,0 @@
import { PageFixture } from "#e2e/fixtures/PageFixture";
import type { LocatorContext } from "#e2e/selectors/types";
import { Page } from "@playwright/test";
export type GetByRoleParameters = Parameters<Page["getByRole"]>;
export type ARIARole = GetByRoleParameters[0];
export type ARIAOptions = GetByRoleParameters[1];
export type ClickByName = (name: string) => Promise<void>;
export type ClickByRole = (
role: ARIARole,
options?: ARIAOptions,
context?: LocatorContext,
) => Promise<void>;
export class PointerFixture extends PageFixture {
public static fixtureName = "Pointer";
public click = (
name: string,
optionsOrRole?: ARIAOptions | ARIARole,
context: LocatorContext = this.page,
): Promise<void> => {
if (typeof optionsOrRole === "string") {
return context.getByRole(optionsOrRole, { name }).click();
}
const options = {
...optionsOrRole,
name,
};
return (
context
// ---
.getByRole("button", options)
.or(context.getByRole("link", options))
.click()
);
};
}

View File

@@ -1,119 +0,0 @@
import { PageFixture } from "#e2e/fixtures/PageFixture";
import { expect, Page } from "@playwright/test";
export const GOOD_USERNAME = "test-admin@goauthentik.io";
export const GOOD_PASSWORD = "test-runner";
export const BAD_USERNAME = "bad-username@bad-login.io";
export const BAD_PASSWORD = "-this-is-a-bad-password-";
export interface LoginInit {
username?: string;
password?: string;
to?: URL | string;
}
export class SessionFixture extends PageFixture {
static fixtureName = "Session";
public static readonly pathname = "/if/flow/default-authentication-flow/";
//#region Selectors
public $identificationStage = this.page.locator("ak-stage-identification");
/**
* The username field on the login page.
*/
public $usernameField = this.$identificationStage.locator('input[name="uidField"]');
/**
* The button to continue with the login process,
* typically to the password flow stage.
*/
public $submitUsernameStageButton = this.$identificationStage.locator('button[type="submit"]');
public $passwordStage = this.page.locator("ak-stage-password");
public $passwordField = this.$passwordStage.locator('input[name="password"]');
/**
* The button to submit the the login flow,
* typically redirecting to the authenticated interface.
*/
public $submitPasswordStageButton = this.$passwordStage.locator('button[type="submit"]');
/**
* A possible authentication failure message.
*/
public $authFailureMessage = this.page.locator(".pf-m-error");
//#endregion
constructor(page: Page, testName: string) {
super({ page, testName });
}
//#region Specific interactions
public async submitUsernameStage(username: string) {
this.logger.info("Submitting username stage", username);
await this.$usernameField.fill(username);
await expect(this.$submitUsernameStageButton).toBeEnabled();
await this.$submitUsernameStageButton.click();
}
public async submitPasswordStage(password: string) {
this.logger.info("Submitting password stage");
await this.$passwordField.fill(password);
await expect(this.$submitPasswordStageButton).toBeEnabled();
await this.$submitPasswordStageButton.click();
}
public checkAuthenticated = async (): Promise<boolean> => {
// TODO: Check if the user is authenticated via API
return true;
};
/**
* Log into the application.
*/
public async login({
username = GOOD_USERNAME,
password = GOOD_PASSWORD,
to = SessionFixture.pathname,
}: LoginInit = {}) {
this.logger.info("Logging in...");
const initialURL = new URL(this.page.url());
if (initialURL.pathname === SessionFixture.pathname) {
this.logger.info("Skipping navigation because we're already in a authentication flow");
} else {
await this.page.goto(to.toString());
}
await this.submitUsernameStage(username);
await this.$passwordField.waitFor({ state: "visible" });
await this.submitPasswordStage(password);
const expectedPathname = typeof to === "string" ? to : to.pathname;
await this.page.waitForURL(`**${expectedPathname}`);
}
//#endregion
//#region Navigation
public async toLoginPage() {
await this.page.goto(SessionFixture.pathname);
}
}

View File

@@ -1,56 +0,0 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { createLocatorProxy, DeepLocatorProxy } from "#e2e/elements/proxy";
import { FormFixture } from "#e2e/fixtures/FormFixture";
import { PointerFixture } from "#e2e/fixtures/PointerFixture";
import { SessionFixture } from "#e2e/fixtures/SessionFixture";
import { createOUIDNameEngine } from "#e2e/selectors/ouid";
import { test as base } from "@playwright/test";
export { expect } from "@playwright/test";
type TestIDLocatorProxy = DeepLocatorProxy<TestIDSelectorMap>;
interface E2EFixturesTestScope {
/**
* A proxy to retrieve elements by test ID.
*
* ```ts
* const $button = $.button;
* ```
*/
$: TestIDLocatorProxy;
session: SessionFixture;
pointer: PointerFixture;
form: FormFixture;
}
interface E2EWorkerScope {
selectorRegistration: void;
}
export const test = base.extend<E2EFixturesTestScope, E2EWorkerScope>({
selectorRegistration: [
async ({ playwright }, use) => {
await playwright.selectors.register("ouid", createOUIDNameEngine);
await use();
},
{ auto: true, scope: "worker" },
],
$: async ({ page }, use) => {
await use(createLocatorProxy<TestIDSelectorMap>(page));
},
session: async ({ page }, use, { title }) => {
await use(new SessionFixture(page, title));
},
form: async ({ page }, use, { title }) => {
await use(new FormFixture(page, title));
},
pointer: async ({ page }, use, { title }) => {
await use(new PointerFixture({ page, testName: title }));
},
});

View File

@@ -1,44 +0,0 @@
/* eslint-disable no-console */
type SelectorRoot = Document | ShadowRoot;
export function createOUIDNameEngine() {
const attributeName = "data-ouid-component-name";
console.log("Creating OUID selector engine!!");
return {
// Returns all elements matching given selector in the root's subtree.
queryAll(scope: SelectorRoot, componentName: string) {
const result: Element[] = [];
const match = (element: Element) => {
const name = element.getAttribute(attributeName);
if (name === componentName) {
result.push(element);
}
};
const query = (root: Element | ShadowRoot | Document) => {
const shadows: ShadowRoot[] = [];
if ((root as Element).shadowRoot) {
shadows.push((root as Element).shadowRoot!);
}
for (const element of root.querySelectorAll("*")) {
match(element);
if (element.shadowRoot) {
shadows.push(element.shadowRoot);
}
}
shadows.forEach(query);
};
query(scope);
return result;
},
};
}

View File

@@ -1,13 +0,0 @@
import type { Locator } from "@playwright/test";
export type LocatorContext = Pick<
Locator,
| "locator"
| "getByRole"
| "getByTestId"
| "getByText"
| "getByLabel"
| "getByAltText"
| "getByTitle"
| "getByPlaceholder"
>;

View File

@@ -1,60 +0,0 @@
import { IDGenerator } from "@goauthentik/core/id";
import {
adjectives,
colors,
Config as NameConfig,
uniqueNamesGenerator,
} from "unique-names-generator";
/**
* Given a dictionary of words, slice the dictionary to only include words that start with the given letter.
*/
export function alliterate(dictionary: string[], letter: string): string[] {
let firstIndex = 0;
for (let i = 0; i < dictionary.length; i++) {
if (dictionary[i][0] === letter) {
firstIndex = i;
break;
}
}
let lastIndex = firstIndex;
for (let i = firstIndex; i < dictionary.length; i++) {
if (dictionary[i][0] !== letter) {
lastIndex = i;
break;
}
}
return dictionary.slice(firstIndex, lastIndex);
}
export function createRandomName({
seed = IDGenerator.randomID(),
...config
}: Partial<NameConfig> = {}) {
const randomLetterIndex =
typeof seed === "number"
? seed
: Array.from(seed).reduce((acc, char) => acc + char.charCodeAt(0), 0);
const letter = adjectives[randomLetterIndex % adjectives.length][0];
const availableAdjectives = alliterate(adjectives, letter);
const availableColors = alliterate(colors, letter);
const name = uniqueNamesGenerator({
dictionaries: [availableAdjectives, availableAdjectives, availableColors],
style: "capital",
separator: " ",
length: 3,
seed,
...config,
});
return name;
}

View File

@@ -1,102 +0,0 @@
/**
* Application logger.
*
* @import { LoggerOptions, Logger, Level, ChildLoggerOptions } from "pino"
* @import { PrettyOptions } from "pino-pretty"
*/
import { pino } from "pino";
//#region Constants
/**
* Default options for creating a Pino logger.
*
* @category Logger
* @satisfies {LoggerOptions<never, false>}
*/
export const DEFAULT_PINO_LOGGER_OPTIONS = {
enabled: true,
level: "info",
transport: {
target: "./transport.js",
options: /** @satisfies {PrettyOptions} */ ({
colorize: true,
}),
},
};
//#endregion
//#region Functions
/**
* Read the log level from the environment.
* @return {Level}
*/
export function readLogLevel() {
return process.env.AK_LOG_LEVEL || DEFAULT_PINO_LOGGER_OPTIONS.level;
}
/**
* @typedef {Logger} FixtureLogger
*/
/**
* @this {Logger}
* @param {string} fixtureName
* @param {string} [testName]
* @param {ChildLoggerOptions} [options]
* @returns {FixtureLogger}
*/
function createFixtureLogger(fixtureName, testName, options) {
return this.child(
{ name: fixtureName },
{
msgPrefix: `[${testName}] `,
...options,
},
);
}
/**
* @typedef {object} CustomLoggerMethods
* @property {typeof createFixtureLogger} fixture
*/
/**
* @typedef {Logger & CustomLoggerMethods} ConsoleLogger
*/
/**
* A singleton logger instance for Node.js.
*
* ```js
* import { ConsoleLogger } from "#logger/node";
*
* ConsoleLogger.info("Hello, world!");
* ```
*
* @runtime node
* @type {ConsoleLogger}
*/
export const ConsoleLogger = Object.assign(
pino({
...DEFAULT_PINO_LOGGER_OPTIONS,
level: readLogLevel(),
}),
{ fixture: createFixtureLogger },
);
/**
* @typedef {ReturnType<ConsoleLogger['child']>} ChildConsoleLogger
*/
//#region Aliases
export const info = ConsoleLogger.info.bind(ConsoleLogger);
export const debug = ConsoleLogger.debug.bind(ConsoleLogger);
export const warn = ConsoleLogger.warn.bind(ConsoleLogger);
export const error = ConsoleLogger.error.bind(ConsoleLogger);
//#endregion

View File

@@ -1,22 +0,0 @@
/**
* @file Pretty transport for Pino
*
* @import { PrettyOptions } from "pino-pretty"
*/
import PinoPretty from "pino-pretty";
/**
* @param {PrettyOptions} options
*/
function prettyTransporter(options) {
const pretty = PinoPretty({
...options,
ignore: "pid,hostname",
translateTime: "SYS:HH:MM:ss",
});
return pretty;
}
export default prettyTransporter;

3612
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -94,7 +94,7 @@
"@floating-ui/dom": "^1.7.3",
"@formatjs/intl-listformat": "^7.7.11",
"@fortawesome/fontawesome-free": "^7.0.0",
"@goauthentik/api": "^2025.6.4-1753714826",
"@goauthentik/api": "^2025.6.4-1754241870",
"@goauthentik/core": "^1.0.0",
"@goauthentik/esbuild-plugin-live-reload": "^1.1.0",
"@goauthentik/eslint-config": "^1.0.5",
@@ -128,16 +128,11 @@
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"@wdio/browser-runner": "^9.18.4",
"@wdio/cli": "9.15",
"@wdio/spec-reporter": "^9.15.0",
"@web/test-runner": "^0.20.2",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"change-case": "^5.4.4",
"chart.js": "^4.5.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chromedriver": "^138.0.5",
"codemirror": "^6.0.2",
"construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.44.0",
@@ -184,8 +179,6 @@
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0",
"unist-util-visit": "^5.0.0",
"vite": "^7.0.6",
"vitest": "^3.2.4",
"webcomponent-qr-code": "^1.3.0",
"wireit": "^0.14.12",
"yaml": "^2.8.0"
@@ -196,7 +189,11 @@
"@esbuild/linux-x64": "^0.25.4",
"@rollup/rollup-darwin-arm64": "^4.46.2",
"@rollup/rollup-linux-arm64-gnu": "^4.46.2",
"@rollup/rollup-linux-x64-gnu": "^4.46.2"
"@rollup/rollup-linux-x64-gnu": "^4.46.2",
"@wdio/browser-runner": "^9.18.4",
"@wdio/cli": "^9.18.4",
"@wdio/spec-reporter": "^9.18.0",
"@web/test-runner": "^0.20.2"
},
"wireit": {
"build": {

View File

@@ -1,94 +0,0 @@
/**
* @file Playwright configuration.
*
* @see https://playwright.dev/docs/test-configuration
*
* @import { LogFn, Logger } from "pino"
*/
import { ConsoleLogger } from "#logger/node";
import { defineConfig, devices } from "@playwright/test";
const CI = !!process.env.CI;
/**
* @type {Map<string, Logger>}
*/
const LoggerCache = new Map();
const baseURL = process.env.AK_TEST_RUNNER_PAGE_URL ?? "http://localhost:9000";
export default defineConfig({
testDir: "./test/browser",
fullyParallel: true,
forbidOnly: CI,
retries: CI ? 2 : 0,
workers: CI ? 1 : undefined,
reporter: CI
? "github"
: [
// ---
["list", { printSteps: true }],
["html", { open: "never" }],
],
use: {
testIdAttribute: "data-test-id",
baseURL,
trace: "on-first-retry",
launchOptions: {
logger: {
isEnabled() {
return true;
},
log: (name, severity, message, args) => {
let logger = LoggerCache.get(name);
if (!logger) {
logger = ConsoleLogger.child({
name: `Playwright ${name.toUpperCase()}`,
});
LoggerCache.set(name, logger);
}
/**
* @type {LogFn}
*/
let log;
switch (severity) {
case "verbose":
log = logger.debug;
break;
case "warning":
log = logger.warn;
break;
case "error":
log = logger.error;
break;
default:
log = logger.info;
break;
}
if (name === "api") {
log = logger.debug;
}
log.call(logger, message.toString(), args);
},
},
},
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
],
});

View File

@@ -16,6 +16,7 @@ import {
FlowsInstancesListDesignationEnum,
PropertymappingsApi,
PropertymappingsProviderSamlListRequest,
SAMLNameIDPolicyEnum,
SAMLPropertyMapping,
SAMLProvider,
SpBindingEnum,
@@ -314,6 +315,54 @@ export function renderForm(
"When using IDP-initiated logins, the relay state will be set to this value.",
)}
></ak-text-input>
<ak-form-element-horizontal
label=${msg("Default NameID Policy")}
required
name="defaultNameIdPolicy"
>
<select class="pf-c-form-control">
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
>
${msg("Persistent")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
>
${msg("Email address")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
>
${msg("Windows")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
>
${msg("X509 Subject")}
</option>
<option
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
?selected=${provider?.defaultNameIdPolicy ===
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
>
${msg("Transient")}
</option>
</select>
<p class="pf-c-form__helper-text">
${msg(
"Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).",
)}
</p>
</ak-form-element-horizontal>
<ak-radio-input
name="digestAlgorithm"

View File

@@ -74,8 +74,8 @@ export class InitialPermissionsForm extends ModelForm<InitialPermissions, string
if (query !== undefined) {
args.search = query;
}
const users = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args);
return users.results;
const roles = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args);
return roles.results;
}}
.renderElement=${(role: Role): string => {
return role.name;

View File

@@ -23,7 +23,7 @@ import {
DigestAlgorithmEnum,
FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum,
NameIdPolicyEnum,
SAMLNameIDPolicyEnum,
SAMLSource,
SignatureAlgorithmEnum,
SourcesApi,
@@ -351,37 +351,37 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
>
<select class="pf-c-form-control">
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatPersistent}
>
${msg("Persistent")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatEmailAddress}
>
${msg("Email address")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatWindowsDomainQualifiedName}
>
${msg("Windows")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml11NameidFormatX509SubjectName}
>
${msg("X509 Subject")}
</option>
<option
value=${NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
value=${SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
?selected=${this.instance?.nameIdPolicy ===
NameIdPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
SAMLNameIDPolicyEnum.UrnOasisNamesTcSaml20NameidFormatTransient}
>
${msg("Transient")}
</option>

View File

@@ -1,5 +1,3 @@
/// <reference types="vitest/config" />
import { createBundleDefinitions } from "#bundler/utils/node";
import { inlineCSSPlugin } from "#bundler/vite-plugin-lit-css/node";
@@ -11,41 +9,4 @@ export default defineConfig({
// ---
inlineCSSPlugin(),
],
test: {
dir: "./test",
exclude: [
"**/node_modules/**",
"**/dist/**",
"**/out/**",
"**/.{idea,git,cache,output,temp}/**",
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*",
],
projects: [
{
test: {
include: ["./unit/**/*.{test,spec}.ts", "**/*.unit.{test,spec}.ts"],
name: "unit",
environment: "node",
},
},
{
test: {
setupFiles: ["./test/lit/setup.js"],
include: ["./browser/**/*.{test,spec}.ts", "**/*.browser.{test,spec}.ts"],
name: "browser",
browser: {
enabled: true,
provider: "playwright",
instances: [
{
browser: "chromium",
},
],
},
},
},
],
},
});

View File

@@ -10008,6 +10008,12 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -7886,6 +7886,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -10056,6 +10056,12 @@ El valor de este campo se compara con el atributo de pertenencia del usuario.</t
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -10026,6 +10026,12 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -10012,6 +10012,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -9334,6 +9334,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -9238,6 +9238,12 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -9655,6 +9655,12 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -9664,4 +9664,10 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body></file></xliff>

View File

@@ -9746,6 +9746,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -9719,6 +9719,12 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -6511,6 +6511,12 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -10013,6 +10013,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -7592,6 +7592,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -9314,6 +9314,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s76790480b7b28ad2">
<source>Edit provider</source>
</trans-unit>
<trans-unit id="s9839619155ed2cf6">
<source>Default NameID Policy</source>
</trans-unit>
<trans-unit id="s0c48c5f754275796">
<source>Configure the default NameID Policy used by IDP-initiated logins and when an incoming assertion doesn't specify a NameID Policy (also applies when using a custom NameID Mapping).</source>
</trans-unit>
</body>
</file>

View File

@@ -37,12 +37,13 @@ The following fields are currently sent for users:
- `ak-active`: "true" if the account is active, otherwise "false"
- `ak-superuser`: "true" if the account is part of a group with superuser permissions, otherwise "false"
The following fields are current set for groups:
The following fields are currently set for groups:
- `cn`: The group's name
- `uid`: Unique group identifier
- `gidNumber`: A unique numeric identifier for the group
- `member`: A list of all DNs of the groups members
- `member`: A list of all DNs of the group's members, including groups which have this group as their parent group
- `memberOf`: The DN of the parent group if this group has a parent group
- `objectClass`: A list of these strings:
- "group"
- "goauthentik.io/ldap/group"

View File

@@ -100,10 +100,7 @@ See [Configuration](../configuration/configuration.mdx) to change the internal p
## Startup
:::warning
The server assumes to have local timezone as UTC.
All internals are handled in UTC; whenever a time is displayed to the user in UI, the time shown is localized.
Do not update or mount `/etc/timezone` or `/etc/localtime` in the authentik containers.
This will not give any advantages. It will cause problems with OAuth and SAML authentication, e.g. [see this GitHub issue](https://github.com/goauthentik/authentik/issues/3005).
The server assumes to have local timezone as UTC. All internal operations use UTC; When displaying times in the UI, they are automatically localized for the user. Do not update or mount `/etc/timezone` or `/etc/localtime` in the authentik containers; it will cause problems with OAuth and SAML authentication, as seen this [GitHub issue](https://github.com/goauthentik/authentik/issues/3005).
:::
Afterward, run these commands to finish:

View File

@@ -50,9 +50,9 @@ Instead, the following metrics are now available:
- `authentik_tasks_errors_total`
- `authentik_tasks_retries_total`
- `authentik_tasks_rejected_total`
- `authentik_tasks_inprogress`
- `authentik_tasks_delayed_inprogress`
- `authentik_tasks_duration_miliseconds`
- `authentik_tasks_in_progress`
- `authentik_tasks_delayed_in_progress`
- `authentik_tasks_duration_milliseconds`
## New features

View File

@@ -0,0 +1,157 @@
---
title: Integrate with Home Assistant
sidebar_label: Home Assistant
support_level: community
---
## What is Home Assistant
> Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts. Perfect to run on a Raspberry Pi or a local server.
>
> -- https://www.home-assistant.io/
:::note
To integrate Home Assistant with authentik, a custom integration needs to be installed in Home Assistant.
:::
## Preparation
The following placeholders are used in this guide:
- `hass.company` is the FQDN of the Home Assistant installation.
- `authentik.company` is the FQDN of the authentik installation.
:::note
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## Configuration methods
It is possible to configure Home Assistant to use OIDC or a proxy provider for authentication. Below are the steps to configure each method.
import TabItem from "@theme/TabItem";
import Tabs from "@theme/Tabs";
<Tabs
defaultValue="oidc"
values={[
{ label: "OIDC", value: "oidc" },
{ label: "Proxy Provider", value: "proxy" }
]}
>
<TabItem value="oidc">
## authentik configuration
To support the integration of Home Assistant with authentik, you need to create an application/provider pair in authentik.
### Create an application and provider in authentik
1. Log in to authentik as an administrator, and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **OAuth2/OpenID** as the provider type.
- Note the **Client ID**,**Client Secret**, and **slug** values because they will be required later.
- **Signing Key**: Select any available signing key.
- **Redirect URIs**:
- Strict: `http://hass.company:8123/auth/openid/callback`
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
## Home Assistant configuration
1. Install hass-openid following the instructions at https://github.com/cavefire/hass-openid
2. To support the integration of Home Assistant with authentik, you'll need to update the `configuration.yaml` file of your Home Assistant deployment:
```yaml showLineNumbers title="/config/configuration.yaml"
openid:
client_id: <authentik_client_ID>
client_secret: <authentik_client_secret>
configure_url: "https://authentik.company/application/o/<application_slug>/.well-known/openid-configuration"
scope: "openid profile email"
username_field: "preferred_username"
block_login: false
```
3. Restart Home Assistant
:::note
You must create OIDC users in Home Assistant before they can log in using OIDC.
:::
## Configuration verification
To verify the integration with Home Assistant, log out and attempt to log back in using the **OpenID/OAuth2 authentication** button. You should be redirected to the authenik login page. Once authenticated, you should be redirected to the Home Assistant dashboard.
</TabItem>
<TabItem value="proxy">
:::caution
Using a proxy provider might produce CSRF errors. This is caused by a technology that Home Assistant uses and not authentik. For more information see [this GitHub issue](https://github.com/goauthentik/authentik/issues/884#issuecomment-851542477).
:::
:::caution
Only prefixes starting with `/auth` need to be proxied (excluding prefixes starting with `/auth/token`). See [this GitHub issue](https://github.com/BeryJu/hass-auth-header/issues/212). This can be configured in the reverse proxy (e.g. nginx, Traefik) or in authentik Provider's **Unauthorized Paths**.
:::
## authentik configuration
To support the integration of Home Assistant using `hass-auth-headers` with authentik, you need to create an application/provider pair in authentik.
### Create an application and provider in authentik
1. Log in to authentik as an administrator, and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **Create with Provider** to create an application and provider pair. (Alternatively you can first create a provider separately, then create the application and connect it with the provider.)
- **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
- **Choose a Provider type**: select **Proxy** as the provider type.
- **External Host**: Set this to the external URL you will be accessing Home Assistant from.
- **Internal Host**: `http://hass.company:8123`
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.
3. Click **Submit** to save the new application and provider.
4. Create an outpost deployment for the provider you've created above, as described [here](https://docs.goauthentik.io/docs/add-secure-apps/outposts/). Deploy this Outpost either on the same host or a different host that can access Home Assistant. The outpost will connect to authentik and configure itself.
## Home Assistant configuration
1. Configure [trusted_proxies](https://www.home-assistant.io/integrations/http/#trusted_proxies) for the HTTP integration with the IP(s) of the Host(s) authentik is running on.
2. If you don't already have it set up, https://github.com/BeryJu/hass-auth-header, using the installation guide.
3. There are two ways to configure the custom component:
### Match on user's authentik username
To match on the user's authentik username, use the following configuration:
```yaml
auth_header:
username_header: X-authentik-username
```
### Associate existing Home Assistant username
Alternatively, you can associate an existing Home Assistant username to an authentik username.
1. Within authentik, navigate to **Directory** > **Users**.
2. Select **Edit** for the user then add the following configuration to the **Attributes** section. Be sure to replace `hassusername` with the Home Assistant username.
:::note
This configuration adds an extra header for the authentik user, containing the Home Assistant username, which allows Home Assistant to authenticate the user accordingly.
:::
```yaml
additionalHeaders:
X-ak-hass-user: hassusername
```
3. Then configure the Home Assistant custom component to use this header:
```yaml
auth_header:
username_header: X-ak-hass-user
```
</TabItem>
</Tabs>