From f28b18805ff46eade27a82d70929fdb8a21e6d32 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 14 Nov 2022 13:48:29 +0100 Subject: [PATCH] add initial Signed-off-by: Jens Langhammer Signed-off-by: Jens Langhammer --- authentik/core/api/providers.py | 1 + .../0028_provider_invalidation_flow.py | 26 ++++ authentik/core/models.py | 10 ++ authentik/flows/challenge.py | 6 + authentik/flows/models.py | 4 +- authentik/providers/oauth2/urls.py | 3 +- .../providers/oauth2/views/end_session.py | 39 ++++++ .../flow-default-provider-invalidation.yaml | 13 ++ schema.yml | 117 ++++++++++++++++++ .../providers/oauth2/OAuth2ProviderForm.ts | 35 ++++++ 10 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 authentik/core/migrations/0028_provider_invalidation_flow.py create mode 100644 authentik/providers/oauth2/views/end_session.py create mode 100644 blueprints/default/flow-default-provider-invalidation.yaml diff --git a/authentik/core/api/providers.py b/authentik/core/api/providers.py index a5095dcde..1251d7415 100644 --- a/authentik/core/api/providers.py +++ b/authentik/core/api/providers.py @@ -42,6 +42,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer): "name", "authentication_flow", "authorization_flow", + "invalidation_flow", "property_mappings", "component", "assigned_application_slug", diff --git a/authentik/core/migrations/0028_provider_invalidation_flow.py b/authentik/core/migrations/0028_provider_invalidation_flow.py new file mode 100644 index 000000000..9fedd6c71 --- /dev/null +++ b/authentik/core/migrations/0028_provider_invalidation_flow.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.7 on 2023-03-22 22:26 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_flows", "0025_alter_flowstagebinding_evaluate_on_plan_and_more"), + ("authentik_core", "0027_alter_user_uuid"), + ] + + operations = [ + migrations.AddField( + model_name="provider", + name="invalidation_flow", + field=models.ForeignKey( + default=None, + help_text="Flow used ending the session from a provider.", + null=True, + on_delete=django.db.models.deletion.SET_DEFAULT, + related_name="provider_invalidation", + to="authentik_flows.flow", + ), + ), + ] diff --git a/authentik/core/models.py b/authentik/core/models.py index 7d11af3d6..06ab7d1cd 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -299,11 +299,21 @@ class Provider(SerializerModel): authorization_flow = models.ForeignKey( "authentik_flows.Flow", + # Set to cascade even though null is allowed, since most providers + # still require an authorization flow set on_delete=models.CASCADE, null=True, help_text=_("Flow used when authorizing this provider."), related_name="provider_authorization", ) + invalidation_flow = models.ForeignKey( + "authentik_flows.Flow", + on_delete=models.SET_DEFAULT, + default=None, + null=True, + help_text=_("Flow used ending the session from a provider."), + related_name="provider_invalidation", + ) property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True) diff --git a/authentik/flows/challenge.py b/authentik/flows/challenge.py index eb968ce79..517bdbb27 100644 --- a/authentik/flows/challenge.py +++ b/authentik/flows/challenge.py @@ -125,6 +125,12 @@ class AccessDeniedChallenge(WithUserInfoChallenge): component = CharField(default="ak-stage-access-denied") +class SessionEndChallenge(WithUserInfoChallenge): + """Challenge for ending a session""" + + component = CharField(default="ak-stage-session-end") + + class PermissionDict(TypedDict): """Consent Permission""" diff --git a/authentik/flows/models.py b/authentik/flows/models.py index 67f7f0a9c..f87f7aa1f 100644 --- a/authentik/flows/models.py +++ b/authentik/flows/models.py @@ -105,7 +105,9 @@ class Stage(SerializerModel): def in_memory_stage(view: type["StageView"], **kwargs) -> Stage: - """Creates an in-memory stage instance, based on a `view` as view.""" + """Creates an in-memory stage instance, based on a `view` as view. + Any key-word arguments are set as attributes on the stage object, + accessible via `self.executor.current_stage`.""" stage = Stage() # Because we can't pickle a locally generated function, # we set the view as a separate property and reference a generic function diff --git a/authentik/providers/oauth2/urls.py b/authentik/providers/oauth2/urls.py index f310fa1bc..548cfee35 100644 --- a/authentik/providers/oauth2/urls.py +++ b/authentik/providers/oauth2/urls.py @@ -11,6 +11,7 @@ from authentik.providers.oauth2.api.tokens import ( ) from authentik.providers.oauth2.views.authorize import AuthorizationFlowInitView from authentik.providers.oauth2.views.device_backchannel import DeviceView +from authentik.providers.oauth2.views.end_session import EndSessionView from authentik.providers.oauth2.views.introspection import TokenIntrospectionView from authentik.providers.oauth2.views.jwks import JWKSView from authentik.providers.oauth2.views.provider import ProviderInfoView @@ -43,7 +44,7 @@ urlpatterns = [ ), path( "/end-session/", - RedirectView.as_view(pattern_name="authentik_core:if-session-end", query_string=True), + EndSessionView.as_view(), name="end-session", ), path("/jwks/", JWKSView.as_view(), name="jwks"), diff --git a/authentik/providers/oauth2/views/end_session.py b/authentik/providers/oauth2/views/end_session.py new file mode 100644 index 000000000..bf5b57e01 --- /dev/null +++ b/authentik/providers/oauth2/views/end_session.py @@ -0,0 +1,39 @@ +"""oauth2 provider end_session Views""" +from django.http import Http404, HttpRequest, HttpResponse +from django.shortcuts import get_object_or_404 + +from authentik.core.models import Application +from authentik.flows.challenge import SessionEndChallenge +from authentik.flows.models import in_memory_stage +from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner +from authentik.flows.views.executor import SESSION_KEY_PLAN +from authentik.lib.utils.urls import redirect_with_qs +from authentik.policies.views import PolicyAccessView + + +class EndSessionView(PolicyAccessView): + """Redirect to application's provider's invalidation flow""" + + def resolve_provider_application(self): + self.application = get_object_or_404(Application, slug=self.kwargs["application_slug"]) + self.provider = self.application.get_provider() + if not self.provider or not self.provider.invalidation_flow: + raise Http404 + + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + """Dispatch the flow planner for the invalidation flow""" + planner = FlowPlanner(self.provider.invalidation_flow) + planner.allow_empty_flows = True + plan = planner.plan( + request, + { + PLAN_CONTEXT_APPLICATION: self.application, + }, + ) + plan.insert_stage(in_memory_stage(SessionEndChallenge)) + request.session[SESSION_KEY_PLAN] = plan + return redirect_with_qs( + "authentik_core:if-flow", + self.request.GET, + flow_slug=self.provider.invalidation_flow.slug, + ) diff --git a/blueprints/default/flow-default-provider-invalidation.yaml b/blueprints/default/flow-default-provider-invalidation.yaml new file mode 100644 index 000000000..4d29b8a42 --- /dev/null +++ b/blueprints/default/flow-default-provider-invalidation.yaml @@ -0,0 +1,13 @@ +version: 1 +metadata: + name: Default - Provider invalidation flow +entries: +- attrs: + designation: invalidation + name: Logout + title: You've logged out of %(app)s. + authentication: none + identifiers: + slug: default-provider-invalidation-flow + model: authentik_flows.flow + id: flow diff --git a/schema.yml b/schema.yml index 694eca69c..cb3b9120d 100644 --- a/schema.yml +++ b/schema.yml @@ -16394,6 +16394,11 @@ paths: name: is_backchannel schema: type: boolean + - in: query + name: invalidation_flow + schema: + type: string + format: uuid - in: query name: issuer schema: @@ -29557,6 +29562,7 @@ components: - $ref: '#/components/schemas/PlexAuthenticationChallenge' - $ref: '#/components/schemas/PromptChallenge' - $ref: '#/components/schemas/RedirectChallenge' + - $ref: '#/components/schemas/SessionEndChallenge' - $ref: '#/components/schemas/ShellChallenge' - $ref: '#/components/schemas/UserLoginChallenge' discriminator: @@ -29583,6 +29589,7 @@ components: ak-source-plex: '#/components/schemas/PlexAuthenticationChallenge' ak-stage-prompt: '#/components/schemas/PromptChallenge' xak-flow-redirect: '#/components/schemas/RedirectChallenge' + ak-stage-session-end: '#/components/schemas/SessionEndChallenge' xak-flow-shell: '#/components/schemas/ShellChallenge' ak-stage-user-login: '#/components/schemas/UserLoginChallenge' ClientTypeEnum: @@ -32592,6 +32599,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -32705,6 +32717,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -33627,6 +33644,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -33760,6 +33782,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -36728,6 +36755,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -36986,6 +37018,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -37478,6 +37515,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -37566,6 +37608,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -37657,6 +37704,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -38984,6 +39036,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -39074,6 +39131,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -39242,6 +39304,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -39386,6 +39453,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -39515,6 +39587,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -39602,6 +39679,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -39944,6 +40026,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -40118,6 +40205,11 @@ components: type: string format: uuid description: Flow used when authorizing this provider. + invalidation_flow: + type: string + format: uuid + nullable: true + description: Flow used ending the session from a provider. property_mappings: type: array items: @@ -40814,6 +40906,31 @@ components: required: - healthy - version + SessionEndChallenge: + type: object + description: Challenge for ending a session + properties: + type: + $ref: '#/components/schemas/ChallengeChoices' + flow_info: + $ref: '#/components/schemas/ContextualFlowInfo' + component: + type: string + default: ak-stage-session-end + response_errors: + type: object + additionalProperties: + type: array + items: + $ref: '#/components/schemas/ErrorDetail' + pending_user: + type: string + pending_user_avatar: + type: string + required: + - pending_user + - pending_user_avatar + - type SessionUser: type: object description: |- diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts index 000df8cad..105f05d5f 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts @@ -193,6 +193,41 @@ export class OAuth2ProviderFormPage extends BaseProviderForm { ${msg("Flow used when authorizing this provider.")}

+ + => { + const args: FlowsInstancesListRequest = { + ordering: "slug", + designation: FlowsInstancesListDesignationEnum.Invalidation, + }; + if (query !== undefined) { + args.search = query; + } + const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args); + return flows.results; + }} + .renderElement=${(flow: Flow): string => { + return RenderFlowOption(flow); + }} + .renderDescription=${(flow: Flow): TemplateResult => { + return html`${flow.name}`; + }} + .value=${(flow: Flow | undefined): string | undefined => { + return flow?.pk; + }} + .selected=${(flow: Flow): boolean => { + return flow.pk === this.instance?.invalidationFlow; + }} + > + +

+ ${t`Flow used when authorizing this provider.`} +

+
${msg("Protocol settings")}