
* initial migration Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix loading Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start dynamic ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add serialize Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add error message handling Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix/add tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * prepare docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate to new input Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
"""authentik OAuth2 OpenID well-known views"""
|
|
|
|
from typing import Any
|
|
|
|
from django.http import HttpRequest, HttpResponse, JsonResponse
|
|
from django.shortcuts import get_object_or_404, reverse
|
|
from django.views import View
|
|
from guardian.shortcuts import get_anonymous_user
|
|
from structlog.stdlib import get_logger
|
|
|
|
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
|
from authentik.core.models import Application
|
|
from authentik.providers.oauth2.constants import (
|
|
ACR_AUTHENTIK_DEFAULT,
|
|
GRANT_TYPE_AUTHORIZATION_CODE,
|
|
GRANT_TYPE_CLIENT_CREDENTIALS,
|
|
GRANT_TYPE_DEVICE_CODE,
|
|
GRANT_TYPE_IMPLICIT,
|
|
GRANT_TYPE_PASSWORD,
|
|
GRANT_TYPE_REFRESH_TOKEN,
|
|
PKCE_METHOD_PLAIN,
|
|
PKCE_METHOD_S256,
|
|
SCOPE_OPENID,
|
|
)
|
|
from authentik.providers.oauth2.models import (
|
|
OAuth2Provider,
|
|
ResponseMode,
|
|
ResponseTypes,
|
|
ScopeMapping,
|
|
)
|
|
from authentik.providers.oauth2.utils import cors_allow
|
|
|
|
LOGGER = get_logger()
|
|
|
|
|
|
class ProviderInfoView(View):
|
|
"""OpenID-compliant Provider Info"""
|
|
|
|
provider: OAuth2Provider
|
|
|
|
def get_info(self, provider: OAuth2Provider) -> dict[str, Any]:
|
|
"""Get dictionary for OpenID Connect information"""
|
|
scopes = list(
|
|
ScopeMapping.objects.filter(provider=provider).values_list("scope_name", flat=True)
|
|
)
|
|
if SCOPE_OPENID not in scopes:
|
|
scopes.append(SCOPE_OPENID)
|
|
_, supported_alg = provider.jwt_key
|
|
config = {
|
|
"issuer": provider.get_issuer(self.request),
|
|
"authorization_endpoint": self.request.build_absolute_uri(
|
|
reverse("authentik_providers_oauth2:authorize")
|
|
),
|
|
"token_endpoint": self.request.build_absolute_uri(
|
|
reverse("authentik_providers_oauth2:token")
|
|
),
|
|
"userinfo_endpoint": self.request.build_absolute_uri(
|
|
reverse("authentik_providers_oauth2:userinfo")
|
|
),
|
|
"end_session_endpoint": self.request.build_absolute_uri(
|
|
reverse(
|
|
"authentik_providers_oauth2:end-session",
|
|
kwargs={"application_slug": provider.application.slug},
|
|
)
|
|
),
|
|
"introspection_endpoint": self.request.build_absolute_uri(
|
|
reverse("authentik_providers_oauth2:token-introspection")
|
|
),
|
|
"revocation_endpoint": self.request.build_absolute_uri(
|
|
reverse("authentik_providers_oauth2:token-revoke")
|
|
),
|
|
"device_authorization_endpoint": self.request.build_absolute_uri(
|
|
reverse("authentik_providers_oauth2:device")
|
|
),
|
|
"response_types_supported": [
|
|
ResponseTypes.CODE,
|
|
ResponseTypes.ID_TOKEN,
|
|
ResponseTypes.ID_TOKEN_TOKEN,
|
|
ResponseTypes.CODE_TOKEN,
|
|
ResponseTypes.CODE_ID_TOKEN,
|
|
ResponseTypes.CODE_ID_TOKEN_TOKEN,
|
|
],
|
|
"response_modes_supported": [
|
|
ResponseMode.QUERY,
|
|
ResponseMode.FRAGMENT,
|
|
ResponseMode.FORM_POST,
|
|
],
|
|
"jwks_uri": self.request.build_absolute_uri(
|
|
reverse(
|
|
"authentik_providers_oauth2:jwks",
|
|
kwargs={"application_slug": provider.application.slug},
|
|
)
|
|
),
|
|
"grant_types_supported": [
|
|
GRANT_TYPE_AUTHORIZATION_CODE,
|
|
GRANT_TYPE_REFRESH_TOKEN,
|
|
GRANT_TYPE_IMPLICIT,
|
|
GRANT_TYPE_CLIENT_CREDENTIALS,
|
|
GRANT_TYPE_PASSWORD,
|
|
GRANT_TYPE_DEVICE_CODE,
|
|
],
|
|
"id_token_signing_alg_values_supported": [supported_alg],
|
|
# See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
|
"subject_types_supported": ["public"],
|
|
"token_endpoint_auth_methods_supported": [
|
|
"client_secret_post",
|
|
"client_secret_basic",
|
|
],
|
|
"acr_values_supported": [ACR_AUTHENTIK_DEFAULT],
|
|
"scopes_supported": scopes,
|
|
# https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
|
|
"request_parameter_supported": False,
|
|
"claims_supported": self.get_claims(provider),
|
|
"claims_parameter_supported": False,
|
|
"code_challenge_methods_supported": [PKCE_METHOD_PLAIN, PKCE_METHOD_S256],
|
|
}
|
|
if provider.encryption_key:
|
|
config["id_token_encryption_alg_values_supported"] = ["RSA-OAEP-256"]
|
|
config["id_token_encryption_enc_values_supported"] = ["A256CBC-HS512"]
|
|
return config
|
|
|
|
def get_claims(self, provider: OAuth2Provider) -> list[str]:
|
|
"""Get a list of supported claims based on configured scope mappings"""
|
|
default_claims = [
|
|
"sub",
|
|
"iss",
|
|
"aud",
|
|
"exp",
|
|
"iat",
|
|
"auth_time",
|
|
"acr",
|
|
"amr",
|
|
"nonce",
|
|
]
|
|
for scope in ScopeMapping.objects.filter(provider=provider).order_by("scope_name"):
|
|
value = None
|
|
try:
|
|
value = scope.evaluate(
|
|
user=get_anonymous_user(),
|
|
request=self.request,
|
|
provider=provider,
|
|
)
|
|
except PropertyMappingExpressionException:
|
|
continue
|
|
if value is None:
|
|
continue
|
|
if not isinstance(value, dict):
|
|
continue
|
|
default_claims.extend(value.keys())
|
|
return default_claims
|
|
|
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
"""OpenID-compliant Provider Info"""
|
|
return JsonResponse(self.get_info(self.provider), json_dumps_params={"indent": 2})
|
|
|
|
def dispatch(
|
|
self, request: HttpRequest, application_slug: str, *args: Any, **kwargs: Any
|
|
) -> HttpResponse:
|
|
# Since this view only supports get, we can statically set the CORS headers
|
|
application = get_object_or_404(Application, slug=application_slug)
|
|
self.provider: OAuth2Provider = get_object_or_404(
|
|
OAuth2Provider, pk=application.provider_id
|
|
)
|
|
response = super().dispatch(request, *args, **kwargs)
|
|
cors_allow(request, response, *[x.url for x in self.provider.redirect_uris])
|
|
return response
|