mirror of
https://github.com/goauthentik/authentik
synced 2026-05-06 07:02:51 +02:00
Compare commits
3 Commits
saml-endpo
...
web/csp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64e7fa6bc0 | ||
|
|
4b4968c66b | ||
|
|
4e5b938ebe |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -64,7 +64,7 @@ runs:
|
||||
rustflags: ""
|
||||
- name: Setup rust dependencies
|
||||
if: ${{ contains(inputs.dependencies, 'rust') }}
|
||||
uses: taiki-e/install-action@cf525cb33f51aca27cd6fa02034117ab963ff9f1 # v2
|
||||
uses: taiki-e/install-action@787505cde8a44ea468a00478fe52baf23b15bccd # v2
|
||||
with:
|
||||
tool: cargo-deny cargo-machete cargo-llvm-cov nextest
|
||||
- name: Setup node (web)
|
||||
|
||||
@@ -14,7 +14,6 @@ pyproject.toml @goauthentik/backend
|
||||
uv.lock @goauthentik/backend
|
||||
Cargo.toml @goauthentik/backend
|
||||
Cargo.lock @goauthentik/backend
|
||||
build.rs @goauthentik/backend
|
||||
go.mod @goauthentik/backend
|
||||
go.sum @goauthentik/backend
|
||||
.cargo/ @goauthentik/backend
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -198,7 +198,6 @@ dependencies = [
|
||||
"metrics-exporter-prometheus",
|
||||
"nix 0.31.2",
|
||||
"pyo3",
|
||||
"pyo3-build-config",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
||||
@@ -49,7 +49,6 @@ nix = { version = "= 0.31.2", features = ["hostname", "signal"] }
|
||||
notify = "= 8.2.0"
|
||||
pin-project-lite = "= 0.2.17"
|
||||
pyo3 = "= 0.28.3"
|
||||
pyo3-build-config = "= 0.28.3"
|
||||
regex = "= 1.12.3"
|
||||
reqwest = { version = "= 0.13.2", features = [
|
||||
"form",
|
||||
@@ -261,9 +260,6 @@ default = ["core", "proxy"]
|
||||
core = ["ak-common/core", "dep:pyo3", "dep:sqlx"]
|
||||
proxy = ["ak-common/proxy"]
|
||||
|
||||
[build-dependencies]
|
||||
pyo3-build-config.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ak-axum.workspace = true
|
||||
ak-common.workspace = true
|
||||
|
||||
@@ -66,4 +66,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
"footer_links": tenant.footer_links,
|
||||
"html_meta": {**get_http_meta()},
|
||||
"version": authentik_full_version(),
|
||||
"csp_nonce": request.request_id,
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ SAML_BINDING_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
|
||||
SAML_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
||||
|
||||
DEFAULT_ISSUER = "authentik"
|
||||
|
||||
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.2
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Iterator
|
||||
from copy import copy
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Case, QuerySet
|
||||
from django.db.models import Case, Q, QuerySet
|
||||
from django.db.models.expressions import When
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext as _
|
||||
@@ -120,7 +120,6 @@ class ApplicationSerializer(ModelSerializer):
|
||||
"meta_publisher",
|
||||
"policy_engine_mode",
|
||||
"group",
|
||||
"meta_hide",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"backchannel_providers": {"required": False},
|
||||
@@ -284,12 +283,14 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
) == "true"
|
||||
|
||||
queryset = self._filter_queryset_for_list(self.get_queryset())
|
||||
queryset = queryset.exclude(meta_hide=True)
|
||||
if only_with_launch_url:
|
||||
# Pre-filter at DB level to skip expensive per-app policy evaluation
|
||||
# for apps that can never appear in the launcher (no meta_launch_url
|
||||
# and no provider, so no possible launch URL).
|
||||
queryset = queryset.exclude(meta_launch_url="", provider__isnull=True)
|
||||
# for apps that can never appear in the launcher:
|
||||
# - No meta_launch_url AND no provider: no possible launch URL
|
||||
# - meta_launch_url="blank://blank": documented convention to hide from launcher
|
||||
queryset = queryset.exclude(
|
||||
Q(meta_launch_url="", provider__isnull=True) | Q(meta_launch_url="blank://blank")
|
||||
)
|
||||
paginator: Pagination = self.paginator
|
||||
paginated_apps = paginator.paginate_queryset(queryset, request)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.relations import ManyRelatedField, PrimaryKeyRelatedField
|
||||
from rest_framework.relations import PrimaryKeyRelatedField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ListSerializer, ValidationError
|
||||
@@ -37,77 +37,6 @@ from authentik.endpoints.connectors.agent.auth import AgentAuth
|
||||
from authentik.rbac.api.roles import RoleSerializer
|
||||
from authentik.rbac.decorators import permission_required
|
||||
|
||||
|
||||
class BulkManyRelatedField(ManyRelatedField):
|
||||
"""ManyRelatedField that validates all PKs in a single query instead of one per PK."""
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if isinstance(data, str) or not hasattr(data, "__iter__"):
|
||||
self.fail("not_a_list", input_type=type(data).__name__)
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
self.fail("empty")
|
||||
|
||||
child = self.child_relation
|
||||
pk_field = child.pk_field
|
||||
# Coerce PKs through pk_field if defined
|
||||
pk_map = {}
|
||||
for item in data:
|
||||
if isinstance(item, bool):
|
||||
self.fail("incorrect_type", data_type=type(item).__name__)
|
||||
pk = pk_field.to_internal_value(item) if pk_field else item
|
||||
pk_map[pk] = item # map coerced PK -> original value for error reporting
|
||||
|
||||
queryset = child.get_queryset()
|
||||
# Use count to validate all PKs exist in a single query
|
||||
found_count = queryset.filter(pk__in=pk_map.keys()).count()
|
||||
if found_count < len(pk_map):
|
||||
# Some PKs not found — fall back to per-PK checks for error reporting.
|
||||
# This only runs when there's an actual validation error (rare path).
|
||||
for pk, original in pk_map.items():
|
||||
if not queryset.filter(pk=pk).exists():
|
||||
child.fail("does_not_exist", pk_value=original)
|
||||
|
||||
# Return raw PKs — Django's M2M set() accepts both objects and PKs,
|
||||
# using get_prep_value() for type coercion. This avoids loading all
|
||||
# objects into memory and avoids triggering post_init signals.
|
||||
return list(pk_map.keys())
|
||||
|
||||
def to_representation(self, iterable):
|
||||
# For non-prefetched querysets, get PKs directly without loading model instances.
|
||||
# When prefetched, _result_cache is a list (possibly empty); when not, it's None.
|
||||
if hasattr(iterable, "values_list") and getattr(iterable, "_result_cache", None) is None:
|
||||
return list(iterable.values_list("pk", flat=True))
|
||||
return super().to_representation(iterable)
|
||||
|
||||
|
||||
class BulkPrimaryKeyRelatedField(PrimaryKeyRelatedField):
|
||||
"""PrimaryKeyRelatedField that uses bulk validation when many=True."""
|
||||
|
||||
@classmethod
|
||||
def many_init(cls, *args, **kwargs):
|
||||
allow_empty = kwargs.pop("allow_empty", None)
|
||||
max_length = kwargs.pop("max_length", None)
|
||||
min_length = kwargs.pop("min_length", None)
|
||||
child_relation = cls(*args, **kwargs)
|
||||
list_kwargs = {
|
||||
"child_relation": child_relation,
|
||||
}
|
||||
if allow_empty is not None:
|
||||
list_kwargs["allow_empty"] = allow_empty
|
||||
if max_length is not None:
|
||||
list_kwargs["max_length"] = max_length
|
||||
if min_length is not None:
|
||||
list_kwargs["min_length"] = min_length
|
||||
list_kwargs.update(
|
||||
{
|
||||
key: value
|
||||
for key, value in kwargs.items()
|
||||
if key in ("required", "default", "source")
|
||||
}
|
||||
)
|
||||
return BulkManyRelatedField(**list_kwargs)
|
||||
|
||||
|
||||
PARTIAL_USER_SERIALIZER_MODEL_FIELDS = [
|
||||
"pk",
|
||||
"username",
|
||||
@@ -150,7 +79,6 @@ class GroupSerializer(ModelSerializer):
|
||||
"""Group Serializer"""
|
||||
|
||||
attributes = JSONDictField(required=False)
|
||||
users = BulkPrimaryKeyRelatedField(queryset=User.objects.all(), many=True, default=list)
|
||||
parents = PrimaryKeyRelatedField(queryset=Group.objects.all(), many=True, required=False)
|
||||
parents_obj = SerializerMethodField(allow_null=True)
|
||||
children_obj = SerializerMethodField(allow_null=True)
|
||||
@@ -265,6 +193,9 @@ class GroupSerializer(ModelSerializer):
|
||||
"children_obj",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"users": {
|
||||
"default": list,
|
||||
},
|
||||
"children": {
|
||||
"required": False,
|
||||
"default": list,
|
||||
@@ -294,7 +225,6 @@ class GroupFilter(FilterSet):
|
||||
members_by_pk = ModelMultipleChoiceFilter(
|
||||
field_name="users",
|
||||
queryset=User.objects.all(),
|
||||
distinct=False,
|
||||
)
|
||||
|
||||
def filter_attributes(self, queryset, name, value):
|
||||
@@ -346,8 +276,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
# Always prefetch parents and children since their PKs are always serialized
|
||||
base_qs = Group.objects.all().prefetch_related("roles", "parents", "children")
|
||||
base_qs = Group.objects.all().prefetch_related("roles")
|
||||
|
||||
if self.serializer_class(context={"request": self.request})._should_include_users:
|
||||
# Only fetch fields needed by PartialUserSerializer to reduce DB load and instantiation
|
||||
@@ -358,9 +287,16 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
queryset=User.objects.all().only(*PARTIAL_USER_SERIALIZER_MODEL_FIELDS),
|
||||
)
|
||||
)
|
||||
# When include_users=false, skip users prefetch entirely.
|
||||
# BulkManyRelatedField.to_representation will use values_list to get PKs
|
||||
# directly without loading User instances into memory.
|
||||
else:
|
||||
base_qs = base_qs.prefetch_related(
|
||||
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")
|
||||
|
||||
if self.serializer_class(context={"request": self.request})._should_include_parents:
|
||||
base_qs = base_qs.prefetch_related("parents")
|
||||
|
||||
return base_qs
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import Any
|
||||
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.models import AnonymousUser, Permission
|
||||
from django.db.models import Exists, OuterRef, Prefetch, Q
|
||||
from django.db.transaction import atomic
|
||||
from django.db.utils import IntegrityError
|
||||
from django.urls import reverse_lazy
|
||||
@@ -132,7 +131,7 @@ class PartialGroupSerializer(ModelSerializer):
|
||||
class UserSerializer(ModelSerializer):
|
||||
"""User Serializer"""
|
||||
|
||||
is_superuser = SerializerMethodField()
|
||||
is_superuser = BooleanField(read_only=True)
|
||||
avatar = SerializerMethodField()
|
||||
attributes = JSONDictField(required=False)
|
||||
groups = PrimaryKeyRelatedField(
|
||||
@@ -169,14 +168,6 @@ class UserSerializer(ModelSerializer):
|
||||
return True
|
||||
return str(request.query_params.get("include_roles", "true")).lower() == "true"
|
||||
|
||||
@extend_schema_field(BooleanField)
|
||||
def get_is_superuser(self, instance: User) -> bool:
|
||||
"""Use annotation if available to avoid N+1 query"""
|
||||
ann = getattr(instance, "_annotated_is_superuser", None)
|
||||
if ann is not None:
|
||||
return ann
|
||||
return instance.is_superuser
|
||||
|
||||
@extend_schema_field(PartialGroupSerializer(many=True))
|
||||
def get_groups_obj(self, instance: User) -> list[PartialGroupSerializer] | None:
|
||||
if not self._should_include_groups:
|
||||
@@ -550,30 +541,10 @@ class UserViewSet(
|
||||
|
||||
def get_queryset(self):
|
||||
base_qs = User.objects.all().exclude_anonymous()
|
||||
# Always prefetch groups since group PKs are always serialized.
|
||||
# Use full prefetch when include_groups=true (for groups_obj), ID-only otherwise.
|
||||
if self.serializer_class(context={"request": self.request})._should_include_groups:
|
||||
base_qs = base_qs.prefetch_related("groups")
|
||||
else:
|
||||
base_qs = base_qs.prefetch_related(
|
||||
Prefetch("groups", queryset=Group.objects.all().only("group_uuid"))
|
||||
)
|
||||
if self.serializer_class(context={"request": self.request})._should_include_roles:
|
||||
base_qs = base_qs.prefetch_related("roles")
|
||||
else:
|
||||
base_qs = base_qs.prefetch_related(
|
||||
Prefetch("roles", queryset=Role.objects.all().only("uuid"))
|
||||
)
|
||||
# Annotate is_superuser to avoid N+1 query per user
|
||||
base_qs = base_qs.annotate(
|
||||
_annotated_is_superuser=Exists(
|
||||
Group.objects.filter(
|
||||
is_superuser=True,
|
||||
).filter(
|
||||
Q(users=OuterRef("pk")) | Q(descendant_nodes__descendant__users=OuterRef("pk"))
|
||||
)
|
||||
)
|
||||
)
|
||||
return base_qs
|
||||
|
||||
@extend_schema(
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-09 18:04
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_blank_launch_url(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Application = apps.get_model("authentik_core", "Application")
|
||||
|
||||
Application.objects.using(db_alias).filter(meta_launch_url="blank://blank").update(
|
||||
meta_hide=True, meta_launch_url=""
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0058_setup"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="application",
|
||||
name="meta_hide",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Hide this application from the user's My applications page.",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate_blank_launch_url, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -735,9 +735,6 @@ class Application(SerializerModel, PolicyBindingModel):
|
||||
meta_icon = FileField(default="", blank=True)
|
||||
meta_description = models.TextField(default="", blank=True)
|
||||
meta_publisher = models.TextField(default="", blank=True)
|
||||
meta_hide = models.BooleanField(
|
||||
default=False, help_text=_("Hide this application from the user's My applications page.")
|
||||
)
|
||||
|
||||
objects = ApplicationQuerySet.as_manager()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<script data-id="authentik-config">
|
||||
<script data-id="authentik-config" nonce="{{ csp_nonce }}">
|
||||
"use strict";
|
||||
|
||||
window.authentik = {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
{# Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we default to a dark theme based on preferred colour-scheme #}
|
||||
<meta name="darkreader-lock">
|
||||
<script nonce="{{ csp_nonce }}">window.litNonce = "{{ csp_nonce }}";</script>
|
||||
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
|
||||
<link rel="icon" href="{{ brand.branding_favicon_url }}">
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
|
||||
@@ -27,7 +28,7 @@
|
||||
|
||||
{% include "base/theme.html" %}
|
||||
|
||||
<style data-id="brand-css">{{ brand_css }}</style>
|
||||
<style data-id="brand-css" nonce="{{ csp_nonce }}">{{ brand_css }}</style>
|
||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<meta name="color-scheme" content="light" />
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
{% else %}
|
||||
<script data-id="theme-script">
|
||||
<script data-id="theme-script" nonce="{{ csp_nonce }}">
|
||||
"use strict";
|
||||
|
||||
(function () {
|
||||
@@ -22,7 +22,7 @@
|
||||
if (!(["auto", "light", "dark"].includes(locallyStoredTheme))) {
|
||||
locallyStoredTheme = null;
|
||||
}
|
||||
|
||||
|
||||
const initialThemeChoice =
|
||||
new URLSearchParams(window.location.search).get("theme") || locallyStoredTheme;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style data-id="static-styles">
|
||||
<style data-id="static-styles" nonce="{{ csp_nonce }}">
|
||||
:root {
|
||||
--ak-global--background-image: url("{{ request.brand.branding_default_flow_background_url }}");
|
||||
}
|
||||
|
||||
@@ -129,7 +129,6 @@ class TestApplicationsAPI(APITestCase):
|
||||
"meta_icon_url": None,
|
||||
"meta_icon_themed_urls": None,
|
||||
"meta_description": "",
|
||||
"meta_hide": False,
|
||||
"meta_publisher": "",
|
||||
"policy_engine_mode": "any",
|
||||
},
|
||||
@@ -188,14 +187,12 @@ class TestApplicationsAPI(APITestCase):
|
||||
"meta_icon_url": None,
|
||||
"meta_icon_themed_urls": None,
|
||||
"meta_description": "",
|
||||
"meta_hide": False,
|
||||
"meta_publisher": "",
|
||||
"policy_engine_mode": "any",
|
||||
},
|
||||
{
|
||||
"launch_url": None,
|
||||
"meta_description": "",
|
||||
"meta_hide": False,
|
||||
"meta_icon": "",
|
||||
"meta_icon_url": None,
|
||||
"meta_icon_themed_urls": None,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<html>
|
||||
<script>
|
||||
<script nonce="{{ csp_nonce }}">
|
||||
window.parent.postMessage({
|
||||
message: "submit",
|
||||
source: "goauthentik.io",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||
<link rel="prefetch" href="{{ flow_background_url }}" />
|
||||
{% include "base/header_js.html" %}
|
||||
<style data-id="flow-sfe">
|
||||
<style data-id="flow-sfe" nonce="{{ csp_nonce }}">
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
@@ -43,13 +43,13 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<script src="{% static 'dist/sfe/index.js' %}"></script>
|
||||
</head>
|
||||
<body class="d-flex align-items-center py-4 bg-body-tertiary">
|
||||
<div class="card m-auto">
|
||||
<main class="form-signin w-100 m-auto" id="flow-sfe-container">
|
||||
</main>
|
||||
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
|
||||
</div>
|
||||
<script src="{% static 'dist/sfe/index.js' %}"></script>
|
||||
<div class="card m-auto">
|
||||
<main class="form-signin w-100 m-auto" id="flow-sfe-container">
|
||||
</main>
|
||||
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{% comment %}
|
||||
@see {@link web/types/webcomponents.d.ts} for type definitions.
|
||||
{% endcomment %}
|
||||
<script data-id="shady-dom">
|
||||
<script data-id="shady-dom" nonce="{{ csp_nonce }}">
|
||||
"use strict";
|
||||
|
||||
window.ShadyDOM = window.ShadyDOM || {}
|
||||
@@ -20,7 +20,7 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
{% include "base/header_js.html" %}
|
||||
<script data-id="flow-config">
|
||||
<script data-id="flow-config" nonce="{{ csp_nonce }}">
|
||||
"use strict";
|
||||
|
||||
window.authentik.flow = {
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
|
||||
<style data-id="flow-css">
|
||||
<style data-id="flow-css" nonce="{{ csp_nonce }}">
|
||||
:root {
|
||||
--ak-global--background-image: url("{{ flow_background_url }}");
|
||||
}
|
||||
@@ -55,10 +55,10 @@
|
||||
loading
|
||||
>
|
||||
{% include "base/placeholder.html" %}
|
||||
|
||||
|
||||
<ak-brand-links name="flow-links" slot="footer"></ak-brand-links>
|
||||
</ak-flow-executor>
|
||||
|
||||
|
||||
<ak-flow-inspector
|
||||
slot="panel"
|
||||
id="flow-inspector"
|
||||
|
||||
@@ -165,6 +165,8 @@ web:
|
||||
timeout_http_read: 30s
|
||||
timeout_http_write: 60s
|
||||
timeout_http_idle: 120s
|
||||
csp:
|
||||
report_only: false
|
||||
|
||||
worker:
|
||||
processes: 1
|
||||
|
||||
@@ -24,11 +24,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.validation import validate
|
||||
from authentik.common.saml.constants import (
|
||||
DEFAULT_ISSUER,
|
||||
SAML_BINDING_POST,
|
||||
SAML_BINDING_REDIRECT,
|
||||
)
|
||||
from authentik.common.saml.constants import SAML_BINDING_POST, SAML_BINDING_REDIRECT
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer, PropertyMappingPreviewSerializer
|
||||
@@ -59,13 +55,7 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"""SAMLProvider Serializer"""
|
||||
|
||||
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()
|
||||
@@ -95,53 +85,6 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
+ "?download"
|
||||
)
|
||||
|
||||
def get_url_issuer(self, instance: SAMLProvider) -> str:
|
||||
"""Get Issuer/EntityID URL"""
|
||||
if instance.issuer_override:
|
||||
return instance.issuer_override
|
||||
if "request" not in self._context:
|
||||
return DEFAULT_ISSUER
|
||||
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 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:
|
||||
@@ -255,7 +198,7 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"acs_url",
|
||||
"sls_url",
|
||||
"audience",
|
||||
"issuer_override",
|
||||
"issuer",
|
||||
"assertion_valid_not_before",
|
||||
"assertion_valid_not_on_or_after",
|
||||
"session_valid_not_on_or_after",
|
||||
@@ -277,9 +220,6 @@ class SAMLProviderSerializer(ProviderSerializer):
|
||||
"default_relay_state",
|
||||
"default_name_id_policy",
|
||||
"url_download_metadata",
|
||||
"url_issuer",
|
||||
"url_unified",
|
||||
"url_unified_init",
|
||||
"url_sso_post",
|
||||
"url_sso_redirect",
|
||||
"url_sso_init",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Generated by Django 5.2.11 on 2026-02-24 06:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_saml", "0021_samlprovider_sign_logout_response"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="samlprovider",
|
||||
old_name="issuer",
|
||||
new_name="issuer_override",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlprovider",
|
||||
name="issuer_override",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text="Also known as EntityID. Providing a value overrides the default issuer generated by authentik.",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="samlsession",
|
||||
name="issuer",
|
||||
field=models.TextField(
|
||||
default=None, help_text="SAML Issuer used for this session", null=True
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -77,14 +77,7 @@ class SAMLProvider(Provider):
|
||||
"no audience restriction will be added."
|
||||
),
|
||||
)
|
||||
issuer_override = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
help_text=_(
|
||||
"Also known as EntityID. Providing a value overrides the default issuer "
|
||||
"generated by authentik."
|
||||
),
|
||||
)
|
||||
issuer = models.TextField(help_text=_("Also known as EntityID"), default="authentik")
|
||||
sls_url = models.TextField(
|
||||
blank=True,
|
||||
validators=[DomainlessURLValidator(schemes=("http", "https"))],
|
||||
@@ -241,7 +234,7 @@ class SAMLProvider(Provider):
|
||||
"""Use IDP-Initiated SAML flow as launch URL"""
|
||||
try:
|
||||
return reverse(
|
||||
"authentik_providers_saml:init",
|
||||
"authentik_providers_saml:sso-init",
|
||||
kwargs={"application_slug": self.application.slug},
|
||||
)
|
||||
except Provider.application.RelatedObjectDoesNotExist:
|
||||
@@ -325,9 +318,6 @@ class SAMLSession(InternallyManagedMixin, SerializerModel, ExpiringModel):
|
||||
session_index = models.TextField(help_text=_("SAML SessionIndex for this session"))
|
||||
name_id = models.TextField(help_text=_("SAML NameID value for this session"))
|
||||
name_id_format = models.TextField(default="", blank=True, help_text=_("SAML NameID format"))
|
||||
issuer = models.TextField(
|
||||
default=None, null=True, help_text=_("SAML Issuer used for this session")
|
||||
)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@property
|
||||
|
||||
@@ -6,7 +6,6 @@ from types import GeneratorType
|
||||
|
||||
import xmlsec
|
||||
from django.http import HttpRequest
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from lxml import etree # nosec
|
||||
from lxml.etree import Element, SubElement, _Element # nosec
|
||||
@@ -64,7 +63,6 @@ class AssertionProcessor:
|
||||
session_index: str
|
||||
name_id: str
|
||||
name_id_format: str
|
||||
issuer: str
|
||||
session_not_on_or_after_datetime: datetime
|
||||
|
||||
def __init__(self, provider: SAMLProvider, request: HttpRequest, auth_n_request: AuthNRequest):
|
||||
@@ -139,24 +137,10 @@ class AssertionProcessor:
|
||||
continue
|
||||
return attribute_statement
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value, with fallback to generated URL if empty"""
|
||||
# If user has set an override issuer, use it
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
|
||||
return self.http_request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_saml:base",
|
||||
kwargs={"application_slug": self.provider.application.slug},
|
||||
)
|
||||
)
|
||||
|
||||
def get_issuer(self) -> Element:
|
||||
"""Get Issuer Element"""
|
||||
issuer = Element(f"{{{NS_SAML_ASSERTION}}}Issuer", nsmap=NS_MAP)
|
||||
self.issuer = self._get_issuer_value()
|
||||
issuer.text = self.issuer
|
||||
issuer.text = self.provider.issuer
|
||||
return issuer
|
||||
|
||||
def get_assertion_auth_n_statement(self) -> Element:
|
||||
|
||||
@@ -8,7 +8,6 @@ from lxml import etree # nosec
|
||||
from lxml.etree import Element, _Element
|
||||
|
||||
from authentik.common.saml.constants import (
|
||||
DEFAULT_ISSUER,
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
||||
NS_MAP,
|
||||
NS_SAML_ASSERTION,
|
||||
@@ -34,12 +33,11 @@ class LogoutRequestProcessor:
|
||||
name_id_format: str
|
||||
session_index: str | None
|
||||
relay_state: str | None
|
||||
issuer: str | None
|
||||
|
||||
_issue_instant: str
|
||||
_request_id: str
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
def __init__(
|
||||
self,
|
||||
provider: SAMLProvider,
|
||||
user: User | None,
|
||||
@@ -48,7 +46,6 @@ class LogoutRequestProcessor:
|
||||
name_id_format: str = SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index: str | None = None,
|
||||
relay_state: str | None = None,
|
||||
issuer: str | None = None,
|
||||
):
|
||||
self.provider = provider
|
||||
self.user = user
|
||||
@@ -57,23 +54,14 @@ class LogoutRequestProcessor:
|
||||
self.name_id_format = name_id_format
|
||||
self.session_index = session_index
|
||||
self.relay_state = relay_state
|
||||
self.issuer = issuer
|
||||
|
||||
self._issue_instant = get_time_string()
|
||||
self._request_id = get_random_id()
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value from session, with fallback to provider"""
|
||||
if self.issuer:
|
||||
return self.issuer
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
return DEFAULT_ISSUER
|
||||
|
||||
def get_issuer(self) -> Element:
|
||||
"""Get Issuer element"""
|
||||
issuer = Element(f"{{{NS_SAML_ASSERTION}}}Issuer")
|
||||
issuer.text = self._get_issuer_value()
|
||||
issuer.text = self.provider.issuer
|
||||
return issuer
|
||||
|
||||
def get_name_id(self) -> Element:
|
||||
|
||||
@@ -8,7 +8,6 @@ from lxml import etree
|
||||
from lxml.etree import Element, SubElement
|
||||
|
||||
from authentik.common.saml.constants import (
|
||||
DEFAULT_ISSUER,
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP,
|
||||
NS_MAP,
|
||||
NS_SAML_ASSERTION,
|
||||
@@ -29,38 +28,27 @@ class LogoutResponseProcessor:
|
||||
logout_request: LogoutRequest
|
||||
destination: str | None
|
||||
relay_state: str | None
|
||||
issuer: str | None
|
||||
_issue_instant: str
|
||||
_response_id: str
|
||||
|
||||
def __init__( # noqa: PLR0913
|
||||
def __init__(
|
||||
self,
|
||||
provider: SAMLProvider,
|
||||
logout_request: LogoutRequest,
|
||||
destination: str | None = None,
|
||||
relay_state: str | None = None,
|
||||
issuer: str | None = None,
|
||||
):
|
||||
self.provider = provider
|
||||
self.logout_request = logout_request
|
||||
self.destination = destination
|
||||
self.relay_state = relay_state or (logout_request.relay_state if logout_request else None)
|
||||
self.issuer = issuer
|
||||
self._issue_instant = get_time_string()
|
||||
self._response_id = get_random_id()
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value from session, with fallback to provider"""
|
||||
if self.issuer:
|
||||
return self.issuer
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
return DEFAULT_ISSUER
|
||||
|
||||
def get_issuer(self) -> Element:
|
||||
"""Get Issuer element"""
|
||||
issuer = Element(f"{{{NS_SAML_ASSERTION}}}Issuer")
|
||||
issuer.text = self._get_issuer_value()
|
||||
issuer.text = self.provider.issuer
|
||||
return issuer
|
||||
|
||||
def build(self, status: str = "Success") -> Element:
|
||||
|
||||
@@ -40,19 +40,6 @@ class MetadataProcessor:
|
||||
self.force_binding = None
|
||||
self.xml_id = "_" + sha256(f"{provider.name}-{provider.pk}".encode("ascii")).hexdigest()
|
||||
|
||||
def _get_issuer_value(self) -> str:
|
||||
"""Get issuer value, with fallback to generated URL if empty"""
|
||||
# If user has set an override issuer, use it
|
||||
if self.provider.issuer_override:
|
||||
return self.provider.issuer_override
|
||||
|
||||
return self.http_request.build_absolute_uri(
|
||||
reverse(
|
||||
"authentik_providers_saml:base",
|
||||
kwargs={"application_slug": self.provider.application.slug},
|
||||
)
|
||||
)
|
||||
|
||||
# Using type unions doesn't work with cython types (which is what lxml is)
|
||||
def get_signing_key_descriptor(self) -> Element | None:
|
||||
"""Get Signing KeyDescriptor, if enabled for the provider"""
|
||||
@@ -81,35 +68,54 @@ 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 SSO Bindings - both point to unified endpoint"""
|
||||
unified_url = self._get_unified_url()
|
||||
for binding in [SAML_BINDING_REDIRECT, SAML_BINDING_POST]:
|
||||
"""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
|
||||
if self.force_binding and self.force_binding != binding:
|
||||
continue
|
||||
element = Element(f"{{{NS_SAML_METADATA}}}SingleSignOnService")
|
||||
element = Element(f"{{{NS_SAML_METADATA}}}{svc}")
|
||||
element.attrib["Binding"] = binding
|
||||
element.attrib["Location"] = unified_url
|
||||
element.attrib["Location"] = url
|
||||
yield element
|
||||
|
||||
def get_slo_bindings(self) -> Iterator[Element]:
|
||||
"""Get all SLO Bindings - both point to unified endpoint"""
|
||||
unified_url = self._get_unified_url()
|
||||
for binding in [SAML_BINDING_REDIRECT, SAML_BINDING_POST]:
|
||||
"""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
|
||||
if self.force_binding and self.force_binding != binding:
|
||||
continue
|
||||
element = Element(f"{{{NS_SAML_METADATA}}}SingleLogoutService")
|
||||
element = Element(f"{{{NS_SAML_METADATA}}}{svc}")
|
||||
element.attrib["Binding"] = binding
|
||||
element.attrib["Location"] = unified_url
|
||||
element.attrib["Location"] = url
|
||||
yield element
|
||||
|
||||
def _prepare_signature(self, entity_descriptor: _Element):
|
||||
@@ -183,7 +189,7 @@ class MetadataProcessor:
|
||||
"""Build full EntityDescriptor"""
|
||||
entity_descriptor = Element(f"{{{NS_SAML_METADATA}}}EntityDescriptor", nsmap=NS_MAP)
|
||||
entity_descriptor.attrib["ID"] = self.xml_id
|
||||
entity_descriptor.attrib["entityID"] = self._get_issuer_value()
|
||||
entity_descriptor.attrib["entityID"] = self.provider.issuer
|
||||
|
||||
if self.provider.signing_kp:
|
||||
self._prepare_signature(entity_descriptor)
|
||||
|
||||
@@ -51,6 +51,7 @@ class ServiceProviderMetadata:
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=name, authorization_flow=authorization_flow, invalidation_flow=invalidation_flow
|
||||
)
|
||||
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
|
||||
|
||||
@@ -75,7 +75,6 @@ def handle_saml_iframe_pre_user_logout(
|
||||
name_id_format=session.name_id_format,
|
||||
session_index=session.session_index,
|
||||
relay_state=relay_state,
|
||||
issuer=session.issuer,
|
||||
)
|
||||
|
||||
if session.provider.sls_binding == SAMLBindings.POST:
|
||||
@@ -164,7 +163,6 @@ def handle_flow_pre_user_logout(
|
||||
name_id_format=session.name_id_format,
|
||||
session_index=session.session_index,
|
||||
relay_state=relay_state,
|
||||
issuer=session.issuer,
|
||||
)
|
||||
|
||||
if session.provider.sls_binding == SAMLBindings.POST:
|
||||
@@ -226,7 +224,6 @@ def user_session_deleted_saml_logout(sender, instance: AuthenticatedSession, **_
|
||||
name_id=saml_session.name_id,
|
||||
name_id_format=saml_session.name_id_format,
|
||||
session_index=saml_session.session_index,
|
||||
issuer=saml_session.issuer,
|
||||
)
|
||||
|
||||
|
||||
@@ -260,5 +257,4 @@ def user_deactivated_saml_logout(sender, instance: User, **kwargs):
|
||||
name_id=saml_session.name_id,
|
||||
name_id_format=saml_session.name_id_format,
|
||||
session_index=saml_session.session_index,
|
||||
issuer=saml_session.issuer,
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ def send_saml_logout_request(
|
||||
name_id: str,
|
||||
name_id_format: str,
|
||||
session_index: str,
|
||||
issuer: str,
|
||||
):
|
||||
"""Send SAML LogoutRequest to a Service Provider using session data"""
|
||||
provider = SAMLProvider.objects.filter(pk=provider_pk).first()
|
||||
@@ -48,7 +47,6 @@ def send_saml_logout_request(
|
||||
name_id=name_id,
|
||||
name_id_format=name_id_format,
|
||||
session_index=session_index,
|
||||
issuer=issuer,
|
||||
)
|
||||
|
||||
return send_post_logout_request(provider, processor)
|
||||
@@ -91,7 +89,6 @@ def send_saml_logout_response(
|
||||
sls_url: str,
|
||||
logout_request_id: str | None = None,
|
||||
relay_state: str | None = None,
|
||||
issuer: str | None = None,
|
||||
):
|
||||
"""Send SAML LogoutResponse to a Service Provider using backchannel (server-to-server)"""
|
||||
provider = SAMLProvider.objects.filter(pk=provider_pk).first()
|
||||
@@ -122,7 +119,6 @@ def send_saml_logout_response(
|
||||
logout_request=logout_request,
|
||||
destination=sls_url,
|
||||
relay_state=relay_state,
|
||||
issuer=issuer,
|
||||
)
|
||||
|
||||
encoded_response = processor.encode_post()
|
||||
|
||||
@@ -15,7 +15,6 @@ from authentik.common.saml.constants import (
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_UNSPECIFIED,
|
||||
)
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import (
|
||||
RequestFactory,
|
||||
create_test_admin_user,
|
||||
@@ -98,11 +97,6 @@ class TestAuthNRequest(TestCase):
|
||||
)
|
||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
self.provider.save()
|
||||
Application.objects.create(
|
||||
name="test-app",
|
||||
slug="test-app",
|
||||
provider=self.provider,
|
||||
)
|
||||
self.source = SAMLSource.objects.create(
|
||||
slug="provider",
|
||||
issuer="authentik",
|
||||
@@ -532,7 +526,7 @@ class TestAuthNRequest(TestCase):
|
||||
authorization_flow=create_test_flow(),
|
||||
acs_url="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
audience="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer_override="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
signing_kp=static_keypair,
|
||||
verification_kp=static_keypair,
|
||||
)
|
||||
@@ -553,7 +547,7 @@ class TestAuthNRequest(TestCase):
|
||||
"saml/acs/2d737f96-55fb-4035-953e-5e24134eb778"
|
||||
),
|
||||
audience="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer_override="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
parsed_request = AuthNRequestParser(provider).parse(POST_REQUEST)
|
||||
|
||||
@@ -47,7 +47,7 @@ class TestNativeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp1.example.com/acs",
|
||||
sls_url="https://sp1.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
logout_method=SAMLLogoutMethods.FRONTCHANNEL_NATIVE,
|
||||
@@ -58,7 +58,7 @@ class TestNativeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
sls_url="https://sp2.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="post",
|
||||
sls_binding="post",
|
||||
logout_method=SAMLLogoutMethods.FRONTCHANNEL_NATIVE,
|
||||
@@ -218,7 +218,7 @@ class TestIframeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp1.example.com/acs",
|
||||
sls_url="https://sp1.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
logout_method="frontchannel_iframe",
|
||||
@@ -229,7 +229,7 @@ class TestIframeLogoutStageView(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
sls_url="https://sp2.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="post",
|
||||
sls_binding="post",
|
||||
logout_method="frontchannel_iframe",
|
||||
@@ -372,7 +372,7 @@ class TestIdPLogoutIntegration(FlowTestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signing_kp=self.keypair,
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestLogoutIntegration(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signature_algorithm=RSA_SHA256,
|
||||
@@ -57,7 +57,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse(encoded)
|
||||
|
||||
# Verify all fields match
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "test-session-123")
|
||||
@@ -72,7 +72,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse_detached(encoded)
|
||||
|
||||
# Verify all fields match
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "test-session-123")
|
||||
@@ -106,7 +106,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = parser.parse(encoded)
|
||||
|
||||
# Verify all fields match
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.name_id, "signed@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "signed-session-456")
|
||||
@@ -125,7 +125,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse_detached(saml_request)
|
||||
|
||||
# Verify parsing succeeded
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
|
||||
@@ -164,7 +164,7 @@ class TestLogoutIntegration(TestCase):
|
||||
|
||||
# Parse the SAMLRequest (unsigned XML)
|
||||
parsed = self.parser.parse_detached(params["SAMLRequest"][0])
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
|
||||
def test_form_data_can_be_parsed(self):
|
||||
"""Test that form data generates parseable POST request"""
|
||||
@@ -175,7 +175,7 @@ class TestLogoutIntegration(TestCase):
|
||||
parsed = self.parser.parse(form_data["SAMLRequest"])
|
||||
|
||||
# Verify parsing succeeded
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed.issuer, self.provider.issuer)
|
||||
self.assertEqual(parsed.name_id, "test@example.com")
|
||||
self.assertEqual(parsed.name_id_format, SAML_NAME_ID_FORMAT_EMAIL)
|
||||
self.assertEqual(parsed.session_index, "test-session-123")
|
||||
@@ -244,4 +244,4 @@ class TestLogoutIntegration(TestCase):
|
||||
|
||||
# But same issuer
|
||||
self.assertEqual(parsed1.issuer, parsed2.issuer)
|
||||
self.assertEqual(parsed1.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(parsed1.issuer, self.provider.issuer)
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestLogoutRequestProcessor(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signature_algorithm=RSA_SHA256,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""logout response tests"""
|
||||
|
||||
from defusedxml import ElementTree
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.common.saml.constants import (
|
||||
@@ -9,13 +9,10 @@ from authentik.common.saml.constants import (
|
||||
NS_SAML_PROTOCOL,
|
||||
NS_SIGNATURE,
|
||||
)
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
|
||||
from authentik.providers.saml.processors.logout_request_parser import LogoutRequest
|
||||
from authentik.providers.saml.processors.logout_response_processor import LogoutResponseProcessor
|
||||
from authentik.providers.saml.processors.metadata import MetadataProcessor
|
||||
|
||||
|
||||
class TestLogoutResponse(TestCase):
|
||||
@@ -24,7 +21,6 @@ class TestLogoutResponse(TestCase):
|
||||
@apply_blueprint("system/providers-saml.yaml")
|
||||
def setUp(self):
|
||||
cert = create_test_cert()
|
||||
self.factory = RequestFactory()
|
||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
authorization_flow=create_test_flow(),
|
||||
acs_url="http://testserver/source/saml/provider/acs/",
|
||||
@@ -34,31 +30,17 @@ class TestLogoutResponse(TestCase):
|
||||
)
|
||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
self.provider.save()
|
||||
self.application = Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
provider=self.provider,
|
||||
)
|
||||
|
||||
def test_build_response(self):
|
||||
"""Test building a LogoutResponse uses the generated issuer from the assertion"""
|
||||
# Generate the issuer the same way the assertion/metadata processors would
|
||||
request = self.factory.get("/")
|
||||
metadata_processor = MetadataProcessor(self.provider, request)
|
||||
generated_issuer = metadata_processor._get_issuer_value()
|
||||
|
||||
"""Test building a LogoutResponse"""
|
||||
logout_request = LogoutRequest(
|
||||
id="test-request-id",
|
||||
issuer="test-sp",
|
||||
relay_state="test-relay-state",
|
||||
)
|
||||
|
||||
# Pass the generated issuer as if it came from SAMLSession.issuer
|
||||
processor = LogoutResponseProcessor(
|
||||
self.provider,
|
||||
logout_request,
|
||||
destination=self.provider.sls_url,
|
||||
issuer=generated_issuer,
|
||||
self.provider, logout_request, destination=self.provider.sls_url
|
||||
)
|
||||
response_xml = processor.build_response(status="Success")
|
||||
|
||||
@@ -69,9 +51,9 @@ class TestLogoutResponse(TestCase):
|
||||
self.assertEqual(root.attrib["Destination"], self.provider.sls_url)
|
||||
self.assertEqual(root.attrib["InResponseTo"], "test-request-id")
|
||||
|
||||
# Check Issuer matches the generated issuer from the assertion processor
|
||||
# Check Issuer
|
||||
issuer = root.find(f"{{{NS_SAML_ASSERTION}}}Issuer")
|
||||
self.assertEqual(issuer.text, generated_issuer)
|
||||
self.assertEqual(issuer.text, self.provider.issuer)
|
||||
|
||||
# Check Status
|
||||
status = root.find(f".//{{{NS_SAML_PROTOCOL}}}StatusCode")
|
||||
|
||||
@@ -85,6 +85,7 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/simple.xml"))
|
||||
provider = metadata.to_provider("test", self.flow, self.flow)
|
||||
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(
|
||||
@@ -98,6 +99,7 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
metadata = ServiceProviderMetadataParser().parse(load_fixture("fixtures/cert.xml"))
|
||||
provider = metadata.to_provider("test", self.flow, self.flow)
|
||||
self.assertEqual(provider.acs_url, "http://localhost:8080/apps/user_saml/saml/acs")
|
||||
self.assertEqual(provider.issuer, "http://localhost:8080/apps/user_saml/saml/metadata")
|
||||
self.assertEqual(provider.sp_binding, SAMLBindings.POST)
|
||||
self.assertEqual(
|
||||
provider.verification_kp.certificate_data, load_fixture("fixtures/cert.pem")
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name="test-provider",
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
# Create another provider for testing
|
||||
@@ -40,7 +40,7 @@ class TestSAMLSessionModel(TestCase):
|
||||
name="test-provider-2",
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
issuer_override="https://idp2.example.com",
|
||||
issuer="https://idp2.example.com",
|
||||
)
|
||||
|
||||
# Create a session first (using authentik's custom Session model)
|
||||
@@ -72,7 +72,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify the session was created
|
||||
@@ -101,7 +100,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Try to create another session with same session_index and provider
|
||||
@@ -115,7 +113,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
def test_cascade_deletion_user(self):
|
||||
@@ -130,7 +127,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify session exists
|
||||
@@ -154,7 +150,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify session exists
|
||||
@@ -178,7 +173,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify session exists
|
||||
@@ -202,7 +196,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Create second session with different provider
|
||||
@@ -215,7 +208,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify both sessions exist
|
||||
@@ -237,7 +229,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=future_time,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify expiry time
|
||||
@@ -257,7 +248,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=past_time,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Check if marked as expired
|
||||
@@ -275,7 +265,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format="", # Blank format
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify it was created successfully
|
||||
@@ -294,7 +283,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
session2 = SAMLSession.objects.create(
|
||||
@@ -306,7 +294,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Query by provider
|
||||
@@ -329,7 +316,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Check serializer property
|
||||
@@ -348,7 +334,6 @@ class TestSAMLSessionModel(TestCase):
|
||||
name_id_format=self.name_id_format,
|
||||
expires=self.expires,
|
||||
expiring=True,
|
||||
issuer="authentik",
|
||||
)
|
||||
|
||||
# Verify sessions exist
|
||||
|
||||
@@ -7,7 +7,6 @@ from guardian.shortcuts import get_anonymous_user
|
||||
from lxml import etree # nosec
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import RequestFactory, create_test_cert, create_test_flow
|
||||
from authentik.lib.xml import lxml_from_string
|
||||
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
|
||||
@@ -31,11 +30,6 @@ class TestSchema(TestCase):
|
||||
)
|
||||
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
self.provider.save()
|
||||
Application.objects.create(
|
||||
name="test-app",
|
||||
slug="test-app",
|
||||
provider=self.provider,
|
||||
)
|
||||
self.source = SAMLSource.objects.create(
|
||||
slug="provider",
|
||||
issuer="authentik",
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestSendSamlLogoutResponse(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
signing_kp=self.cert,
|
||||
)
|
||||
|
||||
@@ -137,7 +137,7 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
signing_kp=self.cert,
|
||||
)
|
||||
|
||||
@@ -155,7 +155,6 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
name_id="test@example.com",
|
||||
name_id_format=SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index="test-session-123",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
self.assertTrue(result)
|
||||
@@ -180,7 +179,6 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
name_id="test@example.com",
|
||||
name_id_format=SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index="test-session-123",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
self.assertFalse(result)
|
||||
@@ -200,7 +198,6 @@ class TestSendSamlLogoutRequest(TestCase):
|
||||
name_id="test@example.com",
|
||||
name_id_format=SAML_NAME_ID_FORMAT_EMAIL,
|
||||
session_index="test-session-123",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
|
||||
@@ -217,7 +214,7 @@ class TestSendPostLogoutRequest(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
signing_kp=self.cert,
|
||||
)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
invalidation_flow=self.invalidation_flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
)
|
||||
@@ -90,7 +90,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
# Verify logout request was stored in plan context
|
||||
self.assertIn("authentik/providers/saml/logout_request", view.plan_context)
|
||||
logout_request = view.plan_context["authentik/providers/saml/logout_request"]
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer)
|
||||
self.assertEqual(logout_request.session_index, "test-session-123")
|
||||
|
||||
def test_redirect_view_handles_logout_response_with_plan_context(self):
|
||||
@@ -228,7 +228,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
# Verify logout request was stored in plan context
|
||||
self.assertIn("authentik/providers/saml/logout_request", view.plan_context)
|
||||
logout_request = view.plan_context["authentik/providers/saml/logout_request"]
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer_override)
|
||||
self.assertEqual(logout_request.issuer, self.provider.issuer)
|
||||
self.assertEqual(logout_request.session_index, "test-session-123")
|
||||
|
||||
def test_post_view_handles_logout_response_with_plan_context(self):
|
||||
@@ -396,7 +396,7 @@ class TestSPInitiatedSLOViews(TestCase):
|
||||
authorization_flow=self.flow,
|
||||
acs_url="https://sp2.example.com/acs",
|
||||
sls_url="https://sp2.example.com/sls",
|
||||
issuer_override="https://idp2.example.com",
|
||||
issuer="https://idp2.example.com",
|
||||
invalidation_flow=None, # No invalidation flow
|
||||
)
|
||||
|
||||
@@ -524,7 +524,7 @@ class TestSPInitiatedSLOLogoutMethods(TestCase):
|
||||
invalidation_flow=self.invalidation_flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="https://sp.example.com/sls",
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
sp_binding="redirect",
|
||||
sls_binding="redirect",
|
||||
signing_kp=self.cert,
|
||||
@@ -714,7 +714,7 @@ class TestSPInitiatedSLOLogoutMethods(TestCase):
|
||||
invalidation_flow=self.invalidation_flow,
|
||||
acs_url="https://sp.example.com/acs",
|
||||
sls_url="", # No SLS URL
|
||||
issuer_override="https://idp.example.com",
|
||||
issuer="https://idp.example.com",
|
||||
)
|
||||
|
||||
app_no_sls = Application.objects.create(
|
||||
|
||||
@@ -4,26 +4,13 @@ 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, unified
|
||||
from authentik.providers.saml.views import metadata, sso
|
||||
from authentik.providers.saml.views.sp_slo import (
|
||||
SPInitiatedSLOBindingPOSTView,
|
||||
SPInitiatedSLOBindingRedirectView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# Unified Endpoint - handles SSO and SLO based on message type
|
||||
path(
|
||||
"<slug:application_slug>/",
|
||||
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/",
|
||||
|
||||
@@ -81,7 +81,6 @@ class SAMLFlowFinalView(ChallengeStageView):
|
||||
"session": auth_session,
|
||||
"name_id": processor.name_id,
|
||||
"name_id_format": processor.name_id_format,
|
||||
"issuer": processor.issuer,
|
||||
"expires": processor.session_not_on_or_after_datetime,
|
||||
"expiring": True,
|
||||
},
|
||||
|
||||
@@ -107,25 +107,12 @@ class SPInitiatedSLOView(PolicyAccessView):
|
||||
# Store relay state for the logout response
|
||||
plan.context[PLAN_CONTEXT_SAML_RELAY_STATE] = relay_state
|
||||
|
||||
# Look up the session issuer to use in the logout response
|
||||
auth_session = AuthenticatedSession.from_request(request, request.user)
|
||||
session_issuer = None
|
||||
if auth_session:
|
||||
saml_session = SAMLSession.objects.filter(
|
||||
session=auth_session,
|
||||
user=request.user,
|
||||
provider=self.provider,
|
||||
).first()
|
||||
if saml_session:
|
||||
session_issuer = saml_session.issuer
|
||||
|
||||
if self.provider.logout_method == SAMLLogoutMethods.FRONTCHANNEL_NATIVE:
|
||||
# Native mode - user will be redirected/posted away from authentik
|
||||
processor = LogoutResponseProcessor(
|
||||
self.provider,
|
||||
logout_request,
|
||||
destination=self.provider.sls_url,
|
||||
issuer=session_issuer,
|
||||
)
|
||||
|
||||
if self.provider.sls_binding == SAMLBindings.POST:
|
||||
@@ -165,7 +152,6 @@ class SPInitiatedSLOView(PolicyAccessView):
|
||||
sls_url=self.provider.sls_url,
|
||||
logout_request_id=logout_request.id if logout_request else None,
|
||||
relay_state=relay_state,
|
||||
issuer=session_issuer,
|
||||
)
|
||||
|
||||
LOGGER.debug(
|
||||
@@ -182,7 +168,6 @@ class SPInitiatedSLOView(PolicyAccessView):
|
||||
self.provider,
|
||||
logout_request,
|
||||
destination=self.provider.sls_url,
|
||||
issuer=session_issuer,
|
||||
)
|
||||
|
||||
logout_response = processor.build_response()
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
"""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)
|
||||
@@ -5,6 +5,7 @@ from hashlib import sha512
|
||||
from ipaddress import ip_address
|
||||
from time import perf_counter, time
|
||||
from typing import Any
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from channels.exceptions import DenyConnection
|
||||
from django.conf import settings
|
||||
@@ -314,6 +315,126 @@ class ChannelsLoggingMiddleware:
|
||||
)
|
||||
|
||||
|
||||
CSP_HEADER_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
||||
CSP_HEADER_ENFORCE = "Content-Security-Policy"
|
||||
|
||||
|
||||
class ContentSecurityPolicyMiddleware:
|
||||
"""Emit a Content-Security-Policy(-Report-Only) header carrying the per-request nonce.
|
||||
|
||||
The policy is intentionally strict: inline `<script>`/`<style>` are rejected unless
|
||||
they carry the request's nonce (set via `request.request_id` and exposed to templates
|
||||
as `csp_nonce`). External resources from third-party login providers (Apple, Telegram)
|
||||
and configurable captcha hosts are allow-listed below; the report-only mode lets the
|
||||
browser surface anything else as a console violation without breaking the page.
|
||||
"""
|
||||
|
||||
get_response: Callable[[HttpRequest], HttpResponse]
|
||||
|
||||
# Hosts that the bundled login flows pull resources from. The captcha stage allows
|
||||
# an admin-configured `js_url`, so the well-known third-party captcha origins are
|
||||
# included here so that report-only output is not drowned in expected violations.
|
||||
SCRIPT_SRC_THIRD_PARTY = (
|
||||
"https://appleid.cdn-apple.com",
|
||||
"https://telegram.org",
|
||||
"https://www.google.com",
|
||||
"https://www.gstatic.com",
|
||||
"https://www.recaptcha.net",
|
||||
"https://js.hcaptcha.com",
|
||||
"https://challenges.cloudflare.com",
|
||||
)
|
||||
FRAME_SRC_THIRD_PARTY = (
|
||||
"https://appleid.apple.com",
|
||||
"https://oauth.telegram.org",
|
||||
"https://www.google.com",
|
||||
"https://newassets.hcaptcha.com",
|
||||
"https://challenges.cloudflare.com",
|
||||
)
|
||||
|
||||
# Dev-only origins. The esbuild live-reload plugin opens an EventSource against
|
||||
# a dynamically chosen localhost port, so localhost on any scheme/port is allowed
|
||||
# when DEBUG is on. Never folded into prod policy.
|
||||
DEBUG_CONNECT_SRC = (
|
||||
"http://localhost:*",
|
||||
"https://localhost:*",
|
||||
"ws://localhost:*",
|
||||
"wss://localhost:*",
|
||||
)
|
||||
|
||||
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
|
||||
self.get_response = get_response
|
||||
self.report_only = CONFIG.get_bool("web.csp.report_only", True)
|
||||
self.debug = settings.DEBUG
|
||||
self.sentry_origin = self._sentry_origin(CONFIG.get("error_reporting.sentry_dsn", ""))
|
||||
|
||||
@staticmethod
|
||||
def _sentry_origin(dsn: str) -> str | None:
|
||||
"""Pull `scheme://host[:port]` out of a Sentry DSN so the browser SDK
|
||||
can ship envelopes to it. DSNs are `https://<key>@<host>/<project>`."""
|
||||
if not dsn:
|
||||
return None
|
||||
parts = urlsplit(dsn)
|
||||
if not parts.scheme or not parts.hostname:
|
||||
return None
|
||||
host = parts.hostname
|
||||
if parts.port:
|
||||
host = f"{host}:{parts.port}"
|
||||
return f"{parts.scheme}://{host}"
|
||||
|
||||
def _build_policy(self, nonce: str) -> str:
|
||||
nonce_token = f"'nonce-{nonce}'"
|
||||
script_src = ("'self'", nonce_token, *self.SCRIPT_SRC_THIRD_PARTY)
|
||||
# Per CSP3 §6.6.2.2, browsers ignore `'unsafe-inline'` whenever a
|
||||
# nonce is also present in the same source list. Several runtime
|
||||
# libraries we ship (mermaid, PatternFly's own style injections,
|
||||
# DOMPurify's sanitization sandbox) emit `<style>` elements
|
||||
# dynamically without a nonce, so we drop the nonce for styles
|
||||
# and rely on `'unsafe-inline'`. Script-side CSP is unaffected
|
||||
# — the eval/script protections remain strict.
|
||||
style_src = ("'self'", "'unsafe-inline'")
|
||||
frame_src = ("'self'", *self.FRAME_SRC_THIRD_PARTY)
|
||||
connect_src: tuple[str, ...] = ("'self'", "ws:", "wss:")
|
||||
if self.sentry_origin:
|
||||
connect_src = (*connect_src, self.sentry_origin)
|
||||
if self.debug:
|
||||
connect_src = (*connect_src, *self.DEBUG_CONNECT_SRC)
|
||||
directives = {
|
||||
"default-src": ("'self'",),
|
||||
"script-src": script_src,
|
||||
"style-src": style_src,
|
||||
# Inline `style="..."` attributes can't carry a nonce; many libraries
|
||||
# (PatternFly, Lit style bindings) set them dynamically.
|
||||
"style-src-attr": ("'unsafe-inline'",),
|
||||
"img-src": ("'self'", "data:", "blob:", "https:"),
|
||||
"font-src": ("'self'", "data:"),
|
||||
"connect-src": connect_src,
|
||||
"frame-src": frame_src,
|
||||
"media-src": ("'self'",),
|
||||
"worker-src": ("'self'", "blob:"),
|
||||
"object-src": ("'none'",),
|
||||
"base-uri": ("'self'",),
|
||||
"form-action": ("'self'",),
|
||||
"frame-ancestors": ("'none'",),
|
||||
}
|
||||
return "; ".join(f"{name} {' '.join(values)}" for name, values in directives.items())
|
||||
|
||||
def __call__(self, request: HttpRequest) -> HttpResponse:
|
||||
response = self.get_response(request)
|
||||
# Only attach to HTML responses — CSP on JSON/binary responses is just header bloat.
|
||||
content_type = response.get("Content-Type", "")
|
||||
if not content_type.startswith("text/html"):
|
||||
return response
|
||||
nonce = getattr(request, "request_id", None)
|
||||
if not nonce:
|
||||
return response
|
||||
header = CSP_HEADER_REPORT_ONLY if self.report_only else CSP_HEADER_ENFORCE
|
||||
# Don't clobber a policy a downstream view explicitly set.
|
||||
if header in response:
|
||||
return response
|
||||
response[header] = self._build_policy(nonce)
|
||||
return response
|
||||
|
||||
|
||||
class LoggingMiddleware:
|
||||
"""Logger middleware"""
|
||||
|
||||
|
||||
@@ -276,6 +276,7 @@ MIDDLEWARE = [
|
||||
"authentik.core.middleware.RequestIDMiddleware",
|
||||
"authentik.brands.middleware.BrandMiddleware",
|
||||
"authentik.events.middleware.AuditMiddleware",
|
||||
"authentik.root.middleware.ContentSecurityPolicyMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"authentik.root.middleware.CsrfViewMiddleware",
|
||||
|
||||
@@ -94,7 +94,7 @@ class OAuthCallback(OAuthClientMixin, View):
|
||||
def get_user_id(self, info: dict[str, Any]) -> str | None:
|
||||
"""Return unique identifier from the profile info."""
|
||||
if "id" in info:
|
||||
return str(info["id"])
|
||||
return info["id"]
|
||||
return None
|
||||
|
||||
def handle_login_failure(self, reason: str) -> HttpResponse:
|
||||
|
||||
@@ -353,7 +353,7 @@ class IdentificationStageView(ChallengeStageView):
|
||||
PLAN_CONTEXT_APPLICATION, Application()
|
||||
)
|
||||
challenge.initial_data["application_pre"] = app.name
|
||||
if not app.meta_hide and (launch_url := app.get_launch_url()):
|
||||
if launch_url := app.get_launch_url():
|
||||
challenge.initial_data["application_pre_launch"] = launch_url
|
||||
if (
|
||||
PLAN_CONTEXT_DEVICE in self.executor.plan.context
|
||||
|
||||
@@ -5215,11 +5215,6 @@
|
||||
"type": "string",
|
||||
"title": "Group"
|
||||
},
|
||||
"meta_hide": {
|
||||
"type": "boolean",
|
||||
"title": "Meta hide",
|
||||
"description": "Hide this application from the user's My applications page."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
@@ -10817,10 +10812,11 @@
|
||||
"title": "Audience",
|
||||
"description": "Value of the audience restriction field of the assertion. When left empty, no audience restriction will be added."
|
||||
},
|
||||
"issuer_override": {
|
||||
"issuer": {
|
||||
"type": "string",
|
||||
"title": "Issuer override",
|
||||
"description": "Also known as EntityID. Providing a value overrides the default issuer generated by authentik."
|
||||
"minLength": 1,
|
||||
"title": "Issuer",
|
||||
"description": "Also known as EntityID"
|
||||
},
|
||||
"assertion_valid_not_before": {
|
||||
"type": "string",
|
||||
|
||||
6
build.rs
6
build.rs
@@ -1,6 +0,0 @@
|
||||
fn main() {
|
||||
#[cfg(feature = "core")]
|
||||
{
|
||||
pyo3_build_config::add_libpython_rpath_link_args();
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,6 @@ RUN --mount=type=bind,target=rust-toolchain.toml,src=rust-toolchain.toml \
|
||||
--mount=type=bind,target=Cargo.toml,src=Cargo.toml \
|
||||
--mount=type=bind,target=Cargo.lock,src=Cargo.lock \
|
||||
--mount=type=bind,target=.cargo/,src=.cargo/ \
|
||||
--mount=type=bind,target=build.rs,src=build.rs \
|
||||
--mount=type=bind,target=src/,src=src/ \
|
||||
--mount=type=bind,target=packages/ak-axum,src=packages/ak-axum \
|
||||
--mount=type=bind,target=packages/ak-common,src=packages/ak-common \
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 00:30+0000\n"
|
||||
"POT-Creation-Date: 2026-04-23 00:25+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -2717,10 +2717,6 @@ msgstr ""
|
||||
msgid "Webex"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "vCenter"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "Group filters used to define sync-scope for groups."
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,7 @@
|
||||
//! Utilities for working with [`Router`].
|
||||
|
||||
use ak_common::config;
|
||||
use axum::{
|
||||
Router,
|
||||
extract::Request,
|
||||
http::{HeaderName, HeaderValue, StatusCode},
|
||||
middleware::{Next, from_fn},
|
||||
response::Response,
|
||||
};
|
||||
use axum::{Router, http::StatusCode, middleware::from_fn};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::timeout::TimeoutLayer;
|
||||
|
||||
@@ -19,16 +13,6 @@ use crate::{
|
||||
tracing::{span_middleware, tracing_middleware},
|
||||
};
|
||||
|
||||
const X_POWERED_BY: HeaderName = HeaderName::from_static("x-powered-by");
|
||||
|
||||
async fn powered_by_authentik_middleware(request: Request, next: Next) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(X_POWERED_BY, HeaderValue::from_static("authentik"));
|
||||
response
|
||||
}
|
||||
|
||||
/// Wrap a [`Router`] with common middlewares.
|
||||
///
|
||||
/// Set `with_tracing` to [`true`] to log requests.
|
||||
@@ -46,7 +30,6 @@ pub fn wrap_router(router: Router, with_tracing: bool) -> Router {
|
||||
timeout,
|
||||
))
|
||||
.layer(from_fn(span_middleware))
|
||||
.layer(from_fn(powered_by_authentik_middleware))
|
||||
.layer(from_fn(trusted_proxy_middleware))
|
||||
.layer(from_fn(client_ip_middleware))
|
||||
.layer(from_fn(scheme_middleware))
|
||||
|
||||
12
packages/client-ts/src/apis/ProvidersApi.ts
generated
12
packages/client-ts/src/apis/ProvidersApi.ts
generated
@@ -634,7 +634,7 @@ export interface ProvidersSamlListRequest {
|
||||
encryptionKp?: string;
|
||||
invalidationFlow?: string;
|
||||
isBackchannel?: boolean;
|
||||
issuerOverride?: string;
|
||||
issuer?: string;
|
||||
logoutMethod?: SAMLLogoutMethods;
|
||||
name?: string;
|
||||
nameIdMapping?: string;
|
||||
@@ -841,7 +841,7 @@ export interface ProvidersWsfedListRequest {
|
||||
encryptionKp?: string;
|
||||
invalidationFlow?: string;
|
||||
isBackchannel?: boolean;
|
||||
issuerOverride?: string;
|
||||
issuer?: string;
|
||||
logoutMethod?: SAMLLogoutMethods;
|
||||
name?: string;
|
||||
nameIdMapping?: string;
|
||||
@@ -6842,8 +6842,8 @@ export class ProvidersApi extends runtime.BaseAPI {
|
||||
queryParameters["is_backchannel"] = requestParameters["isBackchannel"];
|
||||
}
|
||||
|
||||
if (requestParameters["issuerOverride"] != null) {
|
||||
queryParameters["issuer_override"] = requestParameters["issuerOverride"];
|
||||
if (requestParameters["issuer"] != null) {
|
||||
queryParameters["issuer"] = requestParameters["issuer"];
|
||||
}
|
||||
|
||||
if (requestParameters["logoutMethod"] != null) {
|
||||
@@ -9326,8 +9326,8 @@ export class ProvidersApi extends runtime.BaseAPI {
|
||||
queryParameters["is_backchannel"] = requestParameters["isBackchannel"];
|
||||
}
|
||||
|
||||
if (requestParameters["issuerOverride"] != null) {
|
||||
queryParameters["issuer_override"] = requestParameters["issuerOverride"];
|
||||
if (requestParameters["issuer"] != null) {
|
||||
queryParameters["issuer"] = requestParameters["issuer"];
|
||||
}
|
||||
|
||||
if (requestParameters["logoutMethod"] != null) {
|
||||
|
||||
8
packages/client-ts/src/models/Application.ts
generated
8
packages/client-ts/src/models/Application.ts
generated
@@ -127,12 +127,6 @@ export interface Application {
|
||||
* @memberof Application
|
||||
*/
|
||||
group?: string;
|
||||
/**
|
||||
* Hide this application from the user's My applications page.
|
||||
* @type {boolean}
|
||||
* @memberof Application
|
||||
*/
|
||||
metaHide?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +177,6 @@ export function ApplicationFromJSONTyped(json: any, ignoreDiscriminator: boolean
|
||||
? undefined
|
||||
: PolicyEngineModeFromJSON(json["policy_engine_mode"]),
|
||||
group: json["group"] == null ? undefined : json["group"],
|
||||
metaHide: json["meta_hide"] == null ? undefined : json["meta_hide"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,6 +212,5 @@ export function ApplicationToJSONTyped(
|
||||
meta_publisher: value["metaPublisher"],
|
||||
policy_engine_mode: PolicyEngineModeToJSON(value["policyEngineMode"]),
|
||||
group: value["group"],
|
||||
meta_hide: value["metaHide"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,12 +87,6 @@ export interface ApplicationRequest {
|
||||
* @memberof ApplicationRequest
|
||||
*/
|
||||
group?: string;
|
||||
/**
|
||||
* Hide this application from the user's My applications page.
|
||||
* @type {boolean}
|
||||
* @memberof ApplicationRequest
|
||||
*/
|
||||
metaHide?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +125,6 @@ export function ApplicationRequestFromJSONTyped(
|
||||
? undefined
|
||||
: PolicyEngineModeFromJSON(json["policy_engine_mode"]),
|
||||
group: json["group"] == null ? undefined : json["group"],
|
||||
metaHide: json["meta_hide"] == null ? undefined : json["meta_hide"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,6 +152,5 @@ export function ApplicationRequestToJSONTyped(
|
||||
meta_publisher: value["metaPublisher"],
|
||||
policy_engine_mode: PolicyEngineModeToJSON(value["policyEngineMode"]),
|
||||
group: value["group"],
|
||||
meta_hide: value["metaHide"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,12 +87,6 @@ export interface PatchedApplicationRequest {
|
||||
* @memberof PatchedApplicationRequest
|
||||
*/
|
||||
group?: string;
|
||||
/**
|
||||
* Hide this application from the user's My applications page.
|
||||
* @type {boolean}
|
||||
* @memberof PatchedApplicationRequest
|
||||
*/
|
||||
metaHide?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +125,6 @@ export function PatchedApplicationRequestFromJSONTyped(
|
||||
? undefined
|
||||
: PolicyEngineModeFromJSON(json["policy_engine_mode"]),
|
||||
group: json["group"] == null ? undefined : json["group"],
|
||||
metaHide: json["meta_hide"] == null ? undefined : json["meta_hide"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,6 +152,5 @@ export function PatchedApplicationRequestToJSONTyped(
|
||||
meta_publisher: value["metaPublisher"],
|
||||
policy_engine_mode: PolicyEngineModeToJSON(value["policyEngineMode"]),
|
||||
group: value["group"],
|
||||
meta_hide: value["metaHide"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ export interface PatchedSAMLProviderRequest {
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* Also known as EntityID. Providing a value overrides the default issuer generated by authentik.
|
||||
* Also known as EntityID
|
||||
* @type {string}
|
||||
* @memberof PatchedSAMLProviderRequest
|
||||
*/
|
||||
issuerOverride?: string;
|
||||
issuer?: string;
|
||||
/**
|
||||
* Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).
|
||||
* @type {string}
|
||||
@@ -233,7 +233,7 @@ export function PatchedSAMLProviderRequestFromJSONTyped(
|
||||
acsUrl: json["acs_url"] == null ? undefined : json["acs_url"],
|
||||
slsUrl: json["sls_url"] == null ? undefined : json["sls_url"],
|
||||
audience: json["audience"] == null ? undefined : json["audience"],
|
||||
issuerOverride: json["issuer_override"] == null ? undefined : json["issuer_override"],
|
||||
issuer: json["issuer"] == null ? undefined : json["issuer"],
|
||||
assertionValidNotBefore:
|
||||
json["assertion_valid_not_before"] == null
|
||||
? undefined
|
||||
@@ -306,7 +306,7 @@ export function PatchedSAMLProviderRequestToJSONTyped(
|
||||
acs_url: value["acsUrl"],
|
||||
sls_url: value["slsUrl"],
|
||||
audience: value["audience"],
|
||||
issuer_override: value["issuerOverride"],
|
||||
issuer: value["issuer"],
|
||||
assertion_valid_not_before: value["assertionValidNotBefore"],
|
||||
assertion_valid_not_on_or_after: value["assertionValidNotOnOrAfter"],
|
||||
session_valid_not_on_or_after: value["sessionValidNotOnOrAfter"],
|
||||
|
||||
35
packages/client-ts/src/models/SAMLProvider.ts
generated
35
packages/client-ts/src/models/SAMLProvider.ts
generated
@@ -135,11 +135,11 @@ export interface SAMLProvider {
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* Also known as EntityID. Providing a value overrides the default issuer generated by authentik.
|
||||
* Also known as EntityID
|
||||
* @type {string}
|
||||
* @memberof SAMLProvider
|
||||
*/
|
||||
issuerOverride?: string;
|
||||
issuer?: string;
|
||||
/**
|
||||
* Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).
|
||||
* @type {string}
|
||||
@@ -260,24 +260,6 @@ export interface SAMLProvider {
|
||||
* @memberof SAMLProvider
|
||||
*/
|
||||
readonly urlDownloadMetadata: string;
|
||||
/**
|
||||
* Get Issuer/EntityID URL
|
||||
* @type {string}
|
||||
* @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}
|
||||
@@ -339,9 +321,6 @@ export function instanceOfSAMLProvider(value: object): value is SAMLProvider {
|
||||
if (!("acsUrl" in value) || value["acsUrl"] === undefined) return false;
|
||||
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;
|
||||
@@ -377,7 +356,7 @@ export function SAMLProviderFromJSONTyped(json: any, ignoreDiscriminator: boolea
|
||||
acsUrl: json["acs_url"],
|
||||
slsUrl: json["sls_url"] == null ? undefined : json["sls_url"],
|
||||
audience: json["audience"] == null ? undefined : json["audience"],
|
||||
issuerOverride: json["issuer_override"] == null ? undefined : json["issuer_override"],
|
||||
issuer: json["issuer"] == null ? undefined : json["issuer"],
|
||||
assertionValidNotBefore:
|
||||
json["assertion_valid_not_before"] == null
|
||||
? undefined
|
||||
@@ -427,9 +406,6 @@ export function SAMLProviderFromJSONTyped(json: any, ignoreDiscriminator: boolea
|
||||
? undefined
|
||||
: 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"],
|
||||
@@ -455,9 +431,6 @@ export function SAMLProviderToJSONTyped(
|
||||
| "verbose_name_plural"
|
||||
| "meta_model_name"
|
||||
| "url_download_metadata"
|
||||
| "url_issuer"
|
||||
| "url_unified"
|
||||
| "url_unified_init"
|
||||
| "url_sso_post"
|
||||
| "url_sso_redirect"
|
||||
| "url_sso_init"
|
||||
@@ -479,7 +452,7 @@ export function SAMLProviderToJSONTyped(
|
||||
acs_url: value["acsUrl"],
|
||||
sls_url: value["slsUrl"],
|
||||
audience: value["audience"],
|
||||
issuer_override: value["issuerOverride"],
|
||||
issuer: value["issuer"],
|
||||
assertion_valid_not_before: value["assertionValidNotBefore"],
|
||||
assertion_valid_not_on_or_after: value["assertionValidNotOnOrAfter"],
|
||||
session_valid_not_on_or_after: value["sessionValidNotOnOrAfter"],
|
||||
|
||||
@@ -81,11 +81,11 @@ export interface SAMLProviderRequest {
|
||||
*/
|
||||
audience?: string;
|
||||
/**
|
||||
* Also known as EntityID. Providing a value overrides the default issuer generated by authentik.
|
||||
* Also known as EntityID
|
||||
* @type {string}
|
||||
* @memberof SAMLProviderRequest
|
||||
*/
|
||||
issuerOverride?: string;
|
||||
issuer?: string;
|
||||
/**
|
||||
* Assertion valid not before current time + this value (Format: hours=-1;minutes=-2;seconds=-3).
|
||||
* @type {string}
|
||||
@@ -234,7 +234,7 @@ export function SAMLProviderRequestFromJSONTyped(
|
||||
acsUrl: json["acs_url"],
|
||||
slsUrl: json["sls_url"] == null ? undefined : json["sls_url"],
|
||||
audience: json["audience"] == null ? undefined : json["audience"],
|
||||
issuerOverride: json["issuer_override"] == null ? undefined : json["issuer_override"],
|
||||
issuer: json["issuer"] == null ? undefined : json["issuer"],
|
||||
assertionValidNotBefore:
|
||||
json["assertion_valid_not_before"] == null
|
||||
? undefined
|
||||
@@ -307,7 +307,7 @@ export function SAMLProviderRequestToJSONTyped(
|
||||
acs_url: value["acsUrl"],
|
||||
sls_url: value["slsUrl"],
|
||||
audience: value["audience"],
|
||||
issuer_override: value["issuerOverride"],
|
||||
issuer: value["issuer"],
|
||||
assertion_valid_not_before: value["assertionValidNotBefore"],
|
||||
assertion_valid_not_on_or_after: value["assertionValidNotOnOrAfter"],
|
||||
session_valid_not_on_or_after: value["sessionValidNotOnOrAfter"],
|
||||
|
||||
@@ -97,13 +97,13 @@ dev = [
|
||||
"pytest-django==4.12.0",
|
||||
"pytest-flakefinder==1.1.0",
|
||||
"pytest-github-actions-annotate-failures==0.4.0",
|
||||
"pytest-randomly==4.1.0",
|
||||
"pytest-randomly==4.0.1",
|
||||
"pytest-timeout==2.4.0",
|
||||
"pytest==9.0.3",
|
||||
"requests-mock==1.12.1",
|
||||
"ruff==0.15.12",
|
||||
"ruff==0.15.11",
|
||||
"selenium==4.43.0",
|
||||
"types-channels==4.3.0.20260421",
|
||||
"types-channels==4.3.0.20260408",
|
||||
"types-docker==7.1.0.20260409",
|
||||
"types-jwcrypto==1.5.7.20260409",
|
||||
"types-ldap3==2.9.13.20260408",
|
||||
|
||||
45
schema.yml
45
schema.yml
@@ -18919,7 +18919,7 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: issuer_override
|
||||
name: issuer
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
@@ -20078,7 +20078,7 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: issuer_override
|
||||
name: issuer
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
@@ -34111,9 +34111,6 @@ components:
|
||||
$ref: '#/components/schemas/PolicyEngineMode'
|
||||
group:
|
||||
type: string
|
||||
meta_hide:
|
||||
type: boolean
|
||||
description: Hide this application from the user's My applications page.
|
||||
required:
|
||||
- backchannel_providers_obj
|
||||
- launch_url
|
||||
@@ -34195,9 +34192,6 @@ components:
|
||||
$ref: '#/components/schemas/PolicyEngineMode'
|
||||
group:
|
||||
type: string
|
||||
meta_hide:
|
||||
type: boolean
|
||||
description: Hide this application from the user's My applications page.
|
||||
required:
|
||||
- name
|
||||
- slug
|
||||
@@ -47434,9 +47428,6 @@ components:
|
||||
$ref: '#/components/schemas/PolicyEngineMode'
|
||||
group:
|
||||
type: string
|
||||
meta_hide:
|
||||
type: boolean
|
||||
description: Hide this application from the user's My applications page.
|
||||
PatchedAuthenticatorDuoStageRequest:
|
||||
type: object
|
||||
description: AuthenticatorDuoStage Serializer
|
||||
@@ -50202,10 +50193,10 @@ components:
|
||||
type: string
|
||||
description: Value of the audience restriction field of the assertion. When
|
||||
left empty, no audience restriction will be added.
|
||||
issuer_override:
|
||||
issuer:
|
||||
type: string
|
||||
description: Also known as EntityID. Providing a value overrides the default
|
||||
issuer generated by authentik.
|
||||
minLength: 1
|
||||
description: Also known as EntityID
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -53724,10 +53715,9 @@ components:
|
||||
type: string
|
||||
description: Value of the audience restriction field of the assertion. When
|
||||
left empty, no audience restriction will be added.
|
||||
issuer_override:
|
||||
issuer:
|
||||
type: string
|
||||
description: Also known as EntityID. Providing a value overrides the default
|
||||
issuer generated by authentik.
|
||||
description: Also known as EntityID
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
description: 'Assertion valid not before current time + this value (Format:
|
||||
@@ -53817,18 +53807,6 @@ components:
|
||||
type: string
|
||||
description: Get metadata download URL
|
||||
readOnly: true
|
||||
url_issuer:
|
||||
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
|
||||
@@ -53862,14 +53840,11 @@ components:
|
||||
- name
|
||||
- pk
|
||||
- url_download_metadata
|
||||
- url_issuer
|
||||
- url_slo_post
|
||||
- url_slo_redirect
|
||||
- url_sso_init
|
||||
- url_sso_post
|
||||
- url_sso_redirect
|
||||
- url_unified
|
||||
- url_unified_init
|
||||
- verbose_name
|
||||
- verbose_name_plural
|
||||
SAMLProviderImportRequest:
|
||||
@@ -53932,10 +53907,10 @@ components:
|
||||
type: string
|
||||
description: Value of the audience restriction field of the assertion. When
|
||||
left empty, no audience restriction will be added.
|
||||
issuer_override:
|
||||
issuer:
|
||||
type: string
|
||||
description: Also known as EntityID. Providing a value overrides the default
|
||||
issuer generated by authentik.
|
||||
minLength: 1
|
||||
description: Also known as EntityID
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
minLength: 1
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"9009": "9009",
|
||||
},
|
||||
environment={
|
||||
"SP_ENTITY_ID": provider.issuer_override,
|
||||
"SP_ENTITY_ID": provider.issuer,
|
||||
"SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
||||
"SP_METADATA_URL": metadata_url,
|
||||
**kwargs,
|
||||
@@ -68,7 +68,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -147,7 +147,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -226,7 +226,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -321,7 +321,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -415,7 +415,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -503,7 +503,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -553,7 +553,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer_override="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
invalidation_flow=invalidation_flow,
|
||||
|
||||
@@ -201,15 +201,6 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
)}
|
||||
>
|
||||
</ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="metaHide"
|
||||
?checked=${this.instance?.metaHide ?? false}
|
||||
label=${msg("Hide from My applications")}
|
||||
help=${msg(
|
||||
"If checked, this application will not be shown on the user's My applications page.",
|
||||
)}
|
||||
>
|
||||
</ak-switch-input>
|
||||
<ak-file-search-input
|
||||
name="metaIcon"
|
||||
label=${msg("Icon")}
|
||||
|
||||
@@ -42,7 +42,7 @@ const renderSAMLOverview: ProviderOverview<SAMLProvider> = (provider) => {
|
||||
return renderSummary("SAML", provider.name, [
|
||||
[msg("ACS URL"), provider.acsUrl],
|
||||
[msg("Audience"), provider.audience || "-"],
|
||||
[msg("Issuer"), provider.urlIssuer],
|
||||
[msg("Issuer"), provider.issuer],
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@@ -187,15 +187,6 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
)}
|
||||
>
|
||||
</ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="metaHide"
|
||||
?checked=${app.metaHide ?? false}
|
||||
label=${msg("Hide from My applications")}
|
||||
help=${msg(
|
||||
"If checked, this application will not be shown on the user's My applications page.",
|
||||
)}
|
||||
>
|
||||
</ak-switch-input>
|
||||
<ak-file-search-input
|
||||
name="metaIcon"
|
||||
label=${msg("Icon")}
|
||||
|
||||
@@ -38,20 +38,22 @@ export interface SelectedFeatureEventDetail {
|
||||
|
||||
@customElement("ak-map")
|
||||
export class Map extends OlMap {
|
||||
public styles: CSSResult[] = [
|
||||
OlMap.styles,
|
||||
OL,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<style>
|
||||
${OL}
|
||||
</style>
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<div id="map"></div>
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
@@ -196,6 +196,15 @@ export function renderForm({
|
||||
required
|
||||
.errorMessages=${errors.acsUrl}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("Issuer")}
|
||||
input-hint="code"
|
||||
name="issuer"
|
||||
value="${provider.issuer || "authentik"}"
|
||||
required
|
||||
.errorMessages=${errors.issuer}
|
||||
help=${msg("Also known as Entity ID.")}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="audience"
|
||||
label=${msg("Audience")}
|
||||
@@ -427,15 +436,6 @@ export function renderForm({
|
||||
"When using IDP-initiated logins, the relay state will be set to this value.",
|
||||
)}
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("EntityID/Issuer override")}
|
||||
name="issuerOverride"
|
||||
value="${ifDefined(provider.issuerOverride ?? undefined)}"
|
||||
.errorMessages=${errors.issuerOverride}
|
||||
help=${msg(
|
||||
"Sets a custom EntityID/Issuer to override the authentik generated default.",
|
||||
)}
|
||||
></ak-text-input>
|
||||
<ak-radio-input
|
||||
label=${msg("Service Provider Binding")}
|
||||
name="spBinding"
|
||||
|
||||
@@ -350,7 +350,7 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.provider.issuerOverride}
|
||||
${this.provider.issuer}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -385,26 +385,34 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider?.urlIssuer)}"
|
||||
value="${ifDefined(this.provider?.issuer)}"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text"
|
||||
>${msg("SAML Endpoint")}</span
|
||||
>${msg("SSO URL (Post)")}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider.urlUnified)}"
|
||||
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)}"
|
||||
/>
|
||||
<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">
|
||||
@@ -416,7 +424,33 @@ export class SAMLProviderViewPage extends AKElement {
|
||||
class="pf-c-form-control"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifDefined(this.provider.urlUnifiedInit)}"
|
||||
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)}"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -40,7 +40,11 @@ export class FlowMultitabController implements ReactiveController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIdentificationChallenge(challenge) && challenge.applicationPreLaunch) {
|
||||
if (
|
||||
isIdentificationChallenge(challenge) &&
|
||||
challenge.applicationPreLaunch &&
|
||||
challenge.applicationPreLaunch !== "blank://blank"
|
||||
) {
|
||||
multiTabOrchestrateLeave();
|
||||
window.location.assign(challenge.applicationPreLaunch);
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ export class AppleLoginInit extends BaseStage<AppleLoginChallenge, AppleChalleng
|
||||
|
||||
firstUpdated(): void {
|
||||
const appleAuth = document.createElement("script");
|
||||
appleAuth.nonce = window.litNonce;
|
||||
appleAuth.src =
|
||||
"https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js";
|
||||
appleAuth.type = "text/javascript";
|
||||
|
||||
@@ -17,6 +17,8 @@ export function loadTelegramWidget(
|
||||
const widgetScript = document.createElement("script");
|
||||
widgetScript.src = "https://telegram.org/js/telegram-widget.js?22";
|
||||
widgetScript.type = "text/javascript";
|
||||
widgetScript.nonce = window.litNonce || "";
|
||||
|
||||
widgetScript.setAttribute("data-radius", "0");
|
||||
widgetScript.setAttribute("data-telegram-login", botUsername);
|
||||
if (requestMessageAccess) {
|
||||
|
||||
@@ -308,6 +308,7 @@ export class CaptchaStage
|
||||
const scriptElement = document.createElement("script");
|
||||
|
||||
scriptElement.src = challengeURL.toString();
|
||||
scriptElement.nonce = window.litNonce || "";
|
||||
scriptElement.async = true;
|
||||
scriptElement.defer = true;
|
||||
scriptElement.onload = this.#scriptLoadListener;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// sort-imports-ignore
|
||||
import "@webcomponents/webcomponentsjs";
|
||||
import "lit/polyfill-support.js";
|
||||
import "core-js/actual";
|
||||
import "@formatjs/intl-listformat/polyfill.js";
|
||||
|
||||
@@ -10968,36 +10968,6 @@ Vazby na skupiny/uživatele jsou kontrolovány vůči uživateli události.</tar
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -11001,36 +11001,6 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -8971,36 +8971,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10926,36 +10926,6 @@ Las vinculaciones a grupos/usuarios se verifican en función del usuario del eve
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -11168,36 +11168,6 @@ Liitokset käyttäjiin/ryhmiin tarkistetaan tapahtuman käyttäjästä.</target>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -11156,36 +11156,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10875,36 +10875,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -11157,36 +11157,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10523,36 +10523,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10207,36 +10207,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
11970
web/xliff/no_NO.xlf
11970
web/xliff/no_NO.xlf
File diff suppressed because it is too large
Load Diff
@@ -10549,36 +10549,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -11149,36 +11149,6 @@ por exemplo: <x id="0" equiv-text="<code>"/>oci://registry.domain.tld/path
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10635,36 +10635,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10624,36 +10624,6 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -11429,36 +11429,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -10260,36 +10260,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source><x id="0" equiv-text="${verboseName}"/> is not associated with any objects.</source>
|
||||
<note from="lit-localize">Zero: no objects use this entity.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="s78a13f8f08d3a317">
|
||||
<source>Authorization Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s9765a1c7821ec626">
|
||||
<source>Implicit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6032372e5812d743">
|
||||
<source>Hybrid</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c21579d264ff019">
|
||||
<source>Refresh token</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sec04243bf971ba5c">
|
||||
<source>Client credentials</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf6625f1c093feb17">
|
||||
<source>Device-code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sccb6f4a3d1fb9434">
|
||||
<source>Grant Types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s89c63fb4a8cb6d2b">
|
||||
<source>Grant types this provider may use.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s86a9472dc09ecf48">
|
||||
<source>vCenter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s71724d2ff6637203">
|
||||
<source>Altered behavior for usage with VMware vCenter.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -30,7 +30,7 @@ The following options can be configured:
|
||||
|
||||
For a reference of all fields available, see [the API schema for the User object](https://api.goauthentik.io/reference/core-users-retrieve/).
|
||||
|
||||
Only apps with launch URLs that begin with `http://` or `https://`, or that use relative paths, appear on the user's **My applications** page. To keep an app accessible but remove it from that page, use the **Hide from My applications** option (see [Hide applications](./manage_apps.mdx#hide-applications)).
|
||||
Only applications whose launch URL starts with `http://` or `https://` or are relative URLs are shown on the users' **My applications** page. This can also be used to hide applications that shouldn't be visible on the **My applications** page but are still accessible by users, by setting the _Launch URL_ to `blank://blank`.
|
||||
|
||||
- _Icon (URL)_: Optionally configure an icon for the application. You can select from files uploaded to the [Files](../../customize/files.md) library or enter an absolute URL.
|
||||
|
||||
|
||||
@@ -104,14 +104,10 @@ return {
|
||||
|
||||
## Hide applications
|
||||
|
||||
To hide an application without modifying its policy settings or removing it, you can use the **Hide from My applications** option on the application. The application will no longer appear on the **My applications** page.
|
||||
To hide an application without modifying its policy settings or removing it, you can simply set the _Launch URL_ to `blank://blank`, which will hide the application from users.
|
||||
|
||||
Keep in mind that users still have access, so they can still authorize access when the login process is started from the application.
|
||||
|
||||
:::info Hiding applications before 2026.5
|
||||
Before authentik 2026.5, an application was hidden by setting its **Launch URL** to `blank://blank`. Existing applications using that value are automatically migrated to using the **Hide from My applications** option upon upgrading.
|
||||
:::
|
||||
|
||||
## Launch URLs
|
||||
|
||||
To give users direct links to applications, you can now use a URL like `https://authentik.company/application/launch/<slug>/`. If the user is already logged in, they will be redirected to the application automatically. Otherwise, they'll be sent to the authentication flow and, if successful, forwarded to the application.
|
||||
|
||||
Reference in New Issue
Block a user