diff --git a/authentik/stages/identification/api.py b/authentik/stages/identification/api.py index 1e3e70aab..082241399 100644 --- a/authentik/stages/identification/api.py +++ b/authentik/stages/identification/api.py @@ -17,6 +17,7 @@ class IdentificationStageSerializer(StageSerializer): "show_matched_user", "enrollment_flow", "recovery_flow", + "sources", ] diff --git a/authentik/stages/identification/migrations/0009_identificationstage_sources.py b/authentik/stages/identification/migrations/0009_identificationstage_sources.py new file mode 100644 index 000000000..b2231b441 --- /dev/null +++ b/authentik/stages/identification/migrations/0009_identificationstage_sources.py @@ -0,0 +1,43 @@ +# Generated by Django 3.2.3 on 2021-05-25 14:01 + +from django.apps.registry import Apps +from django.db import migrations, models +from django.db.backends.base.schema import BaseDatabaseSchemaEditor + + +def assign_sources(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): + db_alias = schema_editor.connection.alias + + IdentificationStage = apps.get_model( + "authentik_stages_identification", "identificationstage" + ) + Source = apps.get_model("authentik_core", "source") + + sources = Source.objects.all() + for stage in IdentificationStage.objects.all().using(db_alias): + stage.sources.set(sources) + stage.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_core", "0021_alter_application_slug"), + ( + "authentik_stages_identification", + "0008_alter_identificationstage_user_fields", + ), + ] + + operations = [ + migrations.AddField( + model_name="identificationstage", + name="sources", + field=models.ManyToManyField( + default=list, + help_text="Specify which sources should be shown.", + to="authentik_core.Source", + ), + ), + migrations.RunPython(assign_sources), + ] diff --git a/authentik/stages/identification/models.py b/authentik/stages/identification/models.py index db7b78e3d..b2d71b358 100644 --- a/authentik/stages/identification/models.py +++ b/authentik/stages/identification/models.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _ from django.views import View from rest_framework.serializers import BaseSerializer +from authentik.core.models import Source from authentik.flows.models import Flow, Stage @@ -71,6 +72,10 @@ class IdentificationStage(Stage): ), ) + sources = models.ManyToManyField( + Source, default=list, help_text=_("Specify which sources should be shown.") + ) + @property def serializer(self) -> BaseSerializer: from authentik.stages.identification.api import IdentificationStageSerializer diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py index 163345e64..ed8a1229c 100644 --- a/authentik/stages/identification/stage.py +++ b/authentik/stages/identification/stage.py @@ -111,7 +111,9 @@ class IdentificationStageView(ChallengeStageView): # Check all enabled source, add them if they have a UI Login button. ui_sources = [] sources: list[Source] = ( - Source.objects.filter(enabled=True).order_by("name").select_subclasses() + current_stage.sources.filter(enabled=True) + .order_by("name") + .select_subclasses() ) for source in sources: ui_login_button = source.ui_login_button diff --git a/authentik/stages/identification/tests.py b/authentik/stages/identification/tests.py index 9a09ce5d9..812233914 100644 --- a/authentik/stages/identification/tests.py +++ b/authentik/stages/identification/tests.py @@ -18,6 +18,9 @@ class TestIdentificationStage(TestCase): self.user = User.objects.create(username="unittest", email="test@beryju.org") self.client = Client() + # OAuthSource for the login view + source = OAuthSource.objects.create(name="test", slug="test") + self.flow = Flow.objects.create( name="test-identification", slug="test-identification", @@ -27,15 +30,14 @@ class TestIdentificationStage(TestCase): name="identification", user_fields=[UserFields.E_MAIL], ) + self.stage.sources.set([source]) + self.stage.save() FlowStageBinding.objects.create( target=self.flow, stage=self.stage, order=0, ) - # OAuthSource for the login view - OAuthSource.objects.create(name="test", slug="test") - def test_valid_render(self): """Test that View renders correctly""" response = self.client.get( diff --git a/schema.yml b/schema.yml index f3fc073d5..852cbb2f8 100644 --- a/schema.yml +++ b/schema.yml @@ -17489,6 +17489,12 @@ components: nullable: true description: Optional recovery flow, which is linked at the bottom of the page. + sources: + type: array + items: + type: string + format: uuid + description: Specify which sources should be shown. required: - component - name @@ -17531,6 +17537,12 @@ components: nullable: true description: Optional recovery flow, which is linked at the bottom of the page. + sources: + type: array + items: + type: string + format: uuid + description: Specify which sources should be shown. required: - name IntentEnum: @@ -21952,6 +21964,12 @@ components: nullable: true description: Optional recovery flow, which is linked at the bottom of the page. + sources: + type: array + items: + type: string + format: uuid + description: Specify which sources should be shown. PatchedInvitationRequest: type: object description: Invitation Serializer diff --git a/web/src/pages/stages/identification/IdentificationStageForm.ts b/web/src/pages/stages/identification/IdentificationStageForm.ts index 05fa1d90c..9efcc8dd0 100644 --- a/web/src/pages/stages/identification/IdentificationStageForm.ts +++ b/web/src/pages/stages/identification/IdentificationStageForm.ts @@ -1,4 +1,4 @@ -import { FlowsApi, IdentificationStage, UserFieldsEnum, StagesApi, FlowsInstancesListDesignationEnum } from "authentik-api"; +import { FlowsApi, IdentificationStage, UserFieldsEnum, StagesApi, FlowsInstancesListDesignationEnum, SourcesApi } from "authentik-api"; import { t } from "@lingui/macro"; import { customElement } from "lit-element"; import { html, TemplateResult } from "lit-html"; @@ -85,6 +85,23 @@ export class IdentificationStageForm extends ModelForm

${t`When enabled, user fields are matched regardless of their casing.`}

+ + +

${t`Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP.`}

+

${t`Hold control/command to select multiple items.`}

+
diff --git a/website/docs/releases/next.md b/website/docs/releases/next.md new file mode 100644 index 000000000..126adba27 --- /dev/null +++ b/website/docs/releases/next.md @@ -0,0 +1,27 @@ +--- +title: Next +--- + +## Headline Changes + +- Duo two-factor support + + You can now add the new `authenticator_duo` stage to configure Duo authenticators. Duo has also been added as device class to the `authenticator_validation` stage. + + Currently, only Duo push notifications are supported. Because no additional input is required, Duo also works with the LDAP Outpost. + +## Minor changes + +- You can now specify which sources should be shown on an Identification stage. + +## Upgrading + +This release does not introduce any new requirements. + +### docker-compose + +Download the docker-compose file for 2021.6 from [here](https://raw.githubusercontent.com/goauthentik/authentik/version-2021.6/docker-compose.yml). Afterwards, simply run `docker-compose up -d`. + +### Kubernetes + +Upgrade to the latest chart version to get the new images.