stages/authenticator_validate: add ability to select multiple configuration stages which the user can choose

closes #1843

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-02-12 16:55:50 +01:00
parent 9070df6c26
commit 2ccab75021
16 changed files with 346 additions and 102 deletions

View file

@ -13,8 +13,8 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
def validate_not_configured_action(self, value):
"""Ensure that a configuration stage is set when not_configured_action is configure"""
configuration_stage = self.initial_data.get("configuration_stage")
if value == NotConfiguredAction.CONFIGURE and configuration_stage is None:
configuration_stages = self.initial_data.get("configuration_stages")
if value == NotConfiguredAction.CONFIGURE and configuration_stages is None:
raise ValidationError(
(
'When "Not configured action" is set to "Configure", '
@ -29,7 +29,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
fields = StageSerializer.Meta.fields + [
"not_configured_action",
"device_classes",
"configuration_stage",
"configuration_stages",
]
@ -38,5 +38,5 @@ class AuthenticatorValidateStageViewSet(UsedByMixin, ModelViewSet):
queryset = AuthenticatorValidateStage.objects.all()
serializer_class = AuthenticatorValidateStageSerializer
filterset_fields = ["name", "not_configured_action", "configuration_stage"]
filterset_fields = ["name", "not_configured_action", "configuration_stages"]
ordering = ["name"]

View file

@ -0,0 +1,44 @@
# Generated by Django 4.0.1 on 2022-01-05 22:09
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_configuration_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
AuthenticatorValidateStage = apps.get_model(
"authentik_stages_authenticator_validate", "AuthenticatorValidateStage"
)
for stage in AuthenticatorValidateStage.objects.using(db_alias).all():
if stage.configuration_stage:
stage.configuration_stages.set([stage.configuration_stage])
stage.save()
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0021_auto_20211227_2103"),
("authentik_stages_authenticator_validate", "0009_default_stage"),
]
operations = [
migrations.AddField(
model_name="authenticatorvalidatestage",
name="configuration_stages",
field=models.ManyToManyField(
blank=True,
default=None,
help_text="Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.",
related_name="+",
to="authentik_flows.Stage",
),
),
migrations.RunPython(migrate_configuration_stage),
migrations.RemoveField(
model_name="authenticatorvalidatestage",
name="configuration_stage",
),
]

View file

@ -38,16 +38,14 @@ class AuthenticatorValidateStage(Stage):
choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP
)
configuration_stage = models.ForeignKey(
configuration_stages = models.ManyToManyField(
Stage,
null=True,
blank=True,
default=None,
on_delete=models.SET_DEFAULT,
related_name="+",
help_text=_(
(
"Stage used to configure Authenticator when user doesn't have any compatible "
"Stages used to configure Authenticator when user doesn't have any compatible "
"devices. After this configuration Stage passes, the user is not prompted again."
)
),

View file

@ -1,10 +1,12 @@
"""Authenticator Validation"""
from django.http import HttpRequest, HttpResponse
from django_otp import devices_for_user
from rest_framework.fields import CharField, IntegerField, JSONField, ListField
from rest_framework.fields import CharField, IntegerField, JSONField, ListField, UUIDField
from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.events.utils import cleanse_dict, sanitize_dict
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
@ -26,6 +28,18 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
LOGGER = get_logger()
SESSION_STAGES = "goauthentik.io/stages/authenticator_validate/stages"
SESSION_SELECTED_STAGE = "goauthentik.io/stages/authenticator_validate/selected_stage"
SESSION_DEVICE_CHALLENGES = "goauthentik.io/stages/authenticator_validate/device_challenges"
class SelectableStageSerializer(PassiveSerializer):
"""Serializer for stages which can be selected by users"""
pk = UUIDField()
name = CharField()
verbose_name = CharField()
meta_model_name = CharField()
class AuthenticatorValidationChallenge(WithUserInfoChallenge):
@ -33,12 +47,14 @@ class AuthenticatorValidationChallenge(WithUserInfoChallenge):
device_challenges = ListField(child=DeviceChallenge())
component = CharField(default="ak-stage-authenticator-validate")
configuration_stages = ListField(child=SelectableStageSerializer())
class AuthenticatorValidationChallengeResponse(ChallengeResponse):
"""Challenge used for Code-based and WebAuthn authenticators"""
selected_challenge = DeviceChallenge(required=False)
selected_stage = CharField(required=False)
code = CharField(required=False)
webauthn = JSONField(required=False)
@ -84,6 +100,15 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
select_challenge(self.stage.request, devices.first())
return challenge
def validate_selected_stage(self, stage_pk: str) -> str:
"""Check that the selected stage is valid"""
stages = self.stage.request.session.get(SESSION_STAGES, [])
if not any(str(stage.pk) == stage_pk for stage in stages):
raise ValidationError("Selected stage is invalid")
LOGGER.debug("Setting selected stage to ", stage=stage_pk)
self.stage.request.session[SESSION_SELECTED_STAGE] = stage_pk
return stage_pk
def validate(self, attrs: dict):
# Checking if the given data is from a valid device class is done above
# Here we only check if the any data was sent at all
@ -164,7 +189,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
else:
LOGGER.debug("No pending user, continuing")
return self.executor.stage_ok()
self.request.session["device_challenges"] = challenges
self.request.session[SESSION_DEVICE_CHALLENGES] = challenges
# No allowed devices
if len(challenges) < 1:
@ -175,35 +200,71 @@ class AuthenticatorValidateStageView(ChallengeStageView):
LOGGER.debug("Authenticator not configured, denying")
return self.executor.stage_invalid()
if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
if not stage.configuration_stage:
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(
"Authenticator validation stage is set to configure user "
"but no configuration flow is set."
),
stage=self,
).from_http(self.request).set_user(user).save()
return self.executor.stage_invalid()
LOGGER.debug("Authenticator not configured, sending user to configure")
# Because the foreign key to stage.configuration_stage points to
# a base stage class, we need to do another lookup
stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk)
# plan.insert inserts at 1 index, so when stage_ok pops 0,
# the configuration stage is next
self.executor.plan.insert_stage(stage)
return self.executor.stage_ok()
LOGGER.debug("Authenticator not configured, forcing configure")
return self.prepare_stages(user)
return super().get(request, *args, **kwargs)
def get_challenge(self) -> AuthenticatorValidationChallenge:
challenges = self.request.session.get("device_challenges")
if not challenges:
LOGGER.debug("Authenticator Validation stage ran without challenges")
def prepare_stages(self, user: User, *args, **kwargs) -> HttpResponse:
"""Check how the user can configure themselves. If no stages are set, return an error.
If a single stage is set, insert that stage directly. If multiple are selected, include
them in the challenge."""
stage: AuthenticatorValidateStage = self.executor.current_stage
if not stage.configuration_stages.exists():
Event.new(
EventAction.CONFIGURATION_ERROR,
message=(
"Authenticator validation stage is set to configure user "
"but no configuration flow is set."
),
stage=self,
).from_http(self.request).set_user(user).save()
return self.executor.stage_invalid()
if stage.configuration_stages.count() == 1:
self.request.session[SESSION_SELECTED_STAGE] = stage.configuration_stages.first()
LOGGER.debug(
"Single stage configured, auto-selecting",
stage=self.request.session[SESSION_SELECTED_STAGE],
)
stages = Stage.objects.filter(pk__in=stage.configuration_stages.all()).select_subclasses()
self.request.session[SESSION_STAGES] = stages
return super().get(self.request, *args, **kwargs)
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
if (
SESSION_SELECTED_STAGE in self.request.session
and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
):
LOGGER.debug("Got selected stage in session, running that")
stage_pk = self.request.session.get(SESSION_SELECTED_STAGE)
# Because the foreign key to stage.configuration_stage points to
# a base stage class, we need to do another lookup
stage = Stage.objects.get_subclass(pk=stage_pk)
# plan.insert inserts at 1 index, so when stage_ok pops 0,
# the configuration stage is next
self.executor.plan.insert_stage(stage)
return self.executor.stage_ok()
return super().post(request, *args, **kwargs)
def get_challenge(self) -> AuthenticatorValidationChallenge:
challenges = self.request.session.get(SESSION_DEVICE_CHALLENGES, [])
stages = self.request.session.get(SESSION_STAGES, [])
stage_challenges = []
for stage in stages:
serializer = SelectableStageSerializer(
data={
"pk": stage.pk,
"name": stage.name,
"verbose_name": str(stage._meta.verbose_name),
"meta_model_name": f"{stage._meta.app_label}.{stage._meta.model_name}",
}
)
serializer.is_valid()
stage_challenges.append(serializer.data)
return AuthenticatorValidationChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"device_challenges": challenges,
"configuration_stages": stage_challenges,
}
)

View file

@ -43,8 +43,8 @@ class AuthenticatorValidateStageTests(FlowTestCase):
stage = AuthenticatorValidateStage.objects.create(
name="foo",
not_configured_action=NotConfiguredAction.CONFIGURE,
configuration_stage=conf_stage,
)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)

View file

@ -15045,10 +15045,14 @@ paths:
description: AuthenticatorValidateStage Viewset
parameters:
- in: query
name: configuration_stage
name: configuration_stages
schema:
type: string
format: uuid
type: array
items:
type: string
format: uuid
explode: true
style: form
- in: query
name: name
schema:
@ -19826,11 +19830,12 @@ components:
items:
$ref: '#/components/schemas/DeviceClassesEnum'
description: Device classes which can be used to authenticate
configuration_stage:
type: string
format: uuid
nullable: true
description: Stage used to configure Authenticator when user doesn't have
configuration_stages:
type: array
items:
type: string
format: uuid
description: Stages used to configure Authenticator when user doesn't have
any compatible devices. After this configuration Stage passes, the user
is not prompted again.
required:
@ -19858,11 +19863,12 @@ components:
items:
$ref: '#/components/schemas/DeviceClassesEnum'
description: Device classes which can be used to authenticate
configuration_stage:
type: string
format: uuid
nullable: true
description: Stage used to configure Authenticator when user doesn't have
configuration_stages:
type: array
items:
type: string
format: uuid
description: Stages used to configure Authenticator when user doesn't have
any compatible devices. After this configuration Stage passes, the user
is not prompted again.
required:
@ -19892,7 +19898,12 @@ components:
type: array
items:
$ref: '#/components/schemas/DeviceChallenge'
configuration_stages:
type: array
items:
$ref: '#/components/schemas/SelectableStage'
required:
- configuration_stages
- device_challenges
- pending_user
- pending_user_avatar
@ -19907,6 +19918,9 @@ components:
default: ak-stage-authenticator-validate
selected_challenge:
$ref: '#/components/schemas/DeviceChallengeRequest'
selected_stage:
type: string
minLength: 1
code:
type: string
minLength: 1
@ -26677,11 +26691,12 @@ components:
items:
$ref: '#/components/schemas/DeviceClassesEnum'
description: Device classes which can be used to authenticate
configuration_stage:
type: string
format: uuid
nullable: true
description: Stage used to configure Authenticator when user doesn't have
configuration_stages:
type: array
items:
type: string
format: uuid
description: Stages used to configure Authenticator when user doesn't have
any compatible devices. After this configuration Stage passes, the user
is not prompted again.
PatchedCaptchaStageRequest:
@ -30017,6 +30032,24 @@ components:
- direct
- cached
type: string
SelectableStage:
type: object
description: Serializer for stages which can be selected by users
properties:
pk:
type: string
format: uuid
name:
type: string
verbose_name:
type: string
meta_model_name:
type: string
required:
- meta_model_name
- name
- pk
- verbose_name
ServiceConnection:
type: object
description: ServiceConnection Serializer

View file

@ -67,7 +67,7 @@ export class AuthenticatorValidateStage
return this._selectedDeviceChallenge;
}
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<void> {
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<boolean> {
return this.host?.submit(payload) || Promise.resolve();
}
@ -140,7 +140,7 @@ export class AuthenticatorValidateStage
}
renderDevicePicker(): TemplateResult {
return html` <ul>
return html`<ul>
${this.challenge?.deviceChallenges.map((challenges) => {
return html`<li>
<button
@ -157,6 +157,30 @@ export class AuthenticatorValidateStage
</ul>`;
}
renderStagePicker(): TemplateResult {
return html`<ul>
${this.challenge?.configurationStages.map((stage) => {
return html`<li>
<button
class="pf-c-button authenticator-button"
type="button"
@click=${() => {
this.submit({
component: this.challenge.component || "",
selectedStage: stage.pk,
});
}}
>
<div class="right">
<p>${stage.name}</p>
<small>${stage.verboseName}</small>
</div>
</button>
</li>`;
})}
</ul>`;
}
renderDeviceChallenge(): TemplateResult {
if (!this.selectedDeviceChallenge) {
return html``;
@ -242,6 +266,9 @@ export class AuthenticatorValidateStage
${this.selectedDeviceChallenge
? ""
: html`<p>${t`Select an authentication method.`}</p>`}
${this.challenge.configurationStages.length > 0
? this.renderStagePicker()
: html``}
</form>
${this.renderDevicePicker()}
</div>

View file

@ -959,8 +959,12 @@ msgid "Configuration flow"
msgstr "Ablauf der Konfiguration"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "Konfiguration Stufe"
#~ msgid "Configuration stage"
#~ msgstr "Konfiguration Stufe"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "Konfiguriere WebAuthn"
@ -4410,8 +4414,8 @@ msgid "Stage type"
msgstr "Phasen Typ"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Phase zum Konfigurieren von Authenticator, wenn der Benutzer keine kompatiblen Geräte hat. Nach Ablauf dieser Konfigurationsphase wird der Benutzer nicht erneut aufgefordert."
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Phase zum Konfigurieren von Authenticator, wenn der Benutzer keine kompatiblen Geräte hat. Nach Ablauf dieser Konfigurationsphase wird der Benutzer nicht erneut aufgefordert."
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4470,6 +4474,10 @@ msgstr "Phasen"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "Phasen sind einzelne Schritte eines Flows, durch die ein Benutzer geführt wird. Eine Phase kann nur innerhalb eines Flows ausgeführt werden."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "Zustand"
@ -5935,6 +5943,10 @@ msgstr "Wenn diese Option aktiviert ist, wird die Einladung nach ihrer Benutzung
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "Wenn diese Option aktiviert ist, werden Benutzerfelder unabhängig von ihrem Format abgeglichen."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "Wenn diese Option ausgewählt ist, wird ein Passwortfeld auf derselben Seite statt auf einer separaten Seite angezeigt. Dadurch werden Angriffe auf die Aufzählung von Benutzernamen verhindert."

View file

@ -950,8 +950,12 @@ msgid "Configuration flow"
msgstr "Flujo de configuración"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "Etapa de configuración"
#~ msgid "Configuration stage"
#~ msgstr "Etapa de configuración"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "Configurar WebAuthn"
@ -4403,8 +4407,8 @@ msgid "Stage type"
msgstr "Tipo de escenario"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Etapa utilizada para configurar Authenticator cuando el usuario no tiene ningún dispositivo compatible. Una vez superada esta etapa de configuración, no se volverá a preguntar al usuario."
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Etapa utilizada para configurar Authenticator cuando el usuario no tiene ningún dispositivo compatible. Una vez superada esta etapa de configuración, no se volverá a preguntar al usuario."
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4463,6 +4467,10 @@ msgstr "Etapas"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "Las etapas son pasos individuales de un flujo por los que se guía al usuario. Una etapa solo se puede ejecutar desde dentro de un flujo."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "Estado"
@ -5928,6 +5936,10 @@ msgstr "Cuando se habilita, la invitación se eliminará después de su uso."
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "Cuando se habilita, los campos de usuario coinciden independientemente de su carcasa."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "Cuando se selecciona, se muestra un campo de contraseña en la misma página en lugar de en una página separada. Esto evita ataques de enumeración de nombres de usuario."

View file

@ -947,8 +947,12 @@ msgid "Configuration flow"
msgstr "Przepływ konfiguracji"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "Etap konfiguracji"
#~ msgid "Configuration stage"
#~ msgstr "Etap konfiguracji"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "Skonfiguruj WebAuthn"
@ -4400,8 +4404,8 @@ msgid "Stage type"
msgstr "Typ etapu"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Etap używany do konfiguracji uwierzytelniacza, gdy użytkownik nie ma żadnych kompatybilnych urządzeń. Po zakończeniu tego etapu konfiguracji użytkownik nie jest ponownie pytany."
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Etap używany do konfiguracji uwierzytelniacza, gdy użytkownik nie ma żadnych kompatybilnych urządzeń. Po zakończeniu tego etapu konfiguracji użytkownik nie jest ponownie pytany."
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4460,6 +4464,10 @@ msgstr "Etapy"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "Etapy to pojedyncze kroki przepływu, przez które prowadzony jest użytkownik. Etap można wykonać tylko z przepływu."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "Stan"
@ -5925,6 +5933,10 @@ msgstr "Po włączeniu zaproszenie zostanie usunięte po użyciu."
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "Po włączeniu pola użytkownika są dopasowywane niezależnie od wielkości liter."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "Po wybraniu pole hasła jest wyświetlane na tej samej stronie zamiast na osobnej stronie. Zapobiega to atakom polegającym na wyliczaniu nazw użytkowników."

View file

@ -950,8 +950,12 @@ msgid "Configuration flow"
msgstr "Yapılandırma akışı"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "Yapılandırma aşamasında"
#~ msgid "Configuration stage"
#~ msgstr "Yapılandırma aşamasında"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "WebAuthn'i Yapılandır"
@ -4405,8 +4409,8 @@ msgid "Stage type"
msgstr "Aşama türü"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Kullanıcının uyumlu bir aygıtı olmadığında Kimlik Doğrulayıcısı'nı yapılandırmak için kullanılan Aşama. Bu yapılandırma Stage geçtikten sonra kullanıcıya yeniden istenmez."
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Kullanıcının uyumlu bir aygıtı olmadığında Kimlik Doğrulayıcısı'nı yapılandırmak için kullanılan Aşama. Bu yapılandırma Stage geçtikten sonra kullanıcıya yeniden istenmez."
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4465,6 +4469,10 @@ msgstr "Aşamalar"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "Aşamalar, bir Akış'ın kullanıcının yönlendirildiği tek adımlardır. Bir aşama yalnızca bir akış içinden yürütülebilir."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "Eyalet"
@ -5930,6 +5938,10 @@ msgstr "Etkinleştirildiğinde, davetiye kullanımdan sonra silinir."
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "Etkinleştirildiğinde, kullanıcı alanları muhafazası ne olursa olsun eşleştirilir."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "Seçildiğinde, ayrı bir sayfa yerine aynı sayfada bir parola alanı gösterilir. Bu, kullanıcı adı numaralandırma saldırılarını engeller."

View file

@ -948,8 +948,12 @@ msgid "Configuration flow"
msgstr "配置流程"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "配置阶段"
#~ msgid "Configuration stage"
#~ msgstr "配置阶段"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "配置 WebAuthn"
@ -4401,8 +4405,8 @@ msgid "Stage type"
msgstr "阶段类型"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Stage 用于在用户没有任何兼容设备时配置身份验证器。此配置 Stage 通过后,不会再次提示用户。"
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Stage 用于在用户没有任何兼容设备时配置身份验证器。此配置 Stage 通过后,不会再次提示用户。"
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4461,6 +4465,10 @@ msgstr "阶段"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "阶段是引导用户完成的流程的单个步骤。阶段只能在流程内部执行。"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "状态"
@ -5926,6 +5934,10 @@ msgstr "启用后,邀请将在使用后被删除。"
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "启用后,无论用户字段大小写如何,都将匹配用户字段。"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "选中后,密码字段将显示在同一页面上,而不是单独的页面上。这样可以防止用户名枚举攻击。"

View file

@ -948,8 +948,12 @@ msgid "Configuration flow"
msgstr "配置流程"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "配置阶段"
#~ msgid "Configuration stage"
#~ msgstr "配置阶段"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "配置 WebAuthn"
@ -4401,8 +4405,8 @@ msgid "Stage type"
msgstr "阶段类型"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Stage 用于在用户没有任何兼容设备时配置身份验证器。此配置 Stage 通过后,不会再次提示用户。"
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Stage 用于在用户没有任何兼容设备时配置身份验证器。此配置 Stage 通过后,不会再次提示用户。"
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4461,6 +4465,10 @@ msgstr "阶段"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "阶段是引导用户完成的流程的单个步骤。阶段只能在流程内部执行。"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "州"
@ -5926,6 +5934,10 @@ msgstr "启用后,邀请将在使用后被删除。"
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "启用后,无论用户字段大小写如何,都将匹配用户字段。"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "选中后,密码字段将显示在同一页面上,而不是单独的页面上。这样可以防止用户名枚举攻击。"

View file

@ -948,8 +948,12 @@ msgid "Configuration flow"
msgstr "配置流程"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stage"
msgstr "配置阶段"
#~ msgid "Configuration stage"
#~ msgstr "配置阶段"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Configuration stages"
msgstr ""
#~ msgid "Configure WebAuthn"
#~ msgstr "配置 WebAuthn"
@ -4401,8 +4405,8 @@ msgid "Stage type"
msgstr "阶段类型"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr "Stage 用于在用户没有任何兼容设备时配置身份验证器。此配置 Stage 通过后,不会再次提示用户。"
#~ msgid "Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
#~ msgstr "Stage 用于在用户没有任何兼容设备时配置身份验证器。此配置 Stage 通过后,不会再次提示用户。"
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Stage used to configure a TOTP authenticator (i.e. Authy/Google Authenticator)."
@ -4461,6 +4465,10 @@ msgstr "阶段"
msgid "Stages are single steps of a Flow that a user is guided through. A stage can only be executed from within a flow."
msgstr "阶段是引导用户完成的流程的单个步骤。阶段只能在流程内部执行。"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again."
msgstr ""
#: src/pages/outposts/ServiceConnectionListPage.ts
msgid "State"
msgstr "州"
@ -5926,6 +5934,10 @@ msgstr "启用后,邀请将在使用后被删除。"
msgid "When enabled, user fields are matched regardless of their casing."
msgstr "启用后,无论用户字段大小写如何,都将匹配用户字段。"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "When multiple stages are selected, the user can choose which one they want to enroll."
msgstr ""
#: src/pages/stages/identification/IdentificationStageForm.ts
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
msgstr "选中后,密码字段将显示在同一页面上,而不是单独的页面上。这样可以防止用户名枚举攻击。"

View file

@ -25,14 +25,14 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
stageUuid: pk,
})
.then((stage) => {
this.showConfigurationStage =
this.showConfigurationStages =
stage.notConfiguredAction === NotConfiguredActionEnum.Configure;
return stage;
});
}
@property({ type: Boolean })
showConfigurationStage = true;
showConfigurationStages = true;
getSuccessMessage(): string {
if (this.instance) {
@ -136,9 +136,9 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
target.selectedOptions[0].value ===
NotConfiguredActionEnum.Configure
) {
this.showConfigurationStage = true;
this.showConfigurationStages = true;
} else {
this.showConfigurationStage = false;
this.showConfigurationStages = false;
}
}}
>
@ -165,21 +165,13 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
</option>
</select>
</ak-form-element-horizontal>
${this.showConfigurationStage
${this.showConfigurationStages
? html`
<ak-form-element-horizontal
label=${t`Configuration stage`}
?required=${true}
name="configurationStage"
label=${t`Configuration stages`}
name="configurationStages"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.configurationStage ===
undefined}
>
---------
</option>
<select class="pf-c-form-control" multiple>
${until(
new StagesApi(DEFAULT_CONFIG)
.stagesAllList({
@ -187,9 +179,11 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
})
.then((stages) => {
return stages.results.map((stage) => {
const selected =
this.instance?.configurationStage ===
stage.pk;
const selected = Array.from(
this.instance?.configurationStages || [],
).some((su) => {
return su == stage.pk;
});
return html`<option
value=${ifDefined(stage.pk)}
?selected=${selected}
@ -202,7 +196,10 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
)}
</select>
<p class="pf-c-form__helper-text">
${t`Stage used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}
${t`Stages used to configure Authenticator when user doesn't have any compatible devices. After this configuration Stage passes, the user is not prompted again.`}
</p>
<p class="pf-c-form__helper-text">
${t`When multiple stages are selected, the user can choose which one they want to enroll.`}
</p>
</ak-form-element-horizontal>
`

View file

@ -153,7 +153,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
?required=${true}
name="sources"
>
<select name="users" class="pf-c-form-control" multiple>
<select class="pf-c-form-control" multiple>
${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesAllList({})