providers/oauth2: OpenID conformance (#4758)

* don't open inspector by default when debug is enabled

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* encode error in fragment when using hybrid grant_type

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* require nonce for all response_types that get an id_token from the authorization endpoint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't set empty family_name

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only set at_hash when response has token

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleaner way to get login time

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove authentication requirement from authentication flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use wrapper

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix auth_time not being handled correctly

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* minor cleanup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add test files

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove USER_LOGIN_AUTHENTICATED

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework prompt=login handling

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* also set last login uid for max_age check to prevent double login when max_age and prompt=login is set

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-02-23 15:26:41 +01:00 committed by GitHub
parent c6a14fa4f1
commit 80f4fccd35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 255 additions and 65 deletions

View File

@ -4,6 +4,7 @@ from base64 import b64encode
from django.conf import settings
from django.test import TestCase
from django.utils import timezone
from rest_framework.exceptions import AuthenticationFailed
from authentik.api.authentication import bearer_auth
@ -68,6 +69,7 @@ class TestAPIAuth(TestCase):
user=create_test_admin_user(),
provider=provider,
token=generate_id(),
auth_time=timezone.now(),
_scope=SCOPE_AUTHENTIK_API,
_id_token=json.dumps({}),
)
@ -82,6 +84,7 @@ class TestAPIAuth(TestCase):
user=create_test_admin_user(),
provider=provider,
token=generate_id(),
auth_time=timezone.now(),
_scope="",
_id_token=json.dumps({}),
)

View File

@ -37,7 +37,6 @@ from authentik.lib.utils.file import (
from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import PolicyResult
from authentik.stages.user_login.stage import USER_LOGIN_AUTHENTICATED
LOGGER = get_logger()
@ -186,10 +185,6 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
if superuser_full_list and request.user.is_superuser:
return super().list(request)
# To prevent the user from having to double login when prompt is set to login
# and the user has just signed it. This session variable is set in the UserLoginStage
# and is (quite hackily) removed from the session in applications's API's List method
self.request.session.pop(USER_LOGIN_AUTHENTICATED, None)
queryset = self._filter_queryset_for_list(self.get_queryset())
self.paginate_queryset(queryset)

View File

@ -231,7 +231,7 @@ class AccessDeniedChallengeView(ChallengeStageView):
def get_challenge(self, *args, **kwargs) -> Challenge:
return AccessDeniedChallenge(
data={
"error_message": self.error_message or "Unknown error",
"error_message": str(self.error_message or "Unknown error"),
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-access-denied",
}

View File

@ -1,5 +1,6 @@
"""OAuth2Provider API Views"""
from django.urls import reverse
from django.utils import timezone
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField
@ -153,6 +154,7 @@ class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
user=request.user,
provider=provider,
_scope=" ".join(scope_names),
auth_time=timezone.now(),
),
request,
)

View File

@ -174,10 +174,12 @@ class AuthorizeError(OAuth2Error):
# See:
# http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthError
hash_or_question = "#" if self.grant_type == GrantTypes.IMPLICIT else "?"
fragment_or_query = (
"#" if self.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID] else "?"
)
uri = (
f"{self.redirect_uri}{hash_or_question}error="
f"{self.redirect_uri}{fragment_or_query}error="
f"{self.error}&error_description={description}"
)

View File

@ -110,12 +110,11 @@ class IDToken:
# Convert datetimes into timestamps.
now = timezone.now()
id_token.iat = int(now.timestamp())
id_token.auth_time = int(token.auth_time.timestamp())
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
auth_event = get_login_event(request)
if auth_event:
auth_time = auth_event.created
id_token.auth_time = int(auth_time.timestamp())
# Also check which method was used for authentication
method = auth_event.context.get(PLAN_CONTEXT_METHOD, "")
method_args = auth_event.context.get(PLAN_CONTEXT_METHOD_ARGS, {})

View File

@ -0,0 +1,40 @@
# Generated by Django 4.1.7 on 2023-02-22 22:23
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_oauth2", "0014_alter_refreshtoken_options_and_more"),
]
operations = [
migrations.AddField(
model_name="accesstoken",
name="auth_time",
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name="Authentication time",
),
preserve_default=False,
),
migrations.AddField(
model_name="authorizationcode",
name="auth_time",
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name="Authentication time",
),
preserve_default=False,
),
migrations.AddField(
model_name="refreshtoken",
name="auth_time",
field=models.DateTimeField(
default=django.utils.timezone.now,
verbose_name="Authentication time",
),
preserve_default=False,
),
]

View File

@ -226,7 +226,7 @@ class OAuth2Provider(Provider):
def get_issuer(self, request: HttpRequest) -> Optional[str]:
"""Get issuer, based on request"""
if self.issuer_mode == IssuerMode.GLOBAL:
return request.build_absolute_uri("/")
return request.build_absolute_uri(reverse("authentik_core:root-redirect"))
try:
url = reverse(
"authentik_providers_oauth2:provider-root",
@ -282,6 +282,7 @@ class BaseGrantModel(models.Model):
user = models.ForeignKey(User, verbose_name=_("User"), on_delete=models.CASCADE)
revoked = models.BooleanField(default=False)
_scope = models.TextField(default="", verbose_name=_("Scopes"))
auth_time = models.DateTimeField(verbose_name="Authentication time")
@property
def scope(self) -> list[str]:

View File

@ -204,6 +204,7 @@ class TestAuthorize(OAuthTestCase):
"redirect_uri": "http://local.invalid/Foo",
"scope": "openid",
"state": "foo",
"nonce": generate_id(),
},
)
self.assertEqual(
@ -325,6 +326,7 @@ class TestAuthorize(OAuthTestCase):
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
"nonce": generate_id(),
},
)
response = self.client.get(
@ -378,6 +380,7 @@ class TestAuthorize(OAuthTestCase):
"state": state,
"scope": "openid",
"redirect_uri": "http://localhost",
"nonce": generate_id(),
},
)
response = self.client.get(

View File

@ -4,6 +4,7 @@ from base64 import b64encode
from dataclasses import asdict
from django.urls import reverse
from django.utils import timezone
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
@ -41,6 +42,7 @@ class TesOAuth2Introspection(OAuthTestCase):
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
@ -72,6 +74,7 @@ class TesOAuth2Introspection(OAuthTestCase):
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(

View File

@ -4,6 +4,7 @@ from base64 import b64encode
from dataclasses import asdict
from django.urls import reverse
from django.utils import timezone
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
@ -40,6 +41,7 @@ class TesOAuth2Revoke(OAuthTestCase):
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
@ -62,6 +64,7 @@ class TesOAuth2Revoke(OAuthTestCase):
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(

View File

@ -4,6 +4,7 @@ from json import dumps
from django.test import RequestFactory
from django.urls import reverse
from django.utils import timezone
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
@ -45,7 +46,9 @@ class TestToken(OAuthTestCase):
)
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user()
code = AuthorizationCode.objects.create(code="foobar", provider=provider, user=user)
code = AuthorizationCode.objects.create(
code="foobar", provider=provider, user=user, auth_time=timezone.now()
)
request = self.factory.post(
"/",
data={
@ -99,6 +102,7 @@ class TestToken(OAuthTestCase):
provider=provider,
user=user,
token=generate_id(),
auth_time=timezone.now(),
)
request = self.factory.post(
"/",
@ -127,7 +131,9 @@ class TestToken(OAuthTestCase):
self.app.save()
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user()
code = AuthorizationCode.objects.create(code="foobar", provider=provider, user=user)
code = AuthorizationCode.objects.create(
code="foobar", provider=provider, user=user, auth_time=timezone.now()
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
data={
@ -173,6 +179,7 @@ class TestToken(OAuthTestCase):
user=user,
token=generate_id(),
_id_token=dumps({}),
auth_time=timezone.now(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -221,6 +228,7 @@ class TestToken(OAuthTestCase):
user=user,
token=generate_id(),
_id_token=dumps({}),
auth_time=timezone.now(),
)
response = self.client.post(
reverse("authentik_providers_oauth2:token"),
@ -271,6 +279,7 @@ class TestToken(OAuthTestCase):
user=user,
token=generate_id(),
_id_token=dumps({}),
auth_time=timezone.now(),
)
# Create initial refresh token
response = self.client.post(

View File

@ -3,6 +3,7 @@ import json
from dataclasses import asdict
from django.urls import reverse
from django.utils import timezone
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application
@ -37,6 +38,7 @@ class TestUserinfo(OAuthTestCase):
provider=self.provider,
user=self.user,
token=generate_id(),
auth_time=timezone.now(),
_scope="openid user profile",
_id_token=json.dumps(
asdict(
@ -56,7 +58,6 @@ class TestUserinfo(OAuthTestCase):
{
"name": self.user.name,
"given_name": self.user.name,
"family_name": "",
"preferred_username": self.user.name,
"nickname": self.user.name,
"groups": [group.name for group in self.user.ak_groups.all()],
@ -79,7 +80,6 @@ class TestUserinfo(OAuthTestCase):
{
"name": self.user.name,
"given_name": self.user.name,
"family_name": "",
"preferred_username": self.user.name,
"nickname": self.user.name,
"groups": [group.name for group in self.user.ak_groups.all()],

View File

@ -17,7 +17,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import Application
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.events.signals import get_login_event
from authentik.flows.challenge import (
PLAN_CONTEXT_TITLE,
AutosubmitChallenge,
@ -64,12 +64,11 @@ from authentik.stages.consent.stage import (
PLAN_CONTEXT_CONSENT_PERMISSIONS,
ConsentStageView,
)
from authentik.stages.user_login.stage import USER_LOGIN_AUTHENTICATED
LOGGER = get_logger()
PLAN_CONTEXT_PARAMS = "params"
SESSION_KEY_NEEDS_LOGIN = "authentik/providers/oauth2/needs_login"
SESSION_KEY_LAST_LOGIN_UID = "authentik/providers/oauth2/last_login_uid"
ALLOWED_PROMPT_PARAMS = {PROMPT_NONE, PROMPT_CONSENT, PROMPT_LOGIN}
@ -235,19 +234,22 @@ class OAuthAuthorizationParams:
def check_nonce(self):
"""Nonce parameter validation."""
# https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
# Nonce is only required for Implicit flows
if self.grant_type != GrantTypes.IMPLICIT:
# nonce is required for all flows that return an id_token from the authorization endpoint,
# see https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest or
# https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken and
# https://bitbucket.org/openid/connect/issues/972/nonce-requirement-in-hybrid-auth-request
if self.response_type not in [
ResponseTypes.ID_TOKEN,
ResponseTypes.ID_TOKEN_TOKEN,
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
]:
return
if SCOPE_OPENID not in self.scope:
return
if not self.nonce:
self.nonce = self.state
LOGGER.warning("Using state as nonce for OpenID Request")
if not self.nonce:
if SCOPE_OPENID in self.scope:
LOGGER.warning("Missing nonce for OpenID Request")
raise AuthorizeError(
self.redirect_uri, "invalid_request", self.grant_type, self.state
)
LOGGER.warning("Missing nonce for OpenID Request")
raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state)
def check_code_challenge(self):
"""PKCE validation of the transformation method."""
@ -262,19 +264,24 @@ class OAuthAuthorizationParams:
def create_code(self, request: HttpRequest) -> AuthorizationCode:
"""Create an AuthorizationCode object for the request"""
code = AuthorizationCode()
code.user = request.user
code.provider = self.provider
auth_event = get_login_event(request)
code.code = uuid4().hex
now = timezone.now()
code = AuthorizationCode(
user=request.user,
provider=self.provider,
auth_time=auth_event.created if auth_event else now,
code=uuid4().hex,
expires=now + timedelta_from_string(self.provider.access_code_validity),
scope=self.scope,
nonce=self.nonce,
)
if self.code_challenge and self.code_challenge_method:
code.code_challenge = self.code_challenge
code.code_challenge_method = self.code_challenge_method
code.expires = timezone.now() + timedelta_from_string(self.provider.access_code_validity)
code.scope = self.scope
code.nonce = self.nonce
return code
@ -309,7 +316,6 @@ class AuthorizationFlowInitView(PolicyAccessView):
self.params.grant_type,
self.params.state,
)
error.to_event(redirect_uri=error.redirect_uri).from_http(self.request)
raise RequestValidationError(error.get_response(self.request))
def resolve_provider_application(self):
@ -329,28 +335,40 @@ class AuthorizationFlowInitView(PolicyAccessView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Start FlowPLanner, return to flow executor shell"""
# Require a login event to be set, otherwise make the user re-login
login_event = get_login_event(request)
if not login_event:
LOGGER.warning("request with no login event")
return self.handle_no_permission()
login_uid = str(login_event.pk)
# After we've checked permissions, and the user has access, check if we need
# to re-authenticate the user
if self.params.max_age:
current_age: timedelta = (
timezone.now()
- Event.objects.filter(action=EventAction.LOGIN, user=get_user(self.request.user))
.latest("created")
.created
)
# Attempt to check via the session's login event if set, otherwise we can't
# check
login_time = login_event.created
current_age: timedelta = timezone.now() - login_time
if current_age.total_seconds() > self.params.max_age:
LOGGER.debug(
"Triggering authentication as max_age requirement",
max_age=self.params.max_age,
ago=int(current_age.total_seconds()),
)
# Since we already need to re-authenticate the user, set the old login UID
# in case this request has both max_age and prompt=login
self.request.session[SESSION_KEY_LAST_LOGIN_UID] = login_uid
return self.handle_no_permission()
# If prompt=login, we need to re-authenticate the user regardless
if (
PROMPT_LOGIN in self.params.prompt
and SESSION_KEY_NEEDS_LOGIN not in self.request.session
# To prevent the user from having to double login when prompt is set to login
# and the user has just signed it. This session variable is set in the UserLoginStage
# and is (quite hackily) removed from the session in applications's API's List method
and USER_LOGIN_AUTHENTICATED not in self.request.session
):
self.request.session[SESSION_KEY_NEEDS_LOGIN] = True
return self.handle_no_permission()
# Check if we're not already doing the re-authentication
if PROMPT_LOGIN in self.params.prompt:
# No previous login UID saved, so save the current uid and trigger
# re-login, or previous login UID matches current one, so no re-login happened yet
if (
SESSION_KEY_LAST_LOGIN_UID not in self.request.session
or login_uid == self.request.session[SESSION_KEY_LAST_LOGIN_UID]
):
self.request.session[SESSION_KEY_LAST_LOGIN_UID] = login_uid
return self.handle_no_permission()
scope_descriptions = UserInfoView().get_scope_descriptions(self.params.scope)
# Regardless, we start the planner and return to it
planner = FlowPlanner(self.provider.authorization_flow)
@ -525,6 +543,7 @@ class OAuthFulfillmentStage(StageView):
def create_implicit_response(self, code: Optional[AuthorizationCode]) -> dict:
"""Create implicit response's URL Fragment dictionary"""
query_fragment = {}
auth_event = get_login_event(self.request)
now = timezone.now()
access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity)
@ -533,6 +552,7 @@ class OAuthFulfillmentStage(StageView):
scope=self.params.scope,
expires=access_token_expiry,
provider=self.provider,
auth_time=auth_event.created if auth_event else now,
)
id_token = IDToken.new(self.provider, token, self.request)
@ -553,6 +573,8 @@ class OAuthFulfillmentStage(StageView):
ResponseTypes.CODE_TOKEN,
]:
query_fragment["access_token"] = token.token
# Get at_hash of the current token and update the id_token
id_token.at_hash = token.at_hash
# Check if response_type must include id_token in the response.
if self.params.response_type in [
@ -561,8 +583,6 @@ class OAuthFulfillmentStage(StageView):
ResponseTypes.CODE_ID_TOKEN,
ResponseTypes.CODE_ID_TOKEN_TOKEN,
]:
# Get at_hash of the current token and update the id_token
id_token.at_hash = token.at_hash
query_fragment["id_token"] = self.provider.encode(id_token.to_dict())
token._id_token = dumps(id_token.to_dict())

View File

@ -26,6 +26,7 @@ from authentik.core.models import (
User,
)
from authentik.events.models import Event, EventAction
from authentik.events.signals import get_login_event
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
@ -479,6 +480,7 @@ class TokenView(View):
expires=access_token_expiry,
# Keep same scopes as previous token
scope=self.params.authorization_code.scope,
auth_time=self.params.authorization_code.auth_time,
)
access_token.id_token = IDToken.new(
self.provider,
@ -493,6 +495,7 @@ class TokenView(View):
scope=self.params.authorization_code.scope,
expires=refresh_token_expiry,
provider=self.provider,
auth_time=self.params.authorization_code.auth_time,
)
id_token = IDToken.new(
self.provider,
@ -521,7 +524,6 @@ class TokenView(View):
unauthorized_scopes = set(self.params.scope) - set(self.params.refresh_token.scope)
if unauthorized_scopes:
raise TokenError("invalid_scope")
now = timezone.now()
access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity)
access_token = AccessToken(
@ -530,6 +532,7 @@ class TokenView(View):
expires=access_token_expiry,
# Keep same scopes as previous token
scope=self.params.refresh_token.scope,
auth_time=self.params.refresh_token.auth_time,
)
access_token.id_token = IDToken.new(
self.provider,
@ -544,6 +547,7 @@ class TokenView(View):
scope=self.params.refresh_token.scope,
expires=refresh_token_expiry,
provider=self.provider,
auth_time=self.params.refresh_token.auth_time,
)
id_token = IDToken.new(
self.provider,
@ -578,6 +582,7 @@ class TokenView(View):
user=self.params.user,
expires=access_token_expiry,
scope=self.params.scope,
auth_time=now,
)
access_token.id_token = IDToken.new(
self.provider,
@ -600,11 +605,13 @@ class TokenView(View):
raise DeviceCodeError("authorization_pending")
now = timezone.now()
access_token_expiry = now + timedelta_from_string(self.provider.access_token_validity)
auth_event = get_login_event(self.request)
access_token = AccessToken(
provider=self.provider,
user=self.params.device_code.user,
expires=access_token_expiry,
scope=self.params.device_code.scope,
auth_time=auth_event.created if auth_event else now,
)
access_token.id_token = IDToken.new(
self.provider,
@ -619,6 +626,7 @@ class TokenView(View):
scope=self.params.device_code.scope,
expires=refresh_token_expiry,
provider=self.provider,
auth_time=auth_event.created if auth_event else now,
)
id_token = IDToken.new(
self.provider,

View File

@ -11,8 +11,6 @@ from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
USER_LOGIN_AUTHENTICATED = "user_login_authenticated"
class UserLoginStageView(StageView):
"""Finalise Authentication flow by logging the user in"""
@ -51,7 +49,6 @@ class UserLoginStageView(StageView):
flow_slug=self.executor.flow.slug,
session_duration=self.executor.current_stage.session_duration,
)
self.request.session[USER_LOGIN_AUTHENTICATED] = True
# Only show success message if we don't have a source in the flow
# as sources show their own success messages
if not self.executor.plan.context.get(PLAN_CONTEXT_SOURCE, None):

View File

@ -11,7 +11,7 @@ entries:
designation: authentication
name: Welcome to authentik!
title: Welcome to authentik!
authentication: require_unauthenticated
authentication: none
identifiers:
slug: default-authentication-flow
model: authentik_flows.flow

View File

@ -40,7 +40,6 @@ entries:
# You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
"name": request.user.name,
"given_name": request.user.name,
"family_name": "",
"preferred_username": request.user.username,
"nickname": request.user.username,
# groups is not part of the official userinfo schema, but is a quasi-standard

View File

@ -0,0 +1,8 @@
# #Test files for OpenID Conformance testing.
These config files assume testing is being done using the [OpenID Conformance Suite
](https://openid.net/certification/about-conformance-suite/), locally.
See https://gitlab.com/openid/conformance-suite/-/wikis/Developers/Build-&-Run for running the conformance suite locally.
Requires docker containers to be able to access the host via `host.docker.internal` and an entry in the hosts file that maps `host.docker.internal` to localhost.

View File

@ -0,0 +1,81 @@
version: 1
metadata:
name: OIDC conformance testing
entries:
- identifiers:
managed: goauthentik.io/providers/oauth2/scope-address
model: authentik_providers_oauth2.scopemapping
attrs:
name: "authentik default OAuth Mapping: OpenID 'address'"
scope_name: address
description: "General Address Information"
expression: |
return {
"address": {
"formatted": "foo",
}
}
- identifiers:
managed: goauthentik.io/providers/oauth2/scope-phone
model: authentik_providers_oauth2.scopemapping
attrs:
name: "authentik default OAuth Mapping: OpenID 'phone'"
scope_name: phone
description: "General phone Information"
expression: |
return {
"phone_number": "+1234",
"phone_number_verified": True,
}
- model: authentik_providers_oauth2.oauth2provider
id: provider
identifiers:
name: provider
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
issuer_mode: global
client_id: 4054d882aff59755f2f279968b97ce8806a926e1
client_secret: 4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867
redirect_uris: |
https://localhost:8443/test/a/authentik/callback
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
- model: authentik_core.application
identifiers:
slug: conformance
attrs:
provider: !KeyOf provider
name: Conformance
- model: authentik_providers_oauth2.oauth2provider
id: oidc-conformance-2
identifiers:
name: oidc-conformance-2
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
issuer_mode: global
client_id: ad64aeaf1efe388ecf4d28fcc537e8de08bcae26
client_secret: ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789
redirect_uris: |
https://localhost:8443/test/a/authentik/callback
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
- model: authentik_core.application
identifiers:
slug: oidc-conformance-2
attrs:
provider: !KeyOf oidc-conformance-2
name: OIDC Conformance

View File

@ -0,0 +1,20 @@
{
"alias": "authentik",
"description": "authentik",
"server": {
"discoveryUrl": "http://host.docker.internal:9000/application/o/conformance/.well-known/openid-configuration"
},
"client": {
"client_id": "4054d882aff59755f2f279968b97ce8806a926e1",
"client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867"
},
"client_secret_post": {
"client_id": "4054d882aff59755f2f279968b97ce8806a926e1",
"client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867"
},
"client2": {
"client_id": "ad64aeaf1efe388ecf4d28fcc537e8de08bcae26",
"client_secret": "ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789"
},
"consent": {}
}

View File

@ -41,7 +41,6 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
CapabilitiesEnum,
ChallengeChoices,
ChallengeTypes,
ContextualFlowInfo,
@ -97,7 +96,7 @@ export class FlowExecutor extends AKElement implements StageHost {
tenant!: CurrentTenant;
@state()
inspectorOpen: boolean;
inspectorOpen = false;
_flowInfo?: ContextualFlowInfo;
@ -177,8 +176,6 @@ export class FlowExecutor extends AKElement implements StageHost {
super();
this.ws = new WebsocketClient();
this.flowSlug = window.location.pathname.split("/")[3];
this.inspectorOpen =
globalAK()?.config.capabilities.includes(CapabilitiesEnum.Debug) || false;
if (window.location.search.includes("inspector")) {
this.inspectorOpen = !this.inspectorOpen;
}