Compare commits
15 Commits
website/do
...
flows/buff
Author | SHA1 | Date | |
---|---|---|---|
b0e6558a4f | |||
81e5fef667 | |||
7aa6593760 | |||
c40a17beb9 | |||
335c9fbc10 | |||
51b53caf61 | |||
989100a900 | |||
8e1531d051 | |||
f6f37d6d92 | |||
5b6ca70f22 | |||
a74674c3d6 | |||
f46984dec4 | |||
c7963e4af7 | |||
6e30b11974 | |||
13bd4069e4 |
@ -75,7 +75,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 4: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.12 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.7.13 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.4-slim-bookworm-fips AS python-base
|
||||
|
||||
|
@ -134,7 +134,7 @@ class Command(BaseCommand):
|
||||
"id": {"type": "string"},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [s.value for s in BlueprintEntryDesiredState],
|
||||
"enum": sorted([s.value for s in BlueprintEntryDesiredState]),
|
||||
"default": "present",
|
||||
},
|
||||
"conditions": {"type": "array", "items": {"type": "boolean"}},
|
||||
@ -205,7 +205,7 @@ class Command(BaseCommand):
|
||||
"type": "object",
|
||||
"required": ["permission"],
|
||||
"properties": {
|
||||
"permission": {"type": "string", "enum": perms},
|
||||
"permission": {"type": "string", "enum": sorted(perms)},
|
||||
"user": {"type": "integer"},
|
||||
"role": {"type": "string"},
|
||||
},
|
||||
|
@ -47,7 +47,7 @@ class MetaModelRegistry:
|
||||
models = apps.get_models()
|
||||
for _, value in self.models.items():
|
||||
models.append(value)
|
||||
return models
|
||||
return sorted(models, key=str)
|
||||
|
||||
def get_model(self, app_label: str, model_id: str) -> type[Model]:
|
||||
"""Get model checks if any virtual models are registered, and falls back
|
||||
|
@ -69,6 +69,7 @@ SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre"
|
||||
SESSION_KEY_GET = "authentik/flows/get"
|
||||
SESSION_KEY_POST = "authentik/flows/post"
|
||||
SESSION_KEY_HISTORY = "authentik/flows/history"
|
||||
SESSION_KEY_AUTH_STARTED = "authentik/flows/auth_started"
|
||||
QS_KEY_TOKEN = "flow_token" # nosec
|
||||
QS_QUERY = "query"
|
||||
|
||||
@ -454,6 +455,7 @@ class FlowExecutorView(APIView):
|
||||
SESSION_KEY_APPLICATION_PRE,
|
||||
SESSION_KEY_PLAN,
|
||||
SESSION_KEY_GET,
|
||||
SESSION_KEY_AUTH_STARTED,
|
||||
# We might need the initial POST payloads for later requests
|
||||
# SESSION_KEY_POST,
|
||||
# We don't delete the history on purpose, as a user might
|
||||
|
@ -6,7 +6,8 @@ from django.shortcuts import get_object_or_404
|
||||
from ua_parser.user_agent_parser import Parse
|
||||
|
||||
from authentik.core.views.interface import InterfaceView
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.flows.views.executor import SESSION_KEY_AUTH_STARTED
|
||||
|
||||
|
||||
class FlowInterfaceView(InterfaceView):
|
||||
@ -14,6 +15,12 @@ class FlowInterfaceView(InterfaceView):
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
flow = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||
if (
|
||||
not self.request.user.is_authenticated
|
||||
and flow.designation == FlowDesignation.AUTHENTICATION
|
||||
):
|
||||
self.request.session[SESSION_KEY_AUTH_STARTED] = True
|
||||
self.request.session.save()
|
||||
kwargs["flow"] = flow
|
||||
kwargs["flow_background_url"] = flow.background_url(self.request)
|
||||
kwargs["inspector"] = "inspector" in self.request.GET
|
||||
|
@ -1,11 +1,9 @@
|
||||
"""Websocket tests"""
|
||||
|
||||
from dataclasses import asdict
|
||||
from unittest.mock import patch
|
||||
|
||||
from channels.routing import URLRouter
|
||||
from channels.testing import WebsocketCommunicator
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
from authentik import __version__
|
||||
@ -16,12 +14,6 @@ from authentik.providers.proxy.models import ProxyProvider
|
||||
from authentik.root import websocket
|
||||
|
||||
|
||||
def patched__get_ct_cached(app_label, codename):
|
||||
"""Caches `ContentType` instances like its `QuerySet` does."""
|
||||
return ContentType.objects.get(app_label=app_label, permission__codename=codename)
|
||||
|
||||
|
||||
@patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached)
|
||||
class TestOutpostWS(TransactionTestCase):
|
||||
"""Websocket tests"""
|
||||
|
||||
|
@ -39,3 +39,4 @@ class AuthentikPoliciesConfig(ManagedAppConfig):
|
||||
label = "authentik_policies"
|
||||
verbose_name = "authentik Policies"
|
||||
default = True
|
||||
mountpoint = "policy/"
|
||||
|
89
authentik/policies/templates/policies/buffer.html
Normal file
89
authentik/policies/templates/policies/buffer.html
Normal file
@ -0,0 +1,89 @@
|
||||
{% extends 'login/base_full.html' %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block head %}
|
||||
{{ block.super }}
|
||||
<script>
|
||||
let redirecting = false;
|
||||
const checkAuth = async () => {
|
||||
if (redirecting) return true;
|
||||
const url = "{{ check_auth_url }}";
|
||||
console.debug("authentik/policies/buffer: Checking authentication...");
|
||||
try {
|
||||
const result = await fetch(url, {
|
||||
method: "HEAD",
|
||||
});
|
||||
if (result.status >= 400) {
|
||||
return false
|
||||
}
|
||||
console.debug("authentik/policies/buffer: Continuing");
|
||||
redirecting = true;
|
||||
if ("{{ auth_req_method }}" === "post") {
|
||||
document.querySelector("form").submit();
|
||||
} else {
|
||||
window.location.assign("{{ continue_url|escapejs }}");
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let timeout = 100;
|
||||
let offset = 20;
|
||||
let attempt = 0;
|
||||
const main = async () => {
|
||||
attempt += 1;
|
||||
await checkAuth();
|
||||
console.debug(`authentik/policies/buffer: Waiting ${timeout}ms...`);
|
||||
setTimeout(main, timeout);
|
||||
timeout += (offset * attempt);
|
||||
if (timeout >= 2000) {
|
||||
timeout = 2000;
|
||||
}
|
||||
}
|
||||
document.addEventListener("visibilitychange", async () => {
|
||||
if (document.hidden) return;
|
||||
console.debug("authentik/policies/buffer: Checking authentication on tab activate...");
|
||||
await checkAuth();
|
||||
});
|
||||
main();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% trans 'Waiting for authentication...' %} - {{ brand.branding_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
{% trans 'Waiting for authentication...' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card %}
|
||||
<form class="pf-c-form" method="{{ auth_req_method }}" action="{{ continue_url }}">
|
||||
{% if auth_req_method == "post" %}
|
||||
{% for key, value in auth_req_body.items %}
|
||||
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<div class="pf-c-empty-state">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<div class="pf-c-empty-state__icon">
|
||||
<span class="pf-c-spinner pf-m-xl" role="progressbar">
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</div>
|
||||
<h1 class="pf-c-title pf-m-lg">
|
||||
{% trans "You're already authenticating in another tab. This page will refresh once authentication is completed." %}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<a href="{{ auth_req_url }}" class="pf-c-button pf-m-primary pf-m-block">
|
||||
{% trans "Authenticate in this tab" %}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
121
authentik/policies/tests/test_views.py
Normal file
121
authentik/policies/tests/test_views.py
Normal file
@ -0,0 +1,121 @@
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.http import HttpResponse
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import Application, Provider
|
||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.flows.planner import FlowPlan
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import dummy_get_response
|
||||
from authentik.policies.views import (
|
||||
QS_BUFFER_ID,
|
||||
SESSION_KEY_BUFFER,
|
||||
BufferedPolicyAccessView,
|
||||
BufferView,
|
||||
PolicyAccessView,
|
||||
)
|
||||
|
||||
|
||||
class TestPolicyViews(TestCase):
|
||||
"""Test PolicyAccessView"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.factory = RequestFactory()
|
||||
self.user = create_test_user()
|
||||
|
||||
def test_pav(self):
|
||||
"""Test simple policy access view"""
|
||||
provider = Provider.objects.create(
|
||||
name=generate_id(),
|
||||
)
|
||||
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
||||
|
||||
class TestView(PolicyAccessView):
|
||||
def resolve_provider_application(self):
|
||||
self.provider = provider
|
||||
self.application = app
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return HttpResponse("foo")
|
||||
|
||||
req = self.factory.get("/")
|
||||
req.user = self.user
|
||||
res = TestView.as_view()(req)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.content, b"foo")
|
||||
|
||||
def test_pav_buffer(self):
|
||||
"""Test simple policy access view"""
|
||||
provider = Provider.objects.create(
|
||||
name=generate_id(),
|
||||
)
|
||||
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
|
||||
class TestView(BufferedPolicyAccessView):
|
||||
def resolve_provider_application(self):
|
||||
self.provider = provider
|
||||
self.application = app
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return HttpResponse("foo")
|
||||
|
||||
req = self.factory.get("/")
|
||||
req.user = AnonymousUser()
|
||||
middleware = SessionMiddleware(dummy_get_response)
|
||||
middleware.process_request(req)
|
||||
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
|
||||
req.session.save()
|
||||
res = TestView.as_view()(req)
|
||||
self.assertEqual(res.status_code, 302)
|
||||
self.assertTrue(res.url.startswith(reverse("authentik_policies:buffer")))
|
||||
|
||||
def test_pav_buffer_skip(self):
|
||||
"""Test simple policy access view (skip buffer)"""
|
||||
provider = Provider.objects.create(
|
||||
name=generate_id(),
|
||||
)
|
||||
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
|
||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
|
||||
class TestView(BufferedPolicyAccessView):
|
||||
def resolve_provider_application(self):
|
||||
self.provider = provider
|
||||
self.application = app
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return HttpResponse("foo")
|
||||
|
||||
req = self.factory.get("/?skip_buffer=true")
|
||||
req.user = AnonymousUser()
|
||||
middleware = SessionMiddleware(dummy_get_response)
|
||||
middleware.process_request(req)
|
||||
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
|
||||
req.session.save()
|
||||
res = TestView.as_view()(req)
|
||||
self.assertEqual(res.status_code, 302)
|
||||
self.assertTrue(res.url.startswith(reverse("authentik_flows:default-authentication")))
|
||||
|
||||
def test_buffer(self):
|
||||
"""Test buffer view"""
|
||||
uid = generate_id()
|
||||
req = self.factory.get(f"/?{QS_BUFFER_ID}={uid}")
|
||||
req.user = AnonymousUser()
|
||||
middleware = SessionMiddleware(dummy_get_response)
|
||||
middleware.process_request(req)
|
||||
ts = generate_id()
|
||||
req.session[SESSION_KEY_BUFFER % uid] = {
|
||||
"method": "get",
|
||||
"body": {},
|
||||
"url": f"/{ts}",
|
||||
}
|
||||
req.session.save()
|
||||
|
||||
res = BufferView.as_view()(req)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertIn(ts, res.render().content.decode())
|
@ -1,7 +1,14 @@
|
||||
"""API URLs"""
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from authentik.policies.api.bindings import PolicyBindingViewSet
|
||||
from authentik.policies.api.policies import PolicyViewSet
|
||||
from authentik.policies.views import BufferView
|
||||
|
||||
urlpatterns = [
|
||||
path("buffer", BufferView.as_view(), name="buffer"),
|
||||
]
|
||||
|
||||
api_urlpatterns = [
|
||||
("policies/all", PolicyViewSet),
|
||||
|
@ -1,23 +1,37 @@
|
||||
"""authentik access helper classes"""
|
||||
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import AccessMixin
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http import HttpRequest, HttpResponse, QueryDict
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.base import View
|
||||
from django.views.generic.base import TemplateView, View
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import Application, Provider, User
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_POST
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.flows.planner import FlowPlan
|
||||
from authentik.flows.views.executor import (
|
||||
SESSION_KEY_APPLICATION_PRE,
|
||||
SESSION_KEY_AUTH_STARTED,
|
||||
SESSION_KEY_PLAN,
|
||||
SESSION_KEY_POST,
|
||||
)
|
||||
from authentik.lib.sentry import SentryIgnoredException
|
||||
from authentik.policies.denied import AccessDeniedResponse
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
|
||||
LOGGER = get_logger()
|
||||
QS_BUFFER_ID = "af_bf_id"
|
||||
QS_SKIP_BUFFER = "skip_buffer"
|
||||
SESSION_KEY_BUFFER = "authentik/policies/pav_buffer/%s"
|
||||
|
||||
|
||||
class RequestValidationError(SentryIgnoredException):
|
||||
@ -125,3 +139,65 @@ class PolicyAccessView(AccessMixin, View):
|
||||
for message in result.messages:
|
||||
messages.error(self.request, _(message))
|
||||
return result
|
||||
|
||||
|
||||
def url_with_qs(url: str, **kwargs):
|
||||
"""Update/set querystring of `url` with the parameters in `kwargs`. Original query string
|
||||
parameters are retained"""
|
||||
if "?" not in url:
|
||||
return url + f"?{urlencode(kwargs)}"
|
||||
url, _, qs = url.partition("?")
|
||||
qs = QueryDict(qs, mutable=True)
|
||||
qs.update(kwargs)
|
||||
return url + f"?{urlencode(qs.items())}"
|
||||
|
||||
|
||||
class BufferView(TemplateView):
|
||||
"""Buffer view"""
|
||||
|
||||
template_name = "policies/buffer.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
buf_id = self.request.GET.get(QS_BUFFER_ID)
|
||||
buffer: dict = self.request.session.get(SESSION_KEY_BUFFER % buf_id)
|
||||
kwargs["auth_req_method"] = buffer["method"]
|
||||
kwargs["auth_req_body"] = buffer["body"]
|
||||
kwargs["auth_req_url"] = url_with_qs(buffer["url"], **{QS_SKIP_BUFFER: True})
|
||||
kwargs["check_auth_url"] = reverse("authentik_api:user-me")
|
||||
kwargs["continue_url"] = url_with_qs(buffer["url"], **{QS_BUFFER_ID: buf_id})
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class BufferedPolicyAccessView(PolicyAccessView):
|
||||
"""PolicyAccessView which buffers access requests in case the user is not logged in"""
|
||||
|
||||
def handle_no_permission(self):
|
||||
plan: FlowPlan | None = self.request.session.get(SESSION_KEY_PLAN)
|
||||
authenticating = self.request.session.get(SESSION_KEY_AUTH_STARTED)
|
||||
if plan:
|
||||
flow = Flow.objects.filter(pk=plan.flow_pk).first()
|
||||
if not flow or flow.designation != FlowDesignation.AUTHENTICATION:
|
||||
LOGGER.debug("Not buffering request, no flow or flow not for authentication")
|
||||
return super().handle_no_permission()
|
||||
if not plan and authenticating is None:
|
||||
LOGGER.debug("Not buffering request, no flow plan active")
|
||||
return super().handle_no_permission()
|
||||
if self.request.GET.get(QS_SKIP_BUFFER):
|
||||
LOGGER.debug("Not buffering request, explicit skip")
|
||||
return super().handle_no_permission()
|
||||
buffer_id = str(uuid4())
|
||||
LOGGER.debug("Buffering access request", bf_id=buffer_id)
|
||||
self.request.session[SESSION_KEY_BUFFER % buffer_id] = {
|
||||
"body": self.request.POST,
|
||||
"url": self.request.build_absolute_uri(self.request.get_full_path()),
|
||||
"method": self.request.method.lower(),
|
||||
}
|
||||
return redirect(
|
||||
url_with_qs(reverse("authentik_policies:buffer"), **{QS_BUFFER_ID: buffer_id})
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
if QS_BUFFER_ID in self.request.GET:
|
||||
self.request.session.pop(SESSION_KEY_BUFFER % self.request.GET[QS_BUFFER_ID], None)
|
||||
return response
|
||||
|
@ -30,7 +30,7 @@ from authentik.flows.stage import StageView
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.lib.views import bad_request_message
|
||||
from authentik.policies.types import PolicyRequest
|
||||
from authentik.policies.views import PolicyAccessView, RequestValidationError
|
||||
from authentik.policies.views import BufferedPolicyAccessView, RequestValidationError
|
||||
from authentik.providers.oauth2.constants import (
|
||||
PKCE_METHOD_PLAIN,
|
||||
PKCE_METHOD_S256,
|
||||
@ -326,7 +326,7 @@ class OAuthAuthorizationParams:
|
||||
return code
|
||||
|
||||
|
||||
class AuthorizationFlowInitView(PolicyAccessView):
|
||||
class AuthorizationFlowInitView(BufferedPolicyAccessView):
|
||||
"""OAuth2 Flow initializer, checks access to application and starts flow"""
|
||||
|
||||
params: OAuthAuthorizationParams
|
||||
|
@ -18,11 +18,11 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
|
||||
from authentik.flows.stage import RedirectStage
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
from authentik.policies.views import PolicyAccessView
|
||||
from authentik.policies.views import BufferedPolicyAccessView
|
||||
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
|
||||
|
||||
|
||||
class RACStartView(PolicyAccessView):
|
||||
class RACStartView(BufferedPolicyAccessView):
|
||||
"""Start a RAC connection by checking access and creating a connection token"""
|
||||
|
||||
endpoint: Endpoint
|
||||
|
@ -35,8 +35,8 @@ REQUEST_KEY_SAML_SIG_ALG = "SigAlg"
|
||||
REQUEST_KEY_SAML_RESPONSE = "SAMLResponse"
|
||||
REQUEST_KEY_RELAY_STATE = "RelayState"
|
||||
|
||||
SESSION_KEY_AUTH_N_REQUEST = "authentik/providers/saml/authn_request"
|
||||
SESSION_KEY_LOGOUT_REQUEST = "authentik/providers/saml/logout_request"
|
||||
PLAN_CONTEXT_SAML_AUTH_N_REQUEST = "authentik/providers/saml/authn_request"
|
||||
PLAN_CONTEXT_SAML_LOGOUT_REQUEST = "authentik/providers/saml/logout_request"
|
||||
|
||||
|
||||
# This View doesn't have a URL on purpose, as its called by the FlowExecutor
|
||||
@ -50,10 +50,11 @@ class SAMLFlowFinalView(ChallengeStageView):
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
application: Application = self.executor.plan.context[PLAN_CONTEXT_APPLICATION]
|
||||
provider: SAMLProvider = get_object_or_404(SAMLProvider, pk=application.provider_id)
|
||||
if SESSION_KEY_AUTH_N_REQUEST not in self.request.session:
|
||||
if PLAN_CONTEXT_SAML_AUTH_N_REQUEST not in self.executor.plan.context:
|
||||
self.logger.warning("No AuthNRequest in context")
|
||||
return self.executor.stage_invalid()
|
||||
|
||||
auth_n_request: AuthNRequest = self.request.session.pop(SESSION_KEY_AUTH_N_REQUEST)
|
||||
auth_n_request: AuthNRequest = self.executor.plan.context[PLAN_CONTEXT_SAML_AUTH_N_REQUEST]
|
||||
try:
|
||||
response = AssertionProcessor(provider, request, auth_n_request).build_response()
|
||||
except SAMLException as exc:
|
||||
@ -106,6 +107,3 @@ class SAMLFlowFinalView(ChallengeStageView):
|
||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||
# We'll never get here since the challenge redirects to the SP
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
def cleanup(self):
|
||||
self.request.session.pop(SESSION_KEY_AUTH_N_REQUEST, None)
|
||||
|
@ -19,9 +19,9 @@ from authentik.providers.saml.exceptions import CannotHandleAssertion
|
||||
from authentik.providers.saml.models import SAMLProvider
|
||||
from authentik.providers.saml.processors.logout_request_parser import LogoutRequestParser
|
||||
from authentik.providers.saml.views.flows import (
|
||||
PLAN_CONTEXT_SAML_LOGOUT_REQUEST,
|
||||
REQUEST_KEY_RELAY_STATE,
|
||||
REQUEST_KEY_SAML_REQUEST,
|
||||
SESSION_KEY_LOGOUT_REQUEST,
|
||||
)
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -33,6 +33,10 @@ class SAMLSLOView(PolicyAccessView):
|
||||
|
||||
flow: Flow
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.plan_context = {}
|
||||
|
||||
def resolve_provider_application(self):
|
||||
self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"])
|
||||
self.provider: SAMLProvider = get_object_or_404(
|
||||
@ -59,6 +63,7 @@ class SAMLSLOView(PolicyAccessView):
|
||||
request,
|
||||
{
|
||||
PLAN_CONTEXT_APPLICATION: self.application,
|
||||
**self.plan_context,
|
||||
},
|
||||
)
|
||||
plan.append_stage(in_memory_stage(SessionEndStage))
|
||||
@ -83,7 +88,7 @@ class SAMLSLOBindingRedirectView(SAMLSLOView):
|
||||
self.request.GET[REQUEST_KEY_SAML_REQUEST],
|
||||
relay_state=self.request.GET.get(REQUEST_KEY_RELAY_STATE, None),
|
||||
)
|
||||
self.request.session[SESSION_KEY_LOGOUT_REQUEST] = logout_request
|
||||
self.plan_context[PLAN_CONTEXT_SAML_LOGOUT_REQUEST] = logout_request
|
||||
except CannotHandleAssertion as exc:
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
@ -111,7 +116,7 @@ class SAMLSLOBindingPOSTView(SAMLSLOView):
|
||||
payload[REQUEST_KEY_SAML_REQUEST],
|
||||
relay_state=payload.get(REQUEST_KEY_RELAY_STATE, None),
|
||||
)
|
||||
self.request.session[SESSION_KEY_LOGOUT_REQUEST] = logout_request
|
||||
self.plan_context[PLAN_CONTEXT_SAML_LOGOUT_REQUEST] = logout_request
|
||||
except CannotHandleAssertion as exc:
|
||||
LOGGER.info(str(exc))
|
||||
return bad_request_message(self.request, str(exc))
|
||||
|
@ -15,16 +15,16 @@ from authentik.flows.models import in_memory_stage
|
||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
|
||||
from authentik.flows.views.executor import SESSION_KEY_POST
|
||||
from authentik.lib.views import bad_request_message
|
||||
from authentik.policies.views import PolicyAccessView
|
||||
from authentik.policies.views import BufferedPolicyAccessView
|
||||
from authentik.providers.saml.exceptions import CannotHandleAssertion
|
||||
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
|
||||
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
|
||||
from authentik.providers.saml.views.flows import (
|
||||
PLAN_CONTEXT_SAML_AUTH_N_REQUEST,
|
||||
REQUEST_KEY_RELAY_STATE,
|
||||
REQUEST_KEY_SAML_REQUEST,
|
||||
REQUEST_KEY_SAML_SIG_ALG,
|
||||
REQUEST_KEY_SAML_SIGNATURE,
|
||||
SESSION_KEY_AUTH_N_REQUEST,
|
||||
SAMLFlowFinalView,
|
||||
)
|
||||
from authentik.stages.consent.stage import (
|
||||
@ -35,10 +35,14 @@ from authentik.stages.consent.stage import (
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class SAMLSSOView(PolicyAccessView):
|
||||
class SAMLSSOView(BufferedPolicyAccessView):
|
||||
"""SAML SSO Base View, which plans a flow and injects our final stage.
|
||||
Calls get/post handler."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.plan_context = {}
|
||||
|
||||
def resolve_provider_application(self):
|
||||
self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"])
|
||||
self.provider: SAMLProvider = get_object_or_404(
|
||||
@ -68,6 +72,7 @@ class SAMLSSOView(PolicyAccessView):
|
||||
PLAN_CONTEXT_CONSENT_HEADER: _("You're about to sign into %(application)s.")
|
||||
% {"application": self.application.name},
|
||||
PLAN_CONTEXT_CONSENT_PERMISSIONS: [],
|
||||
**self.plan_context,
|
||||
},
|
||||
)
|
||||
except FlowNonApplicableException:
|
||||
@ -83,7 +88,7 @@ class SAMLSSOView(PolicyAccessView):
|
||||
|
||||
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
|
||||
"""GET and POST use the same handler, but we can't
|
||||
override .dispatch easily because PolicyAccessView's dispatch"""
|
||||
override .dispatch easily because BufferedPolicyAccessView's dispatch"""
|
||||
return self.get(request, application_slug)
|
||||
|
||||
|
||||
@ -103,7 +108,7 @@ class SAMLSSOBindingRedirectView(SAMLSSOView):
|
||||
self.request.GET.get(REQUEST_KEY_SAML_SIGNATURE),
|
||||
self.request.GET.get(REQUEST_KEY_SAML_SIG_ALG),
|
||||
)
|
||||
self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request
|
||||
self.plan_context[PLAN_CONTEXT_SAML_AUTH_N_REQUEST] = auth_n_request
|
||||
except CannotHandleAssertion as exc:
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
@ -137,7 +142,7 @@ class SAMLSSOBindingPOSTView(SAMLSSOView):
|
||||
payload[REQUEST_KEY_SAML_REQUEST],
|
||||
payload.get(REQUEST_KEY_RELAY_STATE),
|
||||
)
|
||||
self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request
|
||||
self.plan_context[PLAN_CONTEXT_SAML_AUTH_N_REQUEST] = auth_n_request
|
||||
except CannotHandleAssertion as exc:
|
||||
LOGGER.info(str(exc))
|
||||
return bad_request_message(self.request, str(exc))
|
||||
@ -151,4 +156,4 @@ class SAMLSSOBindingInitView(SAMLSSOView):
|
||||
"""Create SAML Response from scratch"""
|
||||
LOGGER.debug("No SAML Request, using IdP-initiated flow.")
|
||||
auth_n_request = AuthNRequestParser(self.provider).idp_initiated()
|
||||
self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request
|
||||
self.plan_context[PLAN_CONTEXT_SAML_AUTH_N_REQUEST] = auth_n_request
|
||||
|
@ -3,21 +3,38 @@
|
||||
import os
|
||||
from argparse import ArgumentParser
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test.runner import DiscoverRunner
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.lib.sentry import sentry_init
|
||||
from authentik.root.signals import post_startup, pre_startup, startup
|
||||
from tests.e2e.utils import get_docker_tag
|
||||
|
||||
# globally set maxDiff to none to show full assert error
|
||||
TestCase.maxDiff = None
|
||||
|
||||
|
||||
def get_docker_tag() -> str:
|
||||
"""Get docker-tag based off of CI variables"""
|
||||
env_pr_branch = "GITHUB_HEAD_REF"
|
||||
default_branch = "GITHUB_REF"
|
||||
branch_name = os.environ.get(default_branch, "main")
|
||||
if os.environ.get(env_pr_branch, "") != "":
|
||||
branch_name = os.environ[env_pr_branch]
|
||||
branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
|
||||
return f"gh-{branch_name}"
|
||||
|
||||
|
||||
def patched__get_ct_cached(app_label, codename):
|
||||
"""Caches `ContentType` instances like its `QuerySet` does."""
|
||||
return ContentType.objects.get(app_label=app_label, permission__codename=codename)
|
||||
|
||||
|
||||
class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||
"""Runs pytest to discover and run tests."""
|
||||
|
||||
@ -149,8 +166,9 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||
return 1
|
||||
|
||||
self.logger.info("Running tests", test_files=self.args)
|
||||
try:
|
||||
return pytest.main(self.args)
|
||||
except Exception as e:
|
||||
self.logger.error("Error running tests", error=str(e), test_files=self.args)
|
||||
return 1
|
||||
with patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached):
|
||||
try:
|
||||
return pytest.main(self.args)
|
||||
except Exception as e:
|
||||
self.logger.error("Error running tests", error=str(e), test_files=self.args)
|
||||
return 1
|
||||
|
12574
blueprints/schema.json
12574
blueprints/schema.json
File diff suppressed because it is too large
Load Diff
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1018.0",
|
||||
"aws-cdk": "^2.1018.1",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1018.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1018.0.tgz",
|
||||
"integrity": "sha512-sppVsNtFJTW4wawS/PBudHCSNHb8xwaZ2WX1mpsfwaPNyTWm0eSUVJsRbRiRBu9O/Us8pgrd4woUjfM1lgD7Kw==",
|
||||
"version": "2.1018.1",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1018.1.tgz",
|
||||
"integrity": "sha512-kFPRox5kSm+ktJ451o0ng9rD+60p5Kt1CZIWw8kXnvqbsxN2xv6qbmyWSXw7sGVXVwqrRKVj+71/JeDr+LMAZw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1018.0",
|
||||
"aws-cdk": "^2.1018.1",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ dependencies = [
|
||||
"gunicorn==23.0.0",
|
||||
"jsonpatch==1.33",
|
||||
"jwcrypto==1.5.6",
|
||||
"kubernetes==32.0.1",
|
||||
"kubernetes==33.1.0",
|
||||
"ldap3==2.9.1",
|
||||
"lxml==5.4.0",
|
||||
"msgraph-sdk==1.33.0",
|
||||
@ -56,13 +56,13 @@ dependencies = [
|
||||
"pyyaml==6.0.2",
|
||||
"requests-oauthlib==2.0.0",
|
||||
"scim2-filter-parser==0.7.0",
|
||||
"sentry-sdk==2.29.1",
|
||||
"sentry-sdk==2.30.0",
|
||||
"service-identity==24.2.0",
|
||||
"setproctitle==1.3.6",
|
||||
"structlog==25.4.0",
|
||||
"swagger-spec-validator==3.0.4",
|
||||
"tenant-schemas-celery==3.0.0",
|
||||
"twilio==9.6.2",
|
||||
"twilio==9.6.3",
|
||||
"ua-parser==1.0.1",
|
||||
"unidecode==1.4.0",
|
||||
"urllib3<3",
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from dataclasses import asdict
|
||||
from time import sleep
|
||||
from unittest.mock import patch
|
||||
|
||||
from guardian.shortcuts import assign_perm
|
||||
from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
|
||||
@ -16,12 +15,10 @@ from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType
|
||||
from authentik.outposts.tests.test_ws import patched__get_ct_cached
|
||||
from authentik.providers.ldap.models import APIAccessMode, LDAPProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached)
|
||||
class TestProviderLDAP(SeleniumTestCase):
|
||||
"""LDAP and Outpost e2e tests"""
|
||||
|
||||
|
@ -410,3 +410,77 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
self.driver.find_element(By.CSS_SELECTOR, "header > h1").text,
|
||||
"Permission denied",
|
||||
)
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_consent_implied_parallel(self):
|
||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris=[
|
||||
RedirectURI(
|
||||
RedirectURIMatchingMode.STRICT, "http://localhost:3000/login/generic_oauth"
|
||||
)
|
||||
],
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
ScopeMapping.objects.filter(
|
||||
scope_name__in=[
|
||||
SCOPE_OPENID,
|
||||
SCOPE_OPENID_EMAIL,
|
||||
SCOPE_OPENID_PROFILE,
|
||||
SCOPE_OFFLINE_ACCESS,
|
||||
]
|
||||
)
|
||||
)
|
||||
Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=self.app_slug,
|
||||
provider=provider,
|
||||
)
|
||||
|
||||
self.driver.get(self.live_server_url)
|
||||
login_window = self.driver.current_window_handle
|
||||
|
||||
self.driver.switch_to.new_window("tab")
|
||||
grafana_window = self.driver.current_window_handle
|
||||
self.driver.get("http://localhost:3000")
|
||||
self.driver.find_element(By.CLASS_NAME, "btn-service--oauth").click()
|
||||
|
||||
self.driver.switch_to.window(login_window)
|
||||
self.login()
|
||||
|
||||
self.driver.switch_to.window(grafana_window)
|
||||
self.wait_for_url("http://localhost:3000/?orgId=1")
|
||||
self.driver.get("http://localhost:3000/profile")
|
||||
self.assertEqual(
|
||||
self.driver.find_element(By.CLASS_NAME, "page-header__title").text,
|
||||
self.user.name,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute("value"),
|
||||
self.user.name,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.driver.find_element(By.CSS_SELECTOR, "input[name=email]").get_attribute("value"),
|
||||
self.user.email,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.driver.find_element(By.CSS_SELECTOR, "input[name=login]").get_attribute("value"),
|
||||
self.user.email,
|
||||
)
|
||||
|
@ -6,7 +6,6 @@ from json import loads
|
||||
from sys import platform
|
||||
from time import sleep
|
||||
from unittest.case import skip, skipUnless
|
||||
from unittest.mock import patch
|
||||
|
||||
from channels.testing import ChannelsLiveServerTestCase
|
||||
from jwt import decode
|
||||
@ -18,12 +17,10 @@ from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType
|
||||
from authentik.outposts.tasks import outpost_connection_discovery
|
||||
from authentik.outposts.tests.test_ws import patched__get_ct_cached
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached)
|
||||
class TestProviderProxy(SeleniumTestCase):
|
||||
"""Proxy and Outpost e2e tests"""
|
||||
|
||||
|
@ -4,7 +4,6 @@ from json import loads
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
from unittest import skip
|
||||
from unittest.mock import patch
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
@ -13,12 +12,10 @@ from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.outposts.models import Outpost, OutpostType
|
||||
from authentik.outposts.tests.test_ws import patched__get_ct_cached
|
||||
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached)
|
||||
class TestProviderProxyForward(SeleniumTestCase):
|
||||
"""Proxy and Outpost e2e tests"""
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from dataclasses import asdict
|
||||
from time import sleep
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyrad.client import Client
|
||||
from pyrad.dictionary import Dictionary
|
||||
@ -13,12 +12,10 @@ from authentik.core.models import Application, User
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType
|
||||
from authentik.outposts.tests.test_ws import patched__get_ct_cached
|
||||
from authentik.providers.radius.models import RadiusProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@patch("guardian.shortcuts._get_ct_cached", patched__get_ct_cached)
|
||||
class TestProviderRadius(SeleniumTestCase):
|
||||
"""Radius Outpost e2e tests"""
|
||||
|
||||
|
@ -20,7 +20,7 @@ from tests.e2e.utils import SeleniumTestCase, retry
|
||||
class TestProviderSAML(SeleniumTestCase):
|
||||
"""test SAML Provider flow"""
|
||||
|
||||
def setup_client(self, provider: SAMLProvider, force_post: bool = False):
|
||||
def setup_client(self, provider: SAMLProvider, force_post: bool = False, **kwargs):
|
||||
"""Setup client saml-sp container which we test SAML against"""
|
||||
metadata_url = (
|
||||
self.url(
|
||||
@ -40,6 +40,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"SP_ENTITY_ID": provider.issuer,
|
||||
"SP_SSO_BINDING": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
||||
"SP_METADATA_URL": metadata_url,
|
||||
**kwargs,
|
||||
},
|
||||
)
|
||||
|
||||
@ -111,6 +112,74 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
[self.user.email],
|
||||
)
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_sp_initiated_implicit_post(self):
|
||||
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
name="saml-test",
|
||||
acs_url="http://localhost:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
provider.save()
|
||||
Application.objects.create(
|
||||
name="SAML",
|
||||
slug="authentik-saml",
|
||||
provider=provider,
|
||||
)
|
||||
self.setup_client(provider, True)
|
||||
self.driver.get("http://localhost:9009")
|
||||
self.login()
|
||||
self.wait_for_url("http://localhost:9009/")
|
||||
|
||||
body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
|
||||
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],
|
||||
[self.user.name],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"][
|
||||
"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
|
||||
],
|
||||
[self.user.username],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"],
|
||||
[self.user.username],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"],
|
||||
[str(self.user.pk)],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"],
|
||||
[self.user.email],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"],
|
||||
[self.user.email],
|
||||
)
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
@ -450,3 +519,81 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
lambda driver: driver.current_url.startswith(should_url),
|
||||
f"URL {self.driver.current_url} doesn't match expected URL {should_url}",
|
||||
)
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_sp_initiated_implicit_post_buffer(self):
|
||||
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
||||
# Bootstrap all needed objects
|
||||
authorization_flow = Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
)
|
||||
provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
name="saml-test",
|
||||
acs_url=f"http://{self.host}:9009/saml/acs",
|
||||
audience="authentik-e2e",
|
||||
issuer="authentik-e2e",
|
||||
sp_binding=SAMLBindings.POST,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
provider.property_mappings.set(SAMLPropertyMapping.objects.all())
|
||||
provider.save()
|
||||
Application.objects.create(
|
||||
name="SAML",
|
||||
slug="authentik-saml",
|
||||
provider=provider,
|
||||
)
|
||||
self.setup_client(provider, True, SP_ROOT_URL=f"http://{self.host}:9009")
|
||||
|
||||
self.driver.get(self.live_server_url)
|
||||
login_window = self.driver.current_window_handle
|
||||
self.driver.switch_to.new_window("tab")
|
||||
client_window = self.driver.current_window_handle
|
||||
# We need to access the SP on the same host as the IdP for SameSite cookies
|
||||
self.driver.get(f"http://{self.host}:9009")
|
||||
|
||||
self.driver.switch_to.window(login_window)
|
||||
self.login()
|
||||
self.driver.switch_to.window(client_window)
|
||||
|
||||
self.wait_for_url(f"http://{self.host}:9009/")
|
||||
|
||||
body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text)
|
||||
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],
|
||||
[self.user.name],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"][
|
||||
"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
|
||||
],
|
||||
[self.user.username],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"],
|
||||
[self.user.username],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"],
|
||||
[str(self.user.pk)],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"],
|
||||
[self.user.email],
|
||||
)
|
||||
self.assertEqual(
|
||||
body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"],
|
||||
[self.user.email],
|
||||
)
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""authentik e2e testing utilities"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from collections.abc import Callable
|
||||
from functools import lru_cache, wraps
|
||||
@ -37,22 +36,12 @@ from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.root.test_runner import get_docker_tag
|
||||
|
||||
IS_CI = "CI" in environ
|
||||
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
|
||||
|
||||
|
||||
def get_docker_tag() -> str:
|
||||
"""Get docker-tag based off of CI variables"""
|
||||
env_pr_branch = "GITHUB_HEAD_REF"
|
||||
default_branch = "GITHUB_REF"
|
||||
branch_name = os.environ.get(default_branch, "main")
|
||||
if os.environ.get(env_pr_branch, "") != "":
|
||||
branch_name = os.environ[env_pr_branch]
|
||||
branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
|
||||
return f"gh-{branch_name}"
|
||||
|
||||
|
||||
def get_local_ip() -> str:
|
||||
"""Get the local machine's IP"""
|
||||
hostname = socket.gethostname()
|
||||
|
24
uv.lock
generated
24
uv.lock
generated
@ -301,7 +301,7 @@ requires-dist = [
|
||||
{ name = "gunicorn", specifier = "==23.0.0" },
|
||||
{ name = "jsonpatch", specifier = "==1.33" },
|
||||
{ name = "jwcrypto", specifier = "==1.5.6" },
|
||||
{ name = "kubernetes", specifier = "==32.0.1" },
|
||||
{ name = "kubernetes", specifier = "==33.1.0" },
|
||||
{ name = "ldap3", specifier = "==2.9.1" },
|
||||
{ name = "lxml", specifier = "==5.4.0" },
|
||||
{ name = "msgraph-sdk", specifier = "==1.33.0" },
|
||||
@ -317,13 +317,13 @@ requires-dist = [
|
||||
{ name = "pyyaml", specifier = "==6.0.2" },
|
||||
{ name = "requests-oauthlib", specifier = "==2.0.0" },
|
||||
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
|
||||
{ name = "sentry-sdk", specifier = "==2.29.1" },
|
||||
{ name = "sentry-sdk", specifier = "==2.30.0" },
|
||||
{ name = "service-identity", specifier = "==24.2.0" },
|
||||
{ name = "setproctitle", specifier = "==1.3.6" },
|
||||
{ name = "structlog", specifier = "==25.4.0" },
|
||||
{ name = "swagger-spec-validator", specifier = "==3.0.4" },
|
||||
{ name = "tenant-schemas-celery", specifier = "==3.0.0" },
|
||||
{ name = "twilio", specifier = "==9.6.2" },
|
||||
{ name = "twilio", specifier = "==9.6.3" },
|
||||
{ name = "ua-parser", specifier = "==1.0.1" },
|
||||
{ name = "unidecode", specifier = "==1.4.0" },
|
||||
{ name = "urllib3", specifier = "<3" },
|
||||
@ -1772,7 +1772,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "kubernetes"
|
||||
version = "32.0.1"
|
||||
version = "33.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@ -1787,9 +1787,9 @@ dependencies = [
|
||||
{ name = "urllib3" },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/e8/0598f0e8b4af37cd9b10d8b87386cf3173cb8045d834ab5f6ec347a758b3/kubernetes-32.0.1.tar.gz", hash = "sha256:42f43d49abd437ada79a79a16bd48a604d3471a117a8347e87db693f2ba0ba28", size = 946691, upload-time = "2025-02-18T21:06:34.148Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/52/19ebe8004c243fdfa78268a96727c71e08f00ff6fe69a301d0b7fcbce3c2/kubernetes-33.1.0.tar.gz", hash = "sha256:f64d829843a54c251061a8e7a14523b521f2dc5c896cf6d65ccf348648a88993", size = 1036779, upload-time = "2025-06-09T21:57:58.521Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/08/10/9f8af3e6f569685ce3af7faab51c8dd9d93b9c38eba339ca31c746119447/kubernetes-32.0.1-py2.py3-none-any.whl", hash = "sha256:35282ab8493b938b08ab5526c7ce66588232df00ef5e1dbe88a419107dc10998", size = 1988070, upload-time = "2025-02-18T21:06:31.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/43/d9bebfc3db7dea6ec80df5cb2aad8d274dd18ec2edd6c4f21f32c237cbbb/kubernetes-33.1.0-py2.py3-none-any.whl", hash = "sha256:544de42b24b64287f7e0aa9513c93cb503f7f40eea39b20f66810011a86eabc5", size = 1941335, upload-time = "2025-06-09T21:57:56.327Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2931,15 +2931,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.29.1"
|
||||
version = "2.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/22/67/d552a5f8e5a6a56b2feea6529e2d8ccd54349084c84176d5a1f7295044bc/sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d", size = 325518, upload-time = "2025-05-19T14:27:38.512Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/4c/af31e0201b48469786ddeb1bf6fd3dfa3a291cc613a0fe6a60163a7535f9/sentry_sdk-2.30.0.tar.gz", hash = "sha256:436369b02afef7430efb10300a344fb61a11fe6db41c2b11f41ee037d2dd7f45", size = 326767, upload-time = "2025-06-12T10:34:34.733Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/e5/da07b0bd832cefd52d16f2b9bbbe31624d57552602c06631686b93ccb1bd/sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19", size = 341553, upload-time = "2025-05-19T14:27:36.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/99/31ac6faaae33ea698086692638f58d14f121162a8db0039e68e94135e7f1/sentry_sdk-2.30.0-py2.py3-none-any.whl", hash = "sha256:59391db1550662f746ea09b483806a631c3ae38d6340804a1a4c0605044f6877", size = 343149, upload-time = "2025-06-12T10:34:32.896Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3151,7 +3151,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "9.6.2"
|
||||
version = "9.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@ -3159,9 +3159,9 @@ dependencies = [
|
||||
{ name = "pyjwt" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fa/c9/441a07f6552f2b504812501d56c41bd85b02afeef6c23ab8baf41ed6c70e/twilio-9.6.2.tar.gz", hash = "sha256:5da13bb497e39ece34cb9f2b3bc911f3288928612748f7688b3bda262c2767a1", size = 1041300, upload-time = "2025-05-29T12:25:04.59Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/af/1b401bc4cfd3eb41c7e2a98d0040d2bcfd2ad3217f3163401121179b3fb3/twilio-9.6.3.tar.gz", hash = "sha256:16a8c2ab9550343c25c8a195f31db9e230d9b341eca31ebdd301109910fd9730", size = 1041494, upload-time = "2025-06-12T10:40:55.63Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/91/382e83e5d205a7ae4325b66d40cd2fa6ce85526f2ed8fc553265e19abbe4/twilio-9.6.2-py2.py3-none-any.whl", hash = "sha256:8d4af6f42850734a921857df42940f7fed84e3e4a508d0d6bef5b9fb7dc08357", size = 1909253, upload-time = "2025-05-29T12:25:02.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/35/d61a3581eb223e5e1fc0add1c397d7bb60014b22790e8f89aa5eb4e41e04/twilio-9.6.3-py2.py3-none-any.whl", hash = "sha256:a9b2cf11b0718394f12c43585ca25b9094f12b82ff975f1561fcec7f0f6f49b2", size = 1909549, upload-time = "2025-06-12T10:40:53.67Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
92
web/package-lock.json
generated
92
web/package-lock.json
generated
@ -31,8 +31,8 @@
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.28.0",
|
||||
"@spotlightjs/spotlight": "^2.13.3",
|
||||
"@sentry/browser": "^9.28.1",
|
||||
"@spotlightjs/spotlight": "^3.0.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"change-case": "^5.4.4",
|
||||
@ -4478,75 +4478,75 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.28.0.tgz",
|
||||
"integrity": "sha512-SqntPnIXudP3FoKj4mQ1BVPC1RNzo4CGtAxJnLpbIUpdT/khJVM6Q59zrGl2MgZ7URZCI986L5jXihQeferf6g==",
|
||||
"version": "9.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.28.1.tgz",
|
||||
"integrity": "sha512-P/FEZkT7UqTw9P/2n/Y4Aa1OtGP6dnCvyqzPPkjiRdVa7Ep7S5ElBJloGv7077TLLBtAfCsEUVRlM1F6/jQoaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.28.0"
|
||||
"@sentry/core": "9.28.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.28.0.tgz",
|
||||
"integrity": "sha512-z2jShmVENsesmDnShEOv841Saw0zXe1tX6GHNgkK9f6NrUMbL970JvGKByBFTffhQH6uQ0WeNPnXJ5L/YKnfDg==",
|
||||
"version": "9.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.28.1.tgz",
|
||||
"integrity": "sha512-HOk/c26D3nlClO/xEefev8fIJzRA621PFQvNFPu/y0Z5HujEqSmIsrff0cXszPPYD95h4Mwk63E0ZYdspdeXcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.28.0"
|
||||
"@sentry/core": "9.28.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.28.0.tgz",
|
||||
"integrity": "sha512-BVGVBlmcpJdT55d/vywjfK1u6zMC5ycjJBxU1wUCNgCU3cSKRDBnvmYgk/+Ay23bFryT28Q4hM1p5qBBAOfxjQ==",
|
||||
"version": "9.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.28.1.tgz",
|
||||
"integrity": "sha512-Tv9pkfAX+1bmhxF42TL0c4uTiK2+rp5LMYEPdz6JBfpfvG/Z1unPGsuB7fQmHYKyfHBQJmi92DZV+smljm7w/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.28.0",
|
||||
"@sentry/core": "9.28.0"
|
||||
"@sentry-internal/browser-utils": "9.28.1",
|
||||
"@sentry/core": "9.28.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.28.0.tgz",
|
||||
"integrity": "sha512-Bv4mbtUrRV3p6PpFQPseLv3+Uaen+3AlfX02Z6QHY1sMa4lpt+U8OHfRGLprnzb6Rarw6fK2LNVL5rnV9LNMwA==",
|
||||
"version": "9.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.28.1.tgz",
|
||||
"integrity": "sha512-RtkogfcIpXLFCyV8CTnXmVTH2QauT/KwmUAXBbeOz3rRWsM19yjN1moHrsjxn7OdjTv+D4qWSCA8Ka1aKSpr7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "9.28.0",
|
||||
"@sentry/core": "9.28.0"
|
||||
"@sentry-internal/replay": "9.28.1",
|
||||
"@sentry/core": "9.28.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.28.0.tgz",
|
||||
"integrity": "sha512-ttqiv3D9sIB43nZnJTTln1nXw1p4C5BDSh+sHmGUOiqdCH6ND3HByDITYMYIOz1lACSISTT4V+MEpqx0V25Tlw==",
|
||||
"version": "9.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.28.1.tgz",
|
||||
"integrity": "sha512-XAS46iQSq8lXTnv9udQP025JTf3PwSVRE9ePJVQhx25QBWxedqGhEOv5qqX9b1Ijf8KiZYXXhBWMQxBBXVzUaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.28.0",
|
||||
"@sentry-internal/feedback": "9.28.0",
|
||||
"@sentry-internal/replay": "9.28.0",
|
||||
"@sentry-internal/replay-canvas": "9.28.0",
|
||||
"@sentry/core": "9.28.0"
|
||||
"@sentry-internal/browser-utils": "9.28.1",
|
||||
"@sentry-internal/feedback": "9.28.1",
|
||||
"@sentry-internal/replay": "9.28.1",
|
||||
"@sentry-internal/replay-canvas": "9.28.1",
|
||||
"@sentry/core": "9.28.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "9.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.28.0.tgz",
|
||||
"integrity": "sha512-vzD9xhg9S864jxfCpq77feCE4y7iP2cZYsNMoTupl1vTUlmXlhp7XgF832fEMjEZq4vrPhaqCNsde7Sc3PAbaQ==",
|
||||
"version": "9.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.28.1.tgz",
|
||||
"integrity": "sha512-6q59r/71MeE+4StkvwdKAAyhBBNpWcii0HeiWBZ3l1gaFYQlb6bChjZJRZmxSzF5dnvkdF4duQbAC3JmjeIbPA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@ -4717,15 +4717,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@spotlightjs/overlay": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-2.15.1.tgz",
|
||||
"integrity": "sha512-5TpHWFRiTm8rrNINOQs9iFsqVnguFGHU1cK/bmhrysNzts4tYQT9d+kWvl++GlItKezIPbu5xPD9VoapO30cyw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-3.0.0.tgz",
|
||||
"integrity": "sha512-0b03WtsykqpcOKmjDRnRZf0GGfaEB6ZHGctLZZxFK4NHTDBNJ6BaQZjunr4XU35kKR5BT2OFp5E/DPKluih0Hg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@spotlightjs/sidecar": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@spotlightjs/sidecar/-/sidecar-1.11.3.tgz",
|
||||
"integrity": "sha512-2FNZjnvJH71pAsYlJA/LIaEZ0jdtjqrlD58F/xJ5ZhI7z6US5zIqE7DMrqaK/tvObFam71CyCncKHRG6M0l6Cg==",
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@spotlightjs/sidecar/-/sidecar-1.11.4.tgz",
|
||||
"integrity": "sha512-8uDJNhvt6uVNvIoBltjRBqb0a//SxkKoyPACtNjq9k9qMYSfFhE0RVtgqnJNBineXeJfxzK5uvzeG/X7pEhYeQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
@ -4741,21 +4741,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@spotlightjs/spotlight": {
|
||||
"version": "2.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-2.13.3.tgz",
|
||||
"integrity": "sha512-wDnXJaSVexPC/+blgXXx2AYCk7S+5lT4TCJmu0HZAVtYd2sDgNub/wAOitsKYxvpRtIQnPe55IlvL4r1X7goSg==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-3.0.0.tgz",
|
||||
"integrity": "sha512-dkMineYpONLUmkHh7gvBhjf34ES8a08KDQXNem9/0JzAMy/bXSDlC95sqkX9wDfKWjq2rJKYjJulNtCuGHDaeA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@sentry/node": "^8.49.0",
|
||||
"@spotlightjs/overlay": "2.15.1",
|
||||
"@spotlightjs/sidecar": "1.11.3",
|
||||
"@spotlightjs/overlay": "3.0.0",
|
||||
"@spotlightjs/sidecar": "1.11.4",
|
||||
"import-meta-resolve": "^4.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"spotlight": "bin/run.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
@ -17143,9 +17143,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/import-in-the-middle": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.2.tgz",
|
||||
"integrity": "sha512-Yjp9X7s2eHSXvZYQ0aye6UvwYPrVB5C2k47fuXjFKnYinAByaDZjh4t9MT2wEga9775n6WaIqyHnQhBxYtX2mg==",
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.0.tgz",
|
||||
"integrity": "sha512-g5zLT0HaztRJWysayWYiUq/7E5H825QIiecMD2pI5QO7Wzr847l6GDvPvmZaDIdrDtS2w7qRczywxiK6SL5vRw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.0",
|
||||
|
@ -102,8 +102,8 @@
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^9.28.0",
|
||||
"@spotlightjs/spotlight": "^2.13.3",
|
||||
"@sentry/browser": "^9.28.1",
|
||||
"@spotlightjs/spotlight": "^3.0.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"change-case": "^5.4.4",
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
|
||||
<body>
|
||||
<trans-unit id="s4caed5b7a7e5d89b">
|
||||
@ -596,9 +596,9 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="saa0e2675da69651b">
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>未找到 URL "
|
||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>未找到 URL "
|
||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s58cd9c2fe836d9c6">
|
||||
@ -1709,8 +1709,8 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa90b7809586c35ce">
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s0410779cb47de312">
|
||||
@ -3762,10 +3762,10 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa95a538bfbb86111">
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<target>您确定要更新
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sc92d7cfb6ee1fec6">
|
||||
@ -4831,7 +4831,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sdf1d8edef27236f0">
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
||||
|
||||
</trans-unit>
|
||||
@ -5190,7 +5190,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s1608b2f94fa0dbd4">
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target>
|
||||
|
||||
</trans-unit>
|
||||
@ -7466,7 +7466,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>成功创建用户并添加到组 <x id="0" equiv-text="${this.group.name}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s824e0943a7104668">
|
||||
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||
<target>此用户将会被添加到组 &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62e7f6ed7d9cb3ca">
|
||||
@ -8748,7 +8748,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>同步组</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2d5f69929bb7221d">
|
||||
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
|
||||
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${p.name}"/>(&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;,类型为 <x id="2" equiv-text="${p.type}"/>)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s25bacc19d98b444e">
|
||||
@ -8996,8 +8996,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>授权流程成功后有效的重定向 URI。还可以在此处为隐式流程指定任何来源。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c49d27de60a532b">
|
||||
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>要允许任何重定向 URI,请设置模式为正则表达式,并将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>要允许任何重定向 URI,请设置模式为正则表达式,并将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa52bf79fe1ccb13e">
|
||||
<source>Federated OIDC Sources</source>
|
||||
@ -9750,7 +9750,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>在 authorization_code 令牌请求流程期间,如何执行身份验证</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s844baf19a6c4a9b4">
|
||||
<source>Enable "Remember me on this device"</source>
|
||||
<source>Enable "Remember me on this device"</source>
|
||||
<target>启用“在此设备上记住我”</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sfa72bca733f40692">
|
||||
@ -9883,4 +9883,4 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
||||
|
@ -661,12 +661,6 @@
|
||||
<source>Apps with most usage</source>
|
||||
<target>使用率最高的应用</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sda5e1499f93146ad">
|
||||
<source><x id="0" equiv-text="${ago}"/> days ago</source>
|
||||
<target>
|
||||
<x id="0" equiv-text="${ago}"/>天前</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s51ea3a244c781b1f">
|
||||
<source>Objects created</source>
|
||||
@ -6118,21 +6112,11 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source>Download Private key</source>
|
||||
<target>下载私钥</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s3a5fec3d73ac9edc">
|
||||
<source>Create Certificate-Key Pair</source>
|
||||
<target>创建证书密钥对</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45cb501abd43ba52">
|
||||
<source>Generate</source>
|
||||
<target>生成</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sf9bddaf910f4eea5">
|
||||
<source>Generate Certificate-Key Pair</source>
|
||||
<target>生成证书密钥对</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="see2bcbc11bb91960">
|
||||
<source>Successfully updated instance.</source>
|
||||
@ -7533,10 +7517,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<source>Configure SCIM Provider</source>
|
||||
<target>配置 SCIM 提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s7513372fe60f6387">
|
||||
<source>Event volume</source>
|
||||
<target>事件容量</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s3271da6c18c25b18">
|
||||
<source>Connection settings.</source>
|
||||
<target>连接设置。</target>
|
||||
@ -9888,6 +9868,18 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s04bb32ec9f359507">
|
||||
<source>Additional Group DN</source>
|
||||
<target>额外的组 DN</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb7af25ce6e30d61a">
|
||||
<source>The currently selected policy engine mode is <x id="0" equiv-text="${policyEngineMode.label}"/>:</source>
|
||||
<target>当前所选策略引擎模式为 <x id="0" equiv-text="${policyEngineMode.label}"/>:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="se1d2545eda4b1600">
|
||||
<source>Import Existing Certificate-Key Pair</source>
|
||||
<target>导入已有的证书密钥对</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sb3d5c0a0501669df">
|
||||
<source>Generate New Certificate-Key Pair</source>
|
||||
<target>生成新的证书密钥对</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -72,7 +72,7 @@ To check if your config has been applied correctly, you can run the following co
|
||||
- `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD`
|
||||
{/* TODO: Temporarily deactivated feature, see https://github.com/goauthentik/authentik/issues/14320 */}
|
||||
{/* - `AUTHENTIK_POSTGRESQL__USE_POOL`: Use a [connection pool](https://docs.djangoproject.com/en/stable/ref/databases/#connection-pool) for PostgreSQL connections. Defaults to `false`. :ak-version[2025.4] */}
|
||||
- `AUTHENTIK_POSTGRESQL__POOL_OPTIONS`: Extra configuration to pass to the [ConnectionPool object](https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.ConnectionPool) when it is created. Must be a base64-encoded JSON dictionary. Ignored when `USE_POOL` is set to `false`. :ak-version[2025.4]
|
||||
{/* - `AUTHENTIK_POSTGRESQL__POOL_OPTIONS`: Extra configuration to pass to the [ConnectionPool object](https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.ConnectionPool) when it is created. Must be a base64-encoded JSON dictionary. Ignored when `USE_POOL` is set to `false`. :ak-version[2025.4] */}
|
||||
- `AUTHENTIK_POSTGRESQL__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer. Deprecated, see below
|
||||
- `AUTHENTIK_POSTGRESQL__USE_PGPOOL`: Adjust configuration to support connection to Pgpool. Deprecated, see below
|
||||
- `AUTHENTIK_POSTGRESQL__SSLMODE`: Strictness of ssl verification. Defaults to `"verify-ca"`
|
||||
|
66
website/package-lock.json
generated
66
website/package-lock.json
generated
@ -19,11 +19,11 @@
|
||||
"@goauthentik/docusaurus-config": "^1.1.0",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@swc/html-linux-x64-gnu": "1.12.0",
|
||||
"@swc/html-linux-x64-gnu": "1.12.1",
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^4.4.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.4.0",
|
||||
"postcss": "^8.5.4",
|
||||
"postcss": "^8.5.5",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
@ -42,7 +42,7 @@
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/lodash": "^4.17.17",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"@types/postman-collection": "^3.5.11",
|
||||
"@types/react": "^18.3.22",
|
||||
"@types/semver": "^7.7.0",
|
||||
@ -64,12 +64,12 @@
|
||||
"@rspack/binding-darwin-arm64": "1.3.15",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.15",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.15",
|
||||
"@swc/core-darwin-arm64": "1.12.0",
|
||||
"@swc/core-linux-arm64-gnu": "1.12.0",
|
||||
"@swc/core-linux-x64-gnu": "1.12.0",
|
||||
"@swc/html-darwin-arm64": "1.12.0",
|
||||
"@swc/html-linux-arm64-gnu": "1.12.0",
|
||||
"@swc/html-linux-x64-gnu": "1.12.0",
|
||||
"@swc/core-darwin-arm64": "1.12.1",
|
||||
"@swc/core-linux-arm64-gnu": "1.12.1",
|
||||
"@swc/core-linux-x64-gnu": "1.12.1",
|
||||
"@swc/html-darwin-arm64": "1.12.1",
|
||||
"@swc/html-linux-arm64-gnu": "1.12.1",
|
||||
"@swc/html-linux-x64-gnu": "1.12.1",
|
||||
"lightningcss-darwin-arm64": "1.30.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1"
|
||||
@ -5592,9 +5592,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.0.tgz",
|
||||
"integrity": "sha512-usLr8kC80GDv3pwH2zoEaS279kxtWY0MY3blbMFw7zA8fAjqxa8IDxm3WcgyNLNWckWn4asFfguEwz/Weem3nA==",
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.1.tgz",
|
||||
"integrity": "sha512-nUjWVcJ3YS2N40ZbKwYO2RJ4+o2tWYRzNOcIQp05FqW0+aoUCVMdAUUzQinPDynfgwVshDAXCKemY8X7nN5MaA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -5640,9 +5640,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.0.tgz",
|
||||
"integrity": "sha512-Al0x33gUVxNY5tutEYpSyv7mze6qQS1ONa0HEwoRxcK9WXsX0NHLTiOSGZoCUS1SsXM37ONlbA6/Bsp1MQyP+g==",
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.1.tgz",
|
||||
"integrity": "sha512-BxJDIJPq1+aCh9UsaSAN6wo3tuln8UhNXruOrzTI8/ElIig/3sAueDM6Eq7GvZSGGSA7ljhNATMJ0elD7lFatQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -5672,9 +5672,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.0.tgz",
|
||||
"integrity": "sha512-ltIvqNi7H0c5pRawyqjeYSKEIfZP4vv/datT3mwT6BW7muJtd1+KIDCPFLMIQ4wm/h76YQwPocsin3fzmnFdNA==",
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.1.tgz",
|
||||
"integrity": "sha512-CrYnV8SZIgArQ9LKH0xEF95PKXzX9WkRSc5j55arOSBeDCeDUQk1Bg/iKdnDiuj5HC1hZpvzwMzSBJjv+Z70jA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -5830,9 +5830,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/html-darwin-arm64": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.12.0.tgz",
|
||||
"integrity": "sha512-okpx8G7xGSPiSekxS4FQu3aR8k+q8nZJCfVKzanQxdZUaCm7YDVUci2Unqp9TvpgZJRA0GOWs1U3QMu2vdr0sQ==",
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.12.1.tgz",
|
||||
"integrity": "sha512-vbCqYgBBdoxlsnUe/G6irBJ69LUOrlLVXgdxWxDSZ3YcbnpVmwi5YEeaRvqf4vNzZ/nzBMd4DYl6KK2Qsi0prw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -5878,9 +5878,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/html-linux-arm64-gnu": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.12.0.tgz",
|
||||
"integrity": "sha512-ImZLbghifCPqQhwbEprv2zojieD0j/RGJ+tkNpJ6DyGqcf5qVFfPgGDe/WDPEHCMbJlAodvp1iKTdLSAdTfaLg==",
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.12.1.tgz",
|
||||
"integrity": "sha512-KbqPLtsPVt0/kjp7sUT1APfEtNQUqMam3S0RzJkvuMz9jB2F9DREvj5EG+DPnx2s/kxnDm4sh9vM2sG2xNHErQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -5910,9 +5910,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/html-linux-x64-gnu": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.12.0.tgz",
|
||||
"integrity": "sha512-IVFXgsyn0/8e9nfVrQXAdGDFboom0nls7KSOJ/+oXMmdK917wrnYLDt7M4DyRT2c+xJmMxgR6tyaTW8KLAl02w==",
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.12.1.tgz",
|
||||
"integrity": "sha512-9QNCTgCZtyQVifLXqDTW7v4lgaC11v0/iL9OhsSZ19ycJrBmnxBmZtDIbuQrXAIzE1GD8mMOK/GLey2IeceoDQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -6615,9 +6615,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz",
|
||||
"integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==",
|
||||
"version": "24.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz",
|
||||
"integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.8.0"
|
||||
@ -20672,9 +20672,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
|
||||
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
|
||||
"version": "8.5.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
|
||||
"integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -37,7 +37,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^4.4.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.4.0",
|
||||
"postcss": "^8.5.4",
|
||||
"postcss": "^8.5.5",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
@ -56,7 +56,7 @@
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/lodash": "^4.17.17",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"@types/postman-collection": "^3.5.11",
|
||||
"@types/react": "^18.3.22",
|
||||
"@types/semver": "^7.7.0",
|
||||
@ -75,12 +75,12 @@
|
||||
"@rspack/binding-darwin-arm64": "1.3.15",
|
||||
"@rspack/binding-linux-arm64-gnu": "1.3.15",
|
||||
"@rspack/binding-linux-x64-gnu": "1.3.15",
|
||||
"@swc/core-darwin-arm64": "1.12.0",
|
||||
"@swc/core-linux-arm64-gnu": "1.12.0",
|
||||
"@swc/core-linux-x64-gnu": "1.12.0",
|
||||
"@swc/html-darwin-arm64": "1.12.0",
|
||||
"@swc/html-linux-arm64-gnu": "1.12.0",
|
||||
"@swc/html-linux-x64-gnu": "1.12.0",
|
||||
"@swc/core-darwin-arm64": "1.12.1",
|
||||
"@swc/core-linux-arm64-gnu": "1.12.1",
|
||||
"@swc/core-linux-x64-gnu": "1.12.1",
|
||||
"@swc/html-darwin-arm64": "1.12.1",
|
||||
"@swc/html-linux-arm64-gnu": "1.12.1",
|
||||
"@swc/html-linux-x64-gnu": "1.12.1",
|
||||
"lightningcss-darwin-arm64": "1.30.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||
"lightningcss-linux-x64-gnu": "1.30.1"
|
||||
|
Reference in New Issue
Block a user