mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
enterprise/providers: WSFed configurable realm, default wreply (#19996)
* enterprise/providers/wsfed: make realm configurable Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make wreply optional, fallback to configure Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use audience instead of issuer Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix lookup Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@@ -2,10 +2,9 @@
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.urls import reverse
|
||||
from rest_framework.fields import SerializerMethodField, URLField
|
||||
from rest_framework.fields import CharField, SerializerMethodField, URLField
|
||||
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.models import Application
|
||||
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||
from authentik.enterprise.providers.ws_federation.models import WSFederationProvider
|
||||
from authentik.enterprise.providers.ws_federation.processors.metadata import MetadataProcessor
|
||||
@@ -16,8 +15,8 @@ class WSFederationProviderSerializer(EnterpriseRequiredMixin, SAMLProviderSerial
|
||||
"""WSFederationProvider Serializer"""
|
||||
|
||||
reply_url = URLField(source="acs_url")
|
||||
wtrealm = CharField(source="audience")
|
||||
url_wsfed = SerializerMethodField()
|
||||
wtrealm = SerializerMethodField()
|
||||
|
||||
def get_url_wsfed(self, instance: WSFederationProvider) -> str:
|
||||
"""Get WS-Fed url"""
|
||||
@@ -26,16 +25,11 @@ class WSFederationProviderSerializer(EnterpriseRequiredMixin, SAMLProviderSerial
|
||||
request: HttpRequest = self._context["request"]._request
|
||||
return request.build_absolute_uri(reverse("authentik_providers_ws_federation:wsfed"))
|
||||
|
||||
def get_wtrealm(self, instance: WSFederationProvider) -> str:
|
||||
try:
|
||||
return f"goauthentik.io://app/{instance.application.slug}"
|
||||
except Application.DoesNotExist:
|
||||
return None
|
||||
|
||||
class Meta(SAMLProviderSerializer.Meta):
|
||||
model = WSFederationProvider
|
||||
fields = ProviderSerializer.Meta.fields + [
|
||||
"reply_url",
|
||||
"wtrealm",
|
||||
"assertion_valid_not_before",
|
||||
"assertion_valid_not_on_or_after",
|
||||
"session_valid_not_on_or_after",
|
||||
@@ -51,7 +45,6 @@ class WSFederationProviderSerializer(EnterpriseRequiredMixin, SAMLProviderSerial
|
||||
"default_name_id_policy",
|
||||
"url_download_metadata",
|
||||
"url_wsfed",
|
||||
"wtrealm",
|
||||
]
|
||||
extra_kwargs = ProviderSerializer.Meta.extra_kwargs
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ from authentik.providers.saml.models import SAMLProvider
|
||||
class WSFederationProvider(SAMLProvider):
|
||||
"""WS-Federation for applications which support WS-Fed."""
|
||||
|
||||
# Alias'd fields:
|
||||
# - acs_url -> reply_url
|
||||
# - audience -> realm / wtrealm
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
from authentik.enterprise.providers.ws_federation.api.providers import (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -37,8 +36,6 @@ class SignInRequest:
|
||||
wreply: str
|
||||
wctx: str | None
|
||||
|
||||
app_slug: str
|
||||
|
||||
@staticmethod
|
||||
def parse(request: HttpRequest) -> SignInRequest:
|
||||
action = request.GET.get("wa")
|
||||
@@ -47,26 +44,26 @@ class SignInRequest:
|
||||
realm = request.GET.get("wtrealm")
|
||||
if not realm:
|
||||
raise ValueError("Missing Realm")
|
||||
parsed = urlparse(realm)
|
||||
|
||||
req = SignInRequest(
|
||||
wa=action,
|
||||
wtrealm=realm,
|
||||
wreply=request.GET.get("wreply"),
|
||||
wctx=request.GET.get("wctx", ""),
|
||||
app_slug=parsed.path[1:],
|
||||
)
|
||||
|
||||
_, provider = req.get_app_provider()
|
||||
if not req.wreply:
|
||||
req.wreply = provider.acs_url
|
||||
if not req.wreply.startswith(provider.acs_url):
|
||||
raise ValueError("Invalid wreply")
|
||||
return req
|
||||
|
||||
def get_app_provider(self):
|
||||
application = get_object_or_404(Application, slug=self.app_slug)
|
||||
provider: WSFederationProvider = get_object_or_404(
|
||||
WSFederationProvider, pk=application.provider_id
|
||||
WSFederationProvider, audience=self.wtrealm
|
||||
)
|
||||
application = get_object_or_404(Application, provider=provider)
|
||||
return application, provider
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import get_object_or_404
|
||||
@@ -15,8 +14,6 @@ class SignOutRequest:
|
||||
wtrealm: str
|
||||
wreply: str
|
||||
|
||||
app_slug: str
|
||||
|
||||
@staticmethod
|
||||
def parse(request: HttpRequest) -> SignOutRequest:
|
||||
action = request.GET.get("wa")
|
||||
@@ -25,23 +22,23 @@ class SignOutRequest:
|
||||
realm = request.GET.get("wtrealm")
|
||||
if not realm:
|
||||
raise ValueError("Missing Realm")
|
||||
parsed = urlparse(realm)
|
||||
|
||||
req = SignOutRequest(
|
||||
wa=action,
|
||||
wtrealm=realm,
|
||||
wreply=request.GET.get("wreply"),
|
||||
app_slug=parsed.path[1:],
|
||||
)
|
||||
|
||||
_, provider = req.get_app_provider()
|
||||
if not req.wreply:
|
||||
req.wreply = provider.acs_url
|
||||
if not req.wreply.startswith(provider.acs_url):
|
||||
raise ValueError("Invalid wreply")
|
||||
return req
|
||||
|
||||
def get_app_provider(self):
|
||||
application = get_object_or_404(Application, slug=self.app_slug)
|
||||
provider: WSFederationProvider = get_object_or_404(
|
||||
WSFederationProvider, pk=application.provider_id
|
||||
WSFederationProvider, audience=self.wtrealm
|
||||
)
|
||||
application = get_object_or_404(Application, provider=provider)
|
||||
return application, provider
|
||||
|
||||
@@ -43,7 +43,6 @@ class TestWSFedSignIn(TestCase):
|
||||
wtrealm="",
|
||||
wreply="",
|
||||
wctx=None,
|
||||
app_slug="",
|
||||
),
|
||||
)
|
||||
token = proc.response()[WS_FED_POST_KEY_RESULT]
|
||||
@@ -65,7 +64,6 @@ class TestWSFedSignIn(TestCase):
|
||||
wtrealm="",
|
||||
wreply="",
|
||||
wctx=None,
|
||||
app_slug="",
|
||||
),
|
||||
)
|
||||
token = proc.response()[WS_FED_POST_KEY_RESULT]
|
||||
|
||||
@@ -7165,6 +7165,11 @@
|
||||
"minLength": 1,
|
||||
"title": "Reply url"
|
||||
},
|
||||
"wtrealm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Wtrealm"
|
||||
},
|
||||
"assertion_valid_not_before": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
|
||||
+9
-3
@@ -50218,6 +50218,9 @@ components:
|
||||
type: string
|
||||
format: uri
|
||||
minLength: 1
|
||||
wtrealm:
|
||||
type: string
|
||||
minLength: 1
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -57127,6 +57130,8 @@ components:
|
||||
reply_url:
|
||||
type: string
|
||||
format: uri
|
||||
wtrealm:
|
||||
type: string
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
description: 'Assertion valid not before current time + this value (Format:
|
||||
@@ -57187,9 +57192,6 @@ components:
|
||||
type: string
|
||||
description: Get WS-Fed url
|
||||
readOnly: true
|
||||
wtrealm:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- assigned_application_name
|
||||
- assigned_application_slug
|
||||
@@ -57237,6 +57239,9 @@ components:
|
||||
type: string
|
||||
format: uri
|
||||
minLength: 1
|
||||
wtrealm:
|
||||
type: string
|
||||
minLength: 1
|
||||
assertion_valid_not_before:
|
||||
type: string
|
||||
minLength: 1
|
||||
@@ -57297,6 +57302,7 @@ components:
|
||||
- invalidation_flow
|
||||
- name
|
||||
- reply_url
|
||||
- wtrealm
|
||||
WebAuthnDevice:
|
||||
type: object
|
||||
description: Serializer for WebAuthn authenticator devices
|
||||
|
||||
@@ -18,6 +18,10 @@ from tests.e2e.utils import SeleniumTestCase, retry
|
||||
class TestProviderWSFed(SeleniumTestCase):
|
||||
"""test WS Federation flow"""
|
||||
|
||||
def setUp(self):
|
||||
self.realm = generate_id()
|
||||
super().setUp()
|
||||
|
||||
def setup_client(self, provider: WSFederationProvider, app: Application, **kwargs):
|
||||
metadata_url = (
|
||||
self.url(
|
||||
@@ -32,7 +36,7 @@ class TestProviderWSFed(SeleniumTestCase):
|
||||
"8080": "8080",
|
||||
},
|
||||
environment={
|
||||
"WSFED_TEST_SP_WTREALM": f"goauthentik.io://app/{app.slug}",
|
||||
"WSFED_TEST_SP_WTREALM": self.realm,
|
||||
"WSFED_TEST_SP_METADATA": metadata_url,
|
||||
**kwargs,
|
||||
},
|
||||
@@ -61,6 +65,7 @@ class TestProviderWSFed(SeleniumTestCase):
|
||||
provider = WSFederationProvider.objects.create(
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:8080",
|
||||
audience=self.realm,
|
||||
authorization_flow=authorization_flow,
|
||||
invalidation_flow=invalidation_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
@@ -147,6 +152,7 @@ class TestProviderWSFed(SeleniumTestCase):
|
||||
provider = WSFederationProvider.objects.create(
|
||||
name=generate_id(),
|
||||
acs_url="http://localhost:8080",
|
||||
audience=self.realm,
|
||||
authorization_flow=authorization_flow,
|
||||
signing_kp=create_test_cert(),
|
||||
)
|
||||
|
||||
@@ -107,6 +107,14 @@ export class WSFederationProviderForm extends BaseProviderForm<WSFederationProvi
|
||||
value="${ifDefined(this.instance?.replyUrl)}"
|
||||
required
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
name="wtrealm"
|
||||
label=${msg("Realm")}
|
||||
placeholder=${msg("")}
|
||||
input-hint="code"
|
||||
value="${ifDefined(this.instance?.wtrealm)}"
|
||||
required
|
||||
></ak-text-input>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user