providers: Add ability to choose a default authentication flow (#5070)
* core: add ability to choose a default authentication flow for a provider Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * update web to use correct ak-search-select I don't think this element existed when the PR was initially created, lol Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only use provider authentication flow for authentication designation Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
94a93adb4b
commit
1957717160
|
@ -35,6 +35,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
|||
fields = [
|
||||
"pk",
|
||||
"name",
|
||||
"authentication_flow",
|
||||
"authorization_flow",
|
||||
"property_mappings",
|
||||
"component",
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 4.1.7 on 2023-03-23 21:44
|
||||
|
||||
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="authentication_flow",
|
||||
field=models.ForeignKey(
|
||||
help_text="Flow used for authentication when the associated application is accessed by an un-authenticated user.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="provider_authentication",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -249,6 +249,17 @@ class Provider(SerializerModel):
|
|||
|
||||
name = models.TextField(unique=True)
|
||||
|
||||
authentication_flow = models.ForeignKey(
|
||||
"authentik_flows.Flow",
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
help_text=_(
|
||||
"Flow used for authentication when the associated application is accessed by an "
|
||||
"un-authenticated user."
|
||||
),
|
||||
related_name="provider_authentication",
|
||||
)
|
||||
|
||||
authorization_flow = models.ForeignKey(
|
||||
"authentik_flows.Flow",
|
||||
on_delete=models.CASCADE,
|
||||
|
|
|
@ -129,6 +129,7 @@ class TestApplicationsAPI(APITestCase):
|
|||
"provider_obj": {
|
||||
"assigned_application_name": "allowed",
|
||||
"assigned_application_slug": "allowed",
|
||||
"authentication_flow": None,
|
||||
"authorization_flow": str(self.provider.authorization_flow.pk),
|
||||
"component": "ak-provider-oauth2-form",
|
||||
"meta_model_name": "authentik_providers_oauth2.oauth2provider",
|
||||
|
@ -178,6 +179,7 @@ class TestApplicationsAPI(APITestCase):
|
|||
"provider_obj": {
|
||||
"assigned_application_name": "allowed",
|
||||
"assigned_application_slug": "allowed",
|
||||
"authentication_flow": None,
|
||||
"authorization_flow": str(self.provider.authorization_flow.pk),
|
||||
"component": "ak-provider-oauth2-form",
|
||||
"meta_model_name": "authentik_providers_oauth2.oauth2provider",
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.flows.planner import FlowPlan
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||
from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_PLAN
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
|
||||
|
||||
class TestHelperView(TestCase):
|
||||
|
@ -22,6 +25,41 @@ class TestHelperView(TestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, expected_url)
|
||||
|
||||
def test_default_view_app(self):
|
||||
"""Test that ToDefaultFlow returns the expected URL (when accessing an application)"""
|
||||
Flow.objects.filter(designation=FlowDesignation.AUTHENTICATION).delete()
|
||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
self.client.session[SESSION_KEY_APPLICATION_PRE] = Application(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
provider=OAuth2Provider(
|
||||
name=generate_id(),
|
||||
authentication_flow=flow,
|
||||
),
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse("authentik_flows:default-authentication"),
|
||||
)
|
||||
expected_url = reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, expected_url)
|
||||
|
||||
def test_default_view_app_no_provider(self):
|
||||
"""Test that ToDefaultFlow returns the expected URL
|
||||
(when accessing an application, without a provider)"""
|
||||
Flow.objects.filter(designation=FlowDesignation.AUTHENTICATION).delete()
|
||||
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
self.client.session[SESSION_KEY_APPLICATION_PRE] = Application(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse("authentik_flows:default-authentication"),
|
||||
)
|
||||
expected_url = reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, expected_url)
|
||||
|
||||
def test_default_view_invalid_plan(self):
|
||||
"""Test that ToDefaultFlow returns the expected URL (with an invalid plan)"""
|
||||
Flow.objects.filter(designation=FlowDesignation.INVALIDATION).delete()
|
||||
|
|
|
@ -22,6 +22,7 @@ from sentry_sdk.api import set_tag
|
|||
from sentry_sdk.hub import Hub
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.events.models import Event, EventAction, cleanse_dict
|
||||
from authentik.flows.challenge import (
|
||||
Challenge,
|
||||
|
@ -480,8 +481,14 @@ class ToDefaultFlow(View):
|
|||
flow = None
|
||||
# First, attempt to get default flow from tenant
|
||||
if self.designation == FlowDesignation.AUTHENTICATION:
|
||||
flow = tenant.flow_authentication
|
||||
if self.designation == FlowDesignation.INVALIDATION:
|
||||
# Attempt to get default flow from application
|
||||
if SESSION_KEY_APPLICATION_PRE in self.request.session:
|
||||
application: Application = self.request.session[SESSION_KEY_APPLICATION_PRE]
|
||||
if application.provider:
|
||||
flow = application.provider.authentication_flow
|
||||
else:
|
||||
flow = tenant.flow_authentication
|
||||
elif self.designation == FlowDesignation.INVALIDATION:
|
||||
flow = tenant.flow_invalidation
|
||||
# If no flow was set, get the first based on slug and policy
|
||||
if not flow:
|
||||
|
|
115
schema.yml
115
schema.yml
|
@ -15821,6 +15821,11 @@ paths:
|
|||
name: audience
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: authentication_flow
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- in: query
|
||||
name: authorization_flow
|
||||
schema:
|
||||
|
@ -30582,6 +30587,12 @@ components:
|
|||
title: ID
|
||||
name:
|
||||
type: string
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -30672,6 +30683,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -31314,6 +31331,12 @@ components:
|
|||
title: ID
|
||||
name:
|
||||
type: string
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -31430,6 +31453,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -36068,6 +36097,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -36297,6 +36332,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -36759,6 +36800,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -36841,6 +36888,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -36853,7 +36906,7 @@ components:
|
|||
client_networks:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: List of CIDRs (comma-seperated) that clients can connect from.
|
||||
description: List of CIDRs (comma-separated) that clients can connect from.
|
||||
A more specific CIDR will match before a looser one. Clients connecting
|
||||
from a non-specified CIDR will be dropped.
|
||||
shared_secret:
|
||||
|
@ -36911,6 +36964,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38157,6 +38216,12 @@ components:
|
|||
title: ID
|
||||
name:
|
||||
type: string
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38215,6 +38280,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38375,6 +38446,12 @@ components:
|
|||
title: ID
|
||||
name:
|
||||
type: string
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38503,6 +38580,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38598,7 +38681,7 @@ components:
|
|||
type: string
|
||||
client_networks:
|
||||
type: string
|
||||
description: List of CIDRs (comma-seperated) that clients can connect from.
|
||||
description: List of CIDRs (comma-separated) that clients can connect from.
|
||||
A more specific CIDR will match before a looser one. Clients connecting
|
||||
from a non-specified CIDR will be dropped.
|
||||
shared_secret:
|
||||
|
@ -38619,6 +38702,12 @@ components:
|
|||
title: ID
|
||||
name:
|
||||
type: string
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38654,7 +38743,7 @@ components:
|
|||
readOnly: true
|
||||
client_networks:
|
||||
type: string
|
||||
description: List of CIDRs (comma-seperated) that clients can connect from.
|
||||
description: List of CIDRs (comma-separated) that clients can connect from.
|
||||
A more specific CIDR will match before a looser one. Clients connecting
|
||||
from a non-specified CIDR will be dropped.
|
||||
shared_secret:
|
||||
|
@ -38677,6 +38766,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -38689,7 +38784,7 @@ components:
|
|||
client_networks:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: List of CIDRs (comma-seperated) that clients can connect from.
|
||||
description: List of CIDRs (comma-separated) that clients can connect from.
|
||||
A more specific CIDR will match before a looser one. Clients connecting
|
||||
from a non-specified CIDR will be dropped.
|
||||
shared_secret:
|
||||
|
@ -38934,6 +39029,12 @@ components:
|
|||
title: ID
|
||||
name:
|
||||
type: string
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -39090,6 +39191,12 @@ components:
|
|||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
authentication_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
description: Flow used for authentication when the associated application
|
||||
is accessed by an un-authenticated user.
|
||||
authorization_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
|
|
|
@ -92,6 +92,37 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
|
|||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Authentication flow`} name="authenticationFlow">
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
||||
const args: FlowsInstancesListRequest = {
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||
};
|
||||
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?.authenticationFlow;
|
||||
}}
|
||||
>
|
||||
</ak-search-select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when a user access this provider and is not authenticated.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
|
|
|
@ -314,6 +314,41 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
|||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authentication flow`}
|
||||
?required=${false}
|
||||
name="authenticationFlow"
|
||||
>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
||||
const args: FlowsInstancesListRequest = {
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||
};
|
||||
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?.authenticationFlow;
|
||||
}}
|
||||
>
|
||||
</ak-search-select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when a user access this provider and is not authenticated.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
|
|
|
@ -81,6 +81,41 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
|
|||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authentication flow`}
|
||||
?required=${false}
|
||||
name="authenticationFlow"
|
||||
>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
||||
const args: FlowsInstancesListRequest = {
|
||||
ordering: "slug",
|
||||
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||
};
|
||||
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?.authenticationFlow;
|
||||
}}
|
||||
>
|
||||
</ak-search-select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used when a user access this provider and is not authenticated.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authorization flow`}
|
||||
?required=${true}
|
||||
|
|
Reference in a new issue