From a9bee998f2ad62c46cd874a49bec0899620396c6 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 3 Apr 2021 00:22:23 +0200 Subject: [PATCH] stages/password: migrate to web Signed-off-by: Jens Langhammer --- authentik/stages/password/forms.py | 39 ------ .../migrations/0005_auto_20210402_2221.py | 31 +++++ authentik/stages/password/models.py | 22 +++- swagger.yaml | 4 +- .../identification/IdentificationStageForm.ts | 2 +- .../stages/password/PasswordStageForm.ts | 114 ++++++++++++++++++ 6 files changed, 166 insertions(+), 46 deletions(-) delete mode 100644 authentik/stages/password/forms.py create mode 100644 authentik/stages/password/migrations/0005_auto_20210402_2221.py create mode 100644 web/src/pages/stages/password/PasswordStageForm.ts diff --git a/authentik/stages/password/forms.py b/authentik/stages/password/forms.py deleted file mode 100644 index 5fba58db9..000000000 --- a/authentik/stages/password/forms.py +++ /dev/null @@ -1,39 +0,0 @@ -"""authentik administration forms""" -from django import forms -from django.utils.translation import gettext_lazy as _ - -from authentik.flows.models import Flow, FlowDesignation -from authentik.stages.password.models import PasswordStage - - -def get_authentication_backends(): - """Return all available authentication backends as tuple set""" - return [ - ( - "django.contrib.auth.backends.ModelBackend", - _("authentik-internal Userdatabase"), - ), - ( - "authentik.sources.ldap.auth.LDAPBackend", - _("authentik LDAP"), - ), - ] - - -class PasswordStageForm(forms.ModelForm): - """Form to create/edit Password Stages""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["configure_flow"].queryset = Flow.objects.filter( - designation=FlowDesignation.STAGE_CONFIGURATION - ) - - class Meta: - - model = PasswordStage - fields = ["name", "backends", "configure_flow", "failed_attempts_before_cancel"] - widgets = { - "name": forms.TextInput(), - "backends": forms.SelectMultiple(choices=get_authentication_backends()), - } diff --git a/authentik/stages/password/migrations/0005_auto_20210402_2221.py b/authentik/stages/password/migrations/0005_auto_20210402_2221.py new file mode 100644 index 000000000..d88061dd3 --- /dev/null +++ b/authentik/stages/password/migrations/0005_auto_20210402_2221.py @@ -0,0 +1,31 @@ +# Generated by Django 3.1.7 on 2021-04-02 22:21 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_stages_password", "0004_auto_20200925_1057"), + ] + + operations = [ + migrations.AlterField( + model_name="passwordstage", + name="backends", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.TextField( + choices=[ + ( + "django.contrib.auth.backends.ModelBackend", + "authentik-internal Userdatabase", + ), + ("authentik.sources.ldap.auth.LDAPBackend", "authentik LDAP"), + ] + ), + help_text="Selection of backends to test the password against.", + size=None, + ), + ), + ] diff --git a/authentik/stages/password/models.py b/authentik/stages/password/models.py index a4c8619fc..c3db3b174 100644 --- a/authentik/stages/password/models.py +++ b/authentik/stages/password/models.py @@ -12,11 +12,25 @@ from authentik.core.types import UserSettingSerializer from authentik.flows.models import ConfigurableStage, Stage +def get_authentication_backends(): + """Return all available authentication backends as tuple set""" + return [ + ( + "django.contrib.auth.backends.ModelBackend", + _("authentik-internal Userdatabase"), + ), + ( + "authentik.sources.ldap.auth.LDAPBackend", + _("authentik LDAP"), + ), + ] + + class PasswordStage(ConfigurableStage, Stage): """Prompts the user for their password, and validates it against the configured backends.""" backends = ArrayField( - models.TextField(), + models.TextField(choices=get_authentication_backends()), help_text=_("Selection of backends to test the password against."), ) failed_attempts_before_cancel = models.IntegerField( @@ -42,10 +56,8 @@ class PasswordStage(ConfigurableStage, Stage): return PasswordStageView @property - def form(self) -> Type[ModelForm]: - from authentik.stages.password.forms import PasswordStageForm - - return PasswordStageForm + def component(self) -> str: + return "ak-stage-password-form" @property def ui_user_settings(self) -> Optional[UserSettingSerializer]: diff --git a/swagger.yaml b/swagger.yaml index d34490fb1..567fb171e 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -18049,7 +18049,9 @@ definitions: items: title: Backends type: string - minLength: 1 + enum: + - django.contrib.auth.backends.ModelBackend + - authentik.sources.ldap.auth.LDAPBackend configure_flow: title: Configure flow description: Flow used by an authenticated user to configure this Stage. If diff --git a/web/src/pages/stages/identification/IdentificationStageForm.ts b/web/src/pages/stages/identification/IdentificationStageForm.ts index 28501c164..504413bb1 100644 --- a/web/src/pages/stages/identification/IdentificationStageForm.ts +++ b/web/src/pages/stages/identification/IdentificationStageForm.ts @@ -112,7 +112,7 @@ export class IdentificationStageForm extends Form { }); }))} -

${gettext("Optional enrollment flow, which is linked at the bottom of the page..")}

+

${gettext("Optional enrollment flow, which is linked at the bottom of the page.")}

{ + + set stageUUID(value: string) { + new StagesApi(DEFAULT_CONFIG).stagesPasswordRead({ + stageUuid: value, + }).then(stage => { + this.stage = stage; + }); + } + + @property({attribute: false}) + stage?: PasswordStage; + + getSuccessMessage(): string { + if (this.stage) { + return gettext("Successfully updated stage."); + } else { + return gettext("Successfully created stage."); + } + } + + send = (data: PasswordStage): Promise => { + if (this.stage) { + return new StagesApi(DEFAULT_CONFIG).stagesPasswordUpdate({ + stageUuid: this.stage.pk || "", + data: data + }); + } else { + return new StagesApi(DEFAULT_CONFIG).stagesPasswordCreate({ + data: data + }); + } + }; + + isBackendSelected(field: PasswordStageBackendsEnum): boolean { + return (this.stage?.backends || []).filter(isField => { + return field === isField; + }).length > 0; + } + + renderForm(): TemplateResult { + return html`
+ + + + + + ${gettext("Stage-specific settings")} + +
+ + +

${gettext("Selection of backends to test the password against.")}

+

${gettext("Hold control/command to select multiple items.")}

+
+ + +

${gettext("Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password.")}

+
+ + +

${gettext("How many attempts a user has before the flow is canceled. To lock the user out, use a reputation policy and a user_write stage.")}

+
+
+
+
`; + } + +}