Compare commits

...

10 Commits

Author SHA1 Message Date
authentik-automation[bot]
a638558133 release: 2026.5.0-rc1 2026-05-11 15:23:50 +00:00
Marc 'risson' Schmitt
c60537bc28 ci: fix make gen in release workflows (cherry-pick #22235 to version-2026.5) (#22236)
ci: fix make gen in release workflows

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-05-11 16:26:12 +02:00
Marc 'risson' Schmitt
8c89f65ad7 ci: run make gen when tagging a new release (cherry-pick #22229 to version-2026.5) (#22231)
ci: run make gen when tagging a new release

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2026-05-11 14:53:40 +02:00
authentik-automation[bot]
cb07c148f6 core: bump python-kadmin-rs from 0.7.0 to 0.7.1 (cherry-pick #22205 to version-2026.5) (#22227)
core: bump python-kadmin-rs from 0.7.0 to 0.7.1 (#22205)

Bumps [python-kadmin-rs](https://github.com/authentik-community/kadmin-rs) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/authentik-community/kadmin-rs/releases)
- [Commits](https://github.com/authentik-community/kadmin-rs/compare/kadmin/version/0.7.0...kadmin/version/0.7.1)

---
updated-dependencies:
- dependency-name: python-kadmin-rs
  dependency-version: 0.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-11 14:47:01 +02:00
authentik-automation[bot]
b22ce29f8f lifecycle/ak: Add manage support (cherry-pick #22176 to version-2026.5) (#22221)
lifecycle/ak: Add manage support (#22176)

Co-authored-by: Connor Peshek <connor@connorpeshek.me>
2026-05-11 13:10:18 +02:00
authentik-automation[bot]
fd6bf9fd05 providers/saml: make unified saml endpoint (cherry-pick #20026 to version-2026.5) (#22187)
providers/saml: make unified saml endpoint (#20026)

* providers/saml: make unified saml endpoint

Co-authored-by: Connor Peshek <connor@connorpeshek.me>
2026-05-09 10:48:50 -05:00
authentik-automation[bot]
5e0448b541 providers/saml: make issuer url metadata url (cherry-pick #22178 to version-2026.5) (#22184)
providers/saml: make issuer url metadata url (#22178)

Co-authored-by: Connor Peshek <connor@connorpeshek.me>
2026-05-09 15:36:26 +02:00
authentik-automation[bot]
52c37992bd tenants: fix system flags removeable (cherry-pick #22163 to version-2026.5) (#22182)
tenants: fix system flags removeable (#22163)

* tenants: fix system flags removeable



* lint and fix test



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-05-09 15:14:01 +02:00
authentik-automation[bot]
45b78f78b8 web/admin: legacy modal fixes and fix log viewer in form layout (cherry-pick #22168 to version-2026.5) (#22173)
web/admin: legacy modal fixes and fix log viewer in form layout (#22168)

* web/admin: fix log-viewer layout again

I thought I only recently fixed this...?



* switch closeAfterSuccessfulSubmit -> keepOpenAfterSubmit with correct attribute name and false as default



---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens L. <jens@goauthentik.io>
2026-05-09 00:24:15 +02:00
authentik-automation[bot]
7900399a97 blueprints: fix mismatched API schema and implementation (cherry-pick #22087 to version-2026.5) (#22171)
blueprints: fix mismatched API schema and implementation (#22087)

align blueprint import schema with 200 result response

Co-authored-by: Marcelo Elizeche Landó <marcelo@goauthentik.io>
2026-05-08 20:58:06 +02:00
29 changed files with 312 additions and 154 deletions

View File

@@ -68,6 +68,8 @@ jobs:
token: ${{ steps.generate_token.outputs.token }}
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: "system,python,go,node,runtime,rust-nightly"
- name: Run migrations
run: make migrate
- name: Bump version

View File

@@ -82,10 +82,14 @@ jobs:
token: "${{ steps.app-token.outputs.token }}"
- name: Setup authentik env
uses: ./.github/actions/setup
with:
dependencies: "system,python,go,node,runtime,rust-nightly"
- name: Run migrations
run: make migrate
- name: Bump version
run: "make bump version=${{ inputs.version }}"
- name: Re-generate API Clients
run: make gen
- name: Commit and push
run: |
# ID from https://api.github.com/users/authentik-automation[bot]

View File

@@ -217,10 +217,7 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
request={"multipart/form-data": BlueprintUploadSerializer},
responses={
204: BlueprintImportResultSerializer,
400: BlueprintImportResultSerializer,
},
responses={200: BlueprintImportResultSerializer},
)
@action(url_path="import", detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
@validate(
@@ -247,21 +244,13 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
import_response = self.BlueprintImportResultSerializer(
data={
"logs": [],
"success": False,
"logs": [LogEventSerializer(log).data for log in logs],
"success": valid,
}
)
import_response.is_valid(raise_exception=True)
import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs]
import_response.initial_data["success"] = valid
import_response.is_valid()
if not valid:
return Response(data=import_response.initial_data, status=200)
successful = importer.apply()
import_response.initial_data["success"] = successful
import_response.is_valid()
if not successful:
return Response(data=import_response.initial_data, status=200)
if valid:
import_response.initial_data["success"] = importer.apply()
import_response.is_valid()
return Response(data=import_response.initial_data, status=200)

View File

@@ -3,6 +3,7 @@
from json import dumps, loads
from tempfile import NamedTemporaryFile, mkdtemp
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from rest_framework.test import APITestCase
from yaml import dump
@@ -141,6 +142,20 @@ class TestBlueprintsV1API(APITestCase):
)
self.assertEqual(res.status_code, 200)
def test_api_import_invalid_blueprint_returns_result_payload(self):
"""Invalid blueprint content returns a result payload instead of a 400 response."""
file = SimpleUploadedFile("invalid-blueprint.yaml", b'{"version": 3}')
res = self.client.post(
reverse("authentik_api:blueprintinstance-import-"),
data={"file": file},
format="multipart",
)
self.assertEqual(res.status_code, 200)
self.assertFalse(res.json()["success"])
self.assertGreater(len(res.json()["logs"]), 0)
def test_api_import_unknown_path(self):
"""Path not in available blueprints is rejected (covers api.py:56)."""
res = self.client.post(

View File

@@ -61,6 +61,11 @@ class SAMLProviderSerializer(ProviderSerializer):
url_download_metadata = SerializerMethodField()
url_issuer = SerializerMethodField()
# Unified SAML endpoint (primary)
url_unified = SerializerMethodField()
url_unified_init = SerializerMethodField()
# Legacy endpoints (for backward compatibility)
url_sso_post = SerializerMethodField()
url_sso_redirect = SerializerMethodField()
url_sso_init = SerializerMethodField()
@@ -100,13 +105,43 @@ class SAMLProviderSerializer(ProviderSerializer):
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_saml:base",
"authentik_providers_saml:metadata-download",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist:
return DEFAULT_ISSUER
def get_url_unified(self, instance: SAMLProvider) -> str:
"""Get unified SAML endpoint URL (handles SSO and SLO)"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_saml:base",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist:
return "-"
def get_url_unified_init(self, instance: SAMLProvider) -> str:
"""Get IdP-initiated SAML URL"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_saml:init",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist:
return "-"
def get_url_sso_post(self, instance: SAMLProvider) -> str:
"""Get SSO Post URL"""
if "request" not in self._context:
@@ -243,6 +278,8 @@ class SAMLProviderSerializer(ProviderSerializer):
"default_name_id_policy",
"url_download_metadata",
"url_issuer",
"url_unified",
"url_unified_init",
"url_sso_post",
"url_sso_redirect",
"url_sso_init",

View File

@@ -241,7 +241,7 @@ class SAMLProvider(Provider):
"""Use IDP-Initiated SAML flow as launch URL"""
try:
return reverse(
"authentik_providers_saml:sso-init",
"authentik_providers_saml:init",
kwargs={"application_slug": self.application.slug},
)
except Provider.application.RelatedObjectDoesNotExist:

View File

@@ -147,7 +147,7 @@ class AssertionProcessor:
return self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:base",
"authentik_providers_saml:metadata-download",
kwargs={"application_slug": self.provider.application.slug},
)
)

View File

@@ -48,7 +48,7 @@ class MetadataProcessor:
return self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:base",
"authentik_providers_saml:metadata-download",
kwargs={"application_slug": self.provider.application.slug},
)
)
@@ -81,54 +81,35 @@ class MetadataProcessor:
element.text = name_id_format
yield element
def _get_unified_url(self) -> str:
"""Get the unified SAML endpoint URL"""
return self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:base",
kwargs={"application_slug": self.provider.application.slug},
)
)
def get_sso_bindings(self) -> Iterator[Element]:
"""Get all Bindings supported"""
binding_url_map = {
(SAML_BINDING_REDIRECT, "SingleSignOnService"): self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:sso-redirect",
kwargs={"application_slug": self.provider.application.slug},
)
),
(SAML_BINDING_POST, "SingleSignOnService"): self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:sso-post",
kwargs={"application_slug": self.provider.application.slug},
)
),
}
for binding_svc, url in binding_url_map.items():
binding, svc = binding_svc
"""Get all SSO Bindings - both point to unified endpoint"""
unified_url = self._get_unified_url()
for binding in [SAML_BINDING_REDIRECT, SAML_BINDING_POST]:
if self.force_binding and self.force_binding != binding:
continue
element = Element(f"{{{NS_SAML_METADATA}}}{svc}")
element = Element(f"{{{NS_SAML_METADATA}}}SingleSignOnService")
element.attrib["Binding"] = binding
element.attrib["Location"] = url
element.attrib["Location"] = unified_url
yield element
def get_slo_bindings(self) -> Iterator[Element]:
"""Get all Bindings supported"""
binding_url_map = {
(SAML_BINDING_REDIRECT, "SingleLogoutService"): self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:slo-redirect",
kwargs={"application_slug": self.provider.application.slug},
)
),
(SAML_BINDING_POST, "SingleLogoutService"): self.http_request.build_absolute_uri(
reverse(
"authentik_providers_saml:slo-post",
kwargs={"application_slug": self.provider.application.slug},
)
),
}
for binding_svc, url in binding_url_map.items():
binding, svc = binding_svc
"""Get all SLO Bindings - both point to unified endpoint"""
unified_url = self._get_unified_url()
for binding in [SAML_BINDING_REDIRECT, SAML_BINDING_POST]:
if self.force_binding and self.force_binding != binding:
continue
element = Element(f"{{{NS_SAML_METADATA}}}{svc}")
element = Element(f"{{{NS_SAML_METADATA}}}SingleLogoutService")
element.attrib["Binding"] = binding
element.attrib["Location"] = url
element.attrib["Location"] = unified_url
yield element
def _prepare_signature(self, entity_descriptor: _Element):

View File

@@ -4,19 +4,26 @@ from django.urls import path
from authentik.providers.saml.api.property_mappings import SAMLPropertyMappingViewSet
from authentik.providers.saml.api.providers import SAMLProviderViewSet
from authentik.providers.saml.views import metadata, sso
from authentik.providers.saml.views import metadata, sso, unified
from authentik.providers.saml.views.sp_slo import (
SPInitiatedSLOBindingPOSTView,
SPInitiatedSLOBindingRedirectView,
)
urlpatterns = [
# Base path for Issuer/Entity ID
# Unified Endpoint - handles SSO and SLO based on message type
path(
"<slug:application_slug>/",
sso.SAMLSSOBindingRedirectView.as_view(),
unified.SAMLUnifiedView.as_view(),
name="base",
),
# IdP-initiated
path(
"<slug:application_slug>/init/",
sso.SAMLSSOBindingInitView.as_view(),
name="init",
),
# LEGACY Endpoints (backward compatibility)
# SSO Bindings
path(
"<slug:application_slug>/sso/binding/redirect/",

View File

@@ -0,0 +1,118 @@
"""Unified SAML endpoint - handles SSO and SLO based on message type"""
from base64 import b64decode
from defusedxml.lxml import fromstring
from django.http import HttpRequest, HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger
from authentik.common.saml.constants import NS_MAP
from authentik.flows.views.executor import SESSION_KEY_POST
from authentik.lib.views import bad_request_message
from authentik.providers.saml.utils.encoding import decode_base64_and_inflate
from authentik.providers.saml.views.flows import (
REQUEST_KEY_SAML_REQUEST,
REQUEST_KEY_SAML_RESPONSE,
)
from authentik.providers.saml.views.sp_slo import (
SPInitiatedSLOBindingPOSTView,
SPInitiatedSLOBindingRedirectView,
)
from authentik.providers.saml.views.sso import (
SAMLSSOBindingPOSTView,
SAMLSSOBindingRedirectView,
)
LOGGER = get_logger()
# SAML message type constants
SAML_MESSAGE_TYPE_AUTHN_REQUEST = "AuthnRequest"
SAML_MESSAGE_TYPE_LOGOUT_REQUEST = "LogoutRequest"
def detect_saml_message_type(saml_request: str, is_post_binding: bool) -> str | None:
"""Parse SAML request to determine if AuthnRequest or LogoutRequest."""
try:
if is_post_binding:
decoded_xml = b64decode(saml_request.encode())
else:
decoded_xml = decode_base64_and_inflate(saml_request)
root = fromstring(decoded_xml)
if len(root.xpath("//samlp:AuthnRequest", namespaces=NS_MAP)):
return SAML_MESSAGE_TYPE_AUTHN_REQUEST
if len(root.xpath("//samlp:LogoutRequest", namespaces=NS_MAP)):
return SAML_MESSAGE_TYPE_LOGOUT_REQUEST
return None
except Exception: # noqa: BLE001
return None
@method_decorator(xframe_options_sameorigin, name="dispatch")
@method_decorator(csrf_exempt, name="dispatch")
class SAMLUnifiedView(View):
"""Unified SAML endpoint - handles SSO and SLO based on message type.
The operation type is determined by parsing
the incoming SAML message:
- AuthnRequest -> SSO flow (delegates to SAMLSSOBindingRedirectView/POSTView)
- LogoutRequest -> SLO flow (delegates to SPInitiatedSLOBindingRedirectView/POSTView)
- LogoutResponse -> SLO completion (delegates to SPInitiatedSLOBindingRedirectView/POSTView)
"""
def dispatch(self, request: HttpRequest, application_slug: str) -> HttpResponse:
"""Route the request based on SAML message type."""
# ak user was not logged in, redirected to login, and is back w POST payload in session
if SESSION_KEY_POST in request.session:
return self._delegate_to_sso(request, application_slug, is_post_binding=True)
# Determine binding from HTTP method
is_post_binding = request.method == "POST"
data = request.POST if is_post_binding else request.GET
# LogoutResponse - delegate to SLO view (handles it in dispatch)
if REQUEST_KEY_SAML_RESPONSE in data:
return self._delegate_to_slo(request, application_slug, is_post_binding)
# Check for SAML request
if REQUEST_KEY_SAML_REQUEST not in data:
LOGGER.info("SAML payload missing")
return bad_request_message(request, "The SAML request payload is missing.")
# Detect message type and delegate
saml_request = data[REQUEST_KEY_SAML_REQUEST]
message_type = detect_saml_message_type(saml_request, is_post_binding)
if message_type == SAML_MESSAGE_TYPE_AUTHN_REQUEST:
return self._delegate_to_sso(request, application_slug, is_post_binding)
elif message_type == SAML_MESSAGE_TYPE_LOGOUT_REQUEST:
return self._delegate_to_slo(request, application_slug, is_post_binding)
else:
LOGGER.warning("Unknown SAML message type", message_type=message_type)
return bad_request_message(
request, f"Unsupported SAML message type: {message_type or 'unknown'}"
)
def _delegate_to_sso(
self, request: HttpRequest, application_slug: str, is_post_binding: bool
) -> HttpResponse:
"""Delegate to the appropriate SSO view."""
if is_post_binding:
view = SAMLSSOBindingPOSTView.as_view()
else:
view = SAMLSSOBindingRedirectView.as_view()
return view(request, application_slug=application_slug)
def _delegate_to_slo(
self, request: HttpRequest, application_slug: str, is_post_binding: bool
) -> HttpResponse:
"""Delegate to the appropriate SLO view."""
if is_post_binding:
view = SPInitiatedSLOBindingPOSTView.as_view()
else:
view = SPInitiatedSLOBindingRedirectView.as_view()
return view(request, application_slug=application_slug)

View File

@@ -19,6 +19,12 @@ from authentik.tenants.models import Tenant
class FlagJSONField(JSONDictField):
def to_internal_value(self, data: str):
flags = super().to_internal_value(data)
for flag in Flag.available(visibility="system", exclude_system=False):
flags[flag().key] = flag.get()
return flags
def to_representation(self, value: dict) -> dict:
new_value = value.copy()
for flag in Flag.available(exclude_system=False):
@@ -33,13 +39,10 @@ class FlagJSONField(JSONDictField):
def run_validators(self, value: dict):
super().run_validators(value)
for flag in Flag.available(exclude_system=False):
for flag in Flag.available():
_flag = flag()
if _flag.key not in value:
continue
if _flag.visibility == "system":
value.pop(_flag.key, None)
continue
flag_value = value.get(_flag.key)
flag_type = get_args(_flag.__orig_bases__[0])[0]
if flag_value and not isinstance(flag_value, flag_type):

View File

@@ -85,10 +85,30 @@ class TestLocalSettingsAPI(APITestCase):
"flags": {"tenants_test_flag_sys": 123},
},
)
print(response.content)
self.assertEqual(response.status_code, 200)
self.tenant.refresh_from_db()
self.assertEqual(self.tenant.flags, {})
self.assertEqual(self.tenant.flags, {"setup": False, "tenants_test_flag_sys": False})
def test_settings_flags_system_empty_put(self):
"""Test settings API"""
self.tenant.flags = {}
self.tenant.save()
class _TestFlag(Flag[bool], key="tenants_test_flag_sys"):
default = False
visibility = "system"
self.client.force_login(self.local_admin)
response = self.client.patch(
reverse("authentik_api:tenant_settings"),
data={
"flags": {},
},
)
self.assertEqual(response.status_code, 200)
self.tenant.refresh_from_db()
self.assertEqual(self.tenant.flags, {"setup": False, "tenants_test_flag_sys": False})
def test_command(self):
self.tenant.flags = {}

View File

@@ -38,6 +38,10 @@ function run_authentik {
echo cargo run -- "$@"
fi
;;
manage)
shift 1
echo python -m manage "$@"
;;
*)
echo "$@"
;;

View File

@@ -266,6 +266,18 @@ export interface SAMLProvider {
* @memberof SAMLProvider
*/
readonly urlIssuer: string;
/**
* Get unified SAML endpoint URL (handles SSO and SLO)
* @type {string}
* @memberof SAMLProvider
*/
readonly urlUnified: string;
/**
* Get IdP-initiated SAML URL
* @type {string}
* @memberof SAMLProvider
*/
readonly urlUnifiedInit: string;
/**
* Get SSO Post URL
* @type {string}
@@ -328,6 +340,8 @@ export function instanceOfSAMLProvider(value: object): value is SAMLProvider {
if (!("urlDownloadMetadata" in value) || value["urlDownloadMetadata"] === undefined)
return false;
if (!("urlIssuer" in value) || value["urlIssuer"] === undefined) return false;
if (!("urlUnified" in value) || value["urlUnified"] === undefined) return false;
if (!("urlUnifiedInit" in value) || value["urlUnifiedInit"] === undefined) return false;
if (!("urlSsoPost" in value) || value["urlSsoPost"] === undefined) return false;
if (!("urlSsoRedirect" in value) || value["urlSsoRedirect"] === undefined) return false;
if (!("urlSsoInit" in value) || value["urlSsoInit"] === undefined) return false;
@@ -414,6 +428,8 @@ export function SAMLProviderFromJSONTyped(json: any, ignoreDiscriminator: boolea
: SAMLNameIDPolicyEnumFromJSON(json["default_name_id_policy"]),
urlDownloadMetadata: json["url_download_metadata"],
urlIssuer: json["url_issuer"],
urlUnified: json["url_unified"],
urlUnifiedInit: json["url_unified_init"],
urlSsoPost: json["url_sso_post"],
urlSsoRedirect: json["url_sso_redirect"],
urlSsoInit: json["url_sso_init"],
@@ -440,6 +456,8 @@ export function SAMLProviderToJSONTyped(
| "meta_model_name"
| "url_download_metadata"
| "url_issuer"
| "url_unified"
| "url_unified_init"
| "url_sso_post"
| "url_sso_redirect"
| "url_sso_init"

View File

@@ -53,7 +53,7 @@ dependencies = [
"pydantic==2.13.3",
"pyjwt==2.11.0",
"pyrad==2.5.4",
"python-kadmin-rs==0.7.0",
"python-kadmin-rs==0.7.1",
"pyyaml==6.0.3",
"requests-oauthlib==2.0.0",
"scim2-filter-parser==0.7.0",

View File

@@ -9678,18 +9678,14 @@ paths:
security:
- authentik: []
responses:
'204':
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintImportResult'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintImportResult'
description: ''
$ref: '#/components/responses/ValidationErrorResponse'
'403':
$ref: '#/components/responses/GenericErrorResponse'
/oauth2/access_tokens/:
@@ -54332,6 +54328,14 @@ components:
type: string
description: Get Issuer/EntityID URL
readOnly: true
url_unified:
type: string
description: Get unified SAML endpoint URL (handles SSO and SLO)
readOnly: true
url_unified_init:
type: string
description: Get IdP-initiated SAML URL
readOnly: true
url_sso_post:
type: string
description: Get SSO Post URL
@@ -54371,6 +54375,8 @@ components:
- url_sso_init
- url_sso_post
- url_sso_redirect
- url_unified
- url_unified_init
- verbose_name
- verbose_name_plural
SAMLProviderImportRequest:

22
uv.lock generated
View File

@@ -362,7 +362,7 @@ requires-dist = [
{ name = "pydantic-scim", specifier = "==0.0.8" },
{ name = "pyjwt", specifier = "==2.11.0" },
{ name = "pyrad", specifier = "==2.5.4" },
{ name = "python-kadmin-rs", specifier = "==0.7.0" },
{ name = "python-kadmin-rs", specifier = "==0.7.1" },
{ name = "pyyaml", specifier = "==6.0.3" },
{ name = "requests-oauthlib", specifier = "==2.0.0" },
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
@@ -3083,18 +3083,18 @@ wheels = [
[[package]]
name = "python-kadmin-rs"
version = "0.7.0"
version = "0.7.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/18/2773570703e5ab13fc0390797685cb6c09b8002d96438c57a8e887cc3234/python_kadmin_rs-0.7.0.tar.gz", hash = "sha256:e8a539fda1a1006fe5f0868c0e59a36b3b90d451da9c0c2bc3a9bfc7173efbdc", size = 112469, upload-time = "2026-01-15T17:49:10.467Z" }
sdist = { url = "https://files.pythonhosted.org/packages/b7/ea/1ab22665fbf44a78553f0c9fccece5d001f2592a33cf90ac8f1033e2da93/python_kadmin_rs-0.7.1.tar.gz", hash = "sha256:44a4eff8367d8560babdc42915d706886c345cff00b00b562e8857aa3f62f632", size = 118400, upload-time = "2026-05-06T17:02:26.445Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/05/94e7575a69ea5d3fc23d4df4a8e4d5acb6f6d3633f23b0a8b6b6360da775/python_kadmin_rs-0.7.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d1418825ba6c161d504b7905a99ef475d5ec1fdf15e6f5b72e4641f350fbc261", size = 510261, upload-time = "2026-01-15T17:48:52.002Z" },
{ url = "https://files.pythonhosted.org/packages/d7/16/58671c341caef38a492e327cf3e0b24aba2842419da15566f8e3d42c9382/python_kadmin_rs-0.7.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b247bc5f5a075107088cdcec22c67125aa6706fdcd2e264a99a478f1bedecd7d", size = 527751, upload-time = "2026-01-15T17:48:53.504Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d1/505e34ce204601aae0fcecaf56c66e808803426199948d3a26a6c16a9e5b/python_kadmin_rs-0.7.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:8e6d8ea17a02bb0527219abadac08a63a47f97351f41c79fade77dd11a380795", size = 552634, upload-time = "2026-01-15T17:48:54.96Z" },
{ url = "https://files.pythonhosted.org/packages/0b/51/391a3d8ee99aeb2466efe499e52ef6a7479d7ac426635d92cd050a5fe3f9/python_kadmin_rs-0.7.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:82107ee5ea3dc1a3b716323687febc64ed2fa462ebd986565fba7394add04792", size = 554659, upload-time = "2026-01-15T17:48:56.408Z" },
{ url = "https://files.pythonhosted.org/packages/c2/77/6a2fe8a9bef6e3d94f842492db7216c4d0a47c5a67a8a7265c126ed5be58/python_kadmin_rs-0.7.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:ed58ec35dd89a381408fa92f0404d6321f2e6687c58c974f820f113a7052f39f", size = 512638, upload-time = "2026-01-15T17:48:58.519Z" },
{ url = "https://files.pythonhosted.org/packages/ef/e4/ddd909d4b5ff00a3ed277699f3e2204785367a52088dcb41465b8e01f733/python_kadmin_rs-0.7.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:6a6b63680e10a450e553a84a15216f61af838d86d623caec1fb1c2977907d1ef", size = 530752, upload-time = "2026-01-15T17:49:00.108Z" },
{ url = "https://files.pythonhosted.org/packages/fd/b2/7d4ea81b768a4ea6be57d9bc70f1841828483a092598b60243a7ad8c798c/python_kadmin_rs-0.7.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e48cdf80bdece9fdcc70d9ef9237821ae9366cf7944742cd412ac2ebd07a40cc", size = 553270, upload-time = "2026-01-15T17:49:01.682Z" },
{ url = "https://files.pythonhosted.org/packages/26/b7/87851916c895f31e67a9fe827dabfe3a2f09cf8ecf090cb4ac513f100157/python_kadmin_rs-0.7.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:e63aec5daa1a8469f5b617aa8a5b5a689e2b18241026c7e666ca0f8b5e8688c8", size = 556308, upload-time = "2026-01-15T17:49:03.199Z" },
{ url = "https://files.pythonhosted.org/packages/7f/76/4b79d6e713dfc58301de1541cce5a40ced6775331fd424213da23c4d4260/python_kadmin_rs-0.7.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3ce3de9a085f291ead94d35e98612d3dbe00adfc086bd56b6dadf4a63a3a1a92", size = 511621, upload-time = "2026-05-06T17:02:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/ec/ac/a9f42a4a636d9ff450dca96b02cf0aa88178c52370acdbc4cf5b66e68553/python_kadmin_rs-0.7.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:f5019f93a4cc053857ecb626f9ee94f355e07f0dbc2206530b2fb966eb9bc95b", size = 526488, upload-time = "2026-05-06T17:02:11.606Z" },
{ url = "https://files.pythonhosted.org/packages/d1/54/ab16c09ec8f6333da5dc2f47dd450b9a6157dd6082b31510f7c2882f8fe5/python_kadmin_rs-0.7.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:4accbbc15219b21aeca6ca92105681af5768c547b354c6121e7d9f12de1e00ca", size = 562115, upload-time = "2026-05-06T17:02:12.946Z" },
{ url = "https://files.pythonhosted.org/packages/31/80/7993995e9cc960f075cc0df54668a29872f33541ddae29fe7daf3f02a3db/python_kadmin_rs-0.7.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0af1deb03a38731114e449cb7993a56cdbd833a1c9539e04254dace2e42bdaa7", size = 559660, upload-time = "2026-05-06T17:02:14.363Z" },
{ url = "https://files.pythonhosted.org/packages/3e/a7/cd5d4bdf3c55882b71ee1276f14366c45c4385641ea712695e851b7376df/python_kadmin_rs-0.7.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0ab294659e82682262c3d7c1af26457ac659cf44cbaf95edf9a47981081c7522", size = 512555, upload-time = "2026-05-06T17:02:16.032Z" },
{ url = "https://files.pythonhosted.org/packages/23/8c/d161f95823de781df926baa520ca16d246a12d81750c6b117113fd698d83/python_kadmin_rs-0.7.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2b938489ef2baa47ac1ee9cee034ea7c9893c929dfa7917f84e29860d7673804", size = 522912, upload-time = "2026-05-06T17:02:17.647Z" },
{ url = "https://files.pythonhosted.org/packages/69/db/84d649a3599b2a6fc7727e1d98eb222364c14a1b2661681e6befd28c1cef/python_kadmin_rs-0.7.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:af91a0b7af18f44fbe11e7b425c787f2d9207bb464072fbb9850bad3dc48413a", size = 560187, upload-time = "2026-05-06T17:02:19.228Z" },
{ url = "https://files.pythonhosted.org/packages/92/9e/8688cf98aef625c2ccb3df8fc64f5c378caad53f3c213a35cf7c8b2c40ae/python_kadmin_rs-0.7.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d174237a19675e221d56e2e8d0faeedf32cf525d9a654b1c7f70c8436c8e3db0", size = 559623, upload-time = "2026-05-06T17:02:20.882Z" },
]
[[package]]

View File

@@ -123,13 +123,7 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Log messages")}>
<div class="pf-c-form__group-label">
<div class="pf-c-form__horizontal-group ak-policy-test-log-messages">
<dl class="pf-c-description-list pf-m-horizontal">
<ak-log-viewer .items=${this.result?.logMessages}></ak-log-viewer>
</dl>
</div>
</div>
<ak-log-viewer .items=${this.result?.logMessages}></ak-log-viewer>
</ak-form-element-horizontal>`;
}

View File

@@ -31,7 +31,7 @@ export class GoogleWorkspaceProviderGroupList extends Table<GoogleWorkspaceProvi
clearOnRefresh = true;
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Sync")}</span>
<span slot="header">${msg("Sync Group")}</span>
<ak-sync-object-form

View File

@@ -32,7 +32,7 @@ export class GoogleWorkspaceProviderUserList extends Table<GoogleWorkspaceProvid
clearOnRefresh = true;
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Sync")}</span>
<span slot="header">${msg("Sync User")}</span>
<ak-sync-object-form

View File

@@ -28,7 +28,7 @@ export class MicrosoftEntraProviderGroupList extends Table<MicrosoftEntraProvide
protected override searchEnabled = true;
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Sync")}</span>
<span slot="header">${msg("Sync Group")}</span>
<ak-sync-object-form

View File

@@ -32,7 +32,7 @@ export class MicrosoftEntraProviderUserList extends Table<MicrosoftEntraProvider
clearOnRefresh = true;
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Sync")}</span>
<span slot="header">${msg("Sync User")}</span>
<ak-sync-object-form

View File

@@ -391,28 +391,20 @@ export class SAMLProviderViewPage extends AKElement {
<div class="pf-c-form__group">
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("SSO URL (Post)")}</span
>${msg("SAML Endpoint")}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${ifDefined(this.provider.urlSsoPost)}"
/>
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("SSO URL (Redirect)")}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${ifDefined(this.provider.urlSsoRedirect)}"
value="${ifDefined(this.provider.urlUnified)}"
/>
<p class="pf-c-form__helper-text">
${msg(
"SAML provider endpoint. Use this URL for SP configuration.",
)}
</p>
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label">
@@ -424,33 +416,7 @@ export class SAMLProviderViewPage extends AKElement {
class="pf-c-form-control"
readonly
type="text"
value="${ifDefined(this.provider.urlSsoInit)}"
/>
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("SLO URL (Post)")}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${ifDefined(this.provider.urlSloPost)}"
/>
</div>
<div class="pf-c-form__group">
<label class="pf-c-form__label">
<span class="pf-c-form__label-text"
>${msg("SLO URL (Redirect)")}</span
>
</label>
<input
class="pf-c-form-control"
readonly
type="text"
value="${ifDefined(this.provider.urlSloRedirect)}"
value="${ifDefined(this.provider.urlUnifiedInit)}"
/>
</div>
</form>

View File

@@ -31,7 +31,7 @@ export class SCIMProviderGroupList extends Table<SCIMProviderGroup> {
clearOnRefresh = true;
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Sync")}</span>
<span slot="header">${msg("Sync Group")}</span>
<ak-sync-object-form

View File

@@ -32,7 +32,7 @@ export class SCIMProviderUserList extends Table<SCIMProviderUser> {
clearOnRefresh = true;
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Sync")}</span>
<span slot="header">${msg("Sync User")}</span>
<ak-sync-object-form

View File

@@ -41,7 +41,7 @@ export class LDAPSourceGroupList extends Table<GroupLDAPSourceConnection> {
}
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Connect")}</span>
<span slot="header">${msg("Connect Group")}</span>
<ak-source-ldap-group-form .source=${this.source} slot="form">

View File

@@ -47,7 +47,7 @@ export class LDAPSourceUserList extends Table<UserLDAPSourceConnection> {
}
renderToolbar(): TemplateResult {
return html`<ak-forms-modal cancelText=${msg("Close")} ?closeAfterSuccessfulSubmit=${false}>
return html`<ak-forms-modal cancelText=${msg("Close")} keep-open-after-submit>
<span slot="submit">${msg("Connect")}</span>
<span slot="header">${msg("Connect User")}</span>
<ak-source-ldap-user-form .source=${this.source} slot="form">

View File

@@ -52,8 +52,8 @@ export class ModalForm extends ModalButton {
//#region Properties
@property({ type: Boolean })
public closeAfterSuccessfulSubmit = true;
@property({ type: Boolean, attribute: "keep-open-after-submit" })
public keepOpenAfterSubmit = false;
@property({ type: Boolean })
public showSubmitButton = true;
@@ -98,7 +98,7 @@ export class ModalForm extends ModalButton {
return formPromise
.then(() => {
if (this.closeAfterSuccessfulSubmit) {
if (!this.keepOpenAfterSubmit) {
this.open = false;
form?.reset();
@@ -138,7 +138,7 @@ export class ModalForm extends ModalButton {
protected refreshListener = (e: Event): void => {
// if the modal should stay open after successful submit, prevent EVENT_REFRESH from bubbling
// to the parent components (which would cause table refreshes that destroy the modal)
if (!this.closeAfterSuccessfulSubmit) {
if (this.keepOpenAfterSubmit) {
e.stopPropagation();
}
};

View File

@@ -110,13 +110,7 @@ export class SyncObjectForm extends Form<SyncObjectRequest> {
renderResult(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("Log messages")}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal">
<ak-log-viewer .items=${this.result?.messages}></ak-log-viewer>
</dl>
</div>
</div>
<ak-log-viewer .items=${this.result?.messages}></ak-log-viewer>
</ak-form-element-horizontal> `;
}