stages/authenticator_webauthn: make more WebAuthn options configurable

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-01-12 22:57:49 +01:00
parent 4d7d700afa
commit e758db5727
10 changed files with 329 additions and 3 deletions

View file

@ -18,7 +18,12 @@ class AuthenticateWebAuthnStageSerializer(StageSerializer):
class Meta:
model = AuthenticateWebAuthnStage
fields = StageSerializer.Meta.fields + ["configure_flow", "user_verification"]
fields = StageSerializer.Meta.fields + [
"configure_flow",
"user_verification",
"authenticator_attachment",
"resident_key_requirement",
]
class AuthenticateWebAuthnStageViewSet(UsedByMixin, ModelViewSet):

View file

@ -0,0 +1,37 @@
# Generated by Django 4.0.1 on 2022-01-12 21:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"authentik_stages_authenticator_webauthn",
"0005_authenticatewebauthnstage_user_verification",
),
]
operations = [
migrations.AddField(
model_name="authenticatewebauthnstage",
name="authenticator_attachment",
field=models.TextField(
choices=[("platform", "Platform"), ("cross-platform", "Cross Platform")],
default=None,
null=True,
),
),
migrations.AddField(
model_name="authenticatewebauthnstage",
name="resident_key_requirement",
field=models.TextField(
choices=[
("discouraged", "Discouraged"),
("preferred", "Preferred"),
("required", "Required"),
],
default="preferred",
),
),
]

View file

@ -31,6 +31,40 @@ class UserVerification(models.TextChoices):
DISCOURAGED = "discouraged"
class ResidentKeyRequirement(models.TextChoices):
"""The Relying Party's preference for the authenticator to create a dedicated "client-side"
credential for it. Requiring an authenticator to store a dedicated credential should not be
done lightly due to the limited storage capacity of some types of authenticators.
Members:
`DISCOURAGED`: The authenticator should not create a dedicated credential
`PREFERRED`: The authenticator can create and store a dedicated credential, but if it
doesn't that's alright too
`REQUIRED`: The authenticator MUST create a dedicated credential. If it cannot, the RP
is prepared for an error to occur.
https://www.w3.org/TR/webauthn-2/#enum-residentKeyRequirement
"""
DISCOURAGED = "discouraged"
PREFERRED = "preferred"
REQUIRED = "required"
class AuthenticatorAttachment(models.TextChoices):
"""How an authenticator is connected to the client/browser.
Members:
`PLATFORM`: A non-removable authenticator, like TouchID or Windows Hello
`CROSS_PLATFORM`: A "roaming" authenticator, like a YubiKey
https://www.w3.org/TR/webauthn-2/#enumdef-authenticatorattachment
"""
PLATFORM = "platform"
CROSS_PLATFORM = "cross-platform"
class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
"""WebAuthn stage"""
@ -38,6 +72,13 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
choices=UserVerification.choices,
default=UserVerification.PREFERRED,
)
resident_key_requirement = models.TextField(
choices=ResidentKeyRequirement.choices,
default=ResidentKeyRequirement.PREFERRED,
)
authenticator_attachment = models.TextField(
choices=AuthenticatorAttachment.choices, default=None, null=True
)
@property
def serializer(self) -> BaseSerializer:

View file

@ -10,6 +10,7 @@ from webauthn import generate_registration_options, options_to_json, verify_regi
from webauthn.helpers import bytes_to_base64url
from webauthn.helpers.exceptions import InvalidRegistrationResponse
from webauthn.helpers.structs import (
AuthenticatorAttachment,
AuthenticatorSelectionCriteria,
PublicKeyCredentialCreationOptions,
RegistrationCredential,
@ -85,6 +86,12 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
stage: AuthenticateWebAuthnStage = self.executor.current_stage
user = self.get_pending_user()
# library accepts none so we store null in the database, but if there is a value
# set, cast it to string to ensure it's not a django class
authenticator_attachment = stage.authenticator_attachment
if authenticator_attachment:
authenticator_attachment = str(authenticator_attachment)
registration_options: PublicKeyCredentialCreationOptions = generate_registration_options(
rp_id=get_rp_id(self.request),
rp_name=self.request.tenant.branding_title,
@ -92,8 +99,9 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
user_name=user.username,
user_display_name=user.name,
authenticator_selection=AuthenticatorSelectionCriteria(
resident_key=ResidentKeyRequirement.PREFERRED,
resident_key=str(stage.resident_key_requirement),
user_verification=str(stage.user_verification),
authenticator_attachment=authenticator_attachment,
),
)

View file

@ -15270,6 +15270,14 @@ paths:
operationId: stages_authenticator_webauthn_list
description: AuthenticateWebAuthnStage Viewset
parameters:
- in: query
name: authenticator_attachment
schema:
type: string
nullable: true
enum:
- cross-platform
- platform
- in: query
name: configure_flow
schema:
@ -15297,6 +15305,14 @@ paths:
description: Number of results to return per page.
schema:
type: integer
- in: query
name: resident_key_requirement
schema:
type: string
enum:
- discouraged
- preferred
- required
- name: search
required: false
in: query
@ -19174,6 +19190,12 @@ components:
If empty, user will not be able to configure this stage.
user_verification:
$ref: '#/components/schemas/UserVerificationEnum'
authenticator_attachment:
allOf:
- $ref: '#/components/schemas/AuthenticatorAttachmentEnum'
nullable: true
resident_key_requirement:
$ref: '#/components/schemas/ResidentKeyRequirementEnum'
required:
- component
- meta_model_name
@ -19200,6 +19222,12 @@ components:
If empty, user will not be able to configure this stage.
user_verification:
$ref: '#/components/schemas/UserVerificationEnum'
authenticator_attachment:
allOf:
- $ref: '#/components/schemas/AuthenticatorAttachmentEnum'
nullable: true
resident_key_requirement:
$ref: '#/components/schemas/ResidentKeyRequirementEnum'
required:
- name
AuthenticatedSession:
@ -19288,6 +19316,11 @@ components:
- last_used
- user
- user_agent
AuthenticatorAttachmentEnum:
enum:
- platform
- cross-platform
type: string
AuthenticatorDuoChallenge:
type: object
description: Duo Challenge
@ -26519,6 +26552,12 @@ components:
If empty, user will not be able to configure this stage.
user_verification:
$ref: '#/components/schemas/UserVerificationEnum'
authenticator_attachment:
allOf:
- $ref: '#/components/schemas/AuthenticatorAttachmentEnum'
nullable: true
resident_key_requirement:
$ref: '#/components/schemas/ResidentKeyRequirementEnum'
PatchedAuthenticatorDuoStageRequest:
type: object
description: AuthenticatorDuoStage Serializer
@ -29376,6 +29415,12 @@ components:
type: integer
maximum: 2147483647
minimum: -2147483648
ResidentKeyRequirementEnum:
enum:
- discouraged
- preferred
- required
type: string
SAMLMetadata:
type: object
description: SAML Provider Metadata serializer

View file

@ -62,6 +62,10 @@ msgstr "6 digits, widely compatible"
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr "8 digits, not compatible with apps like Google Authenticator"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A \"roaming\" authenticator, like a YubiKey"
msgstr "A \"roaming\" authenticator, like a YubiKey"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr "A code has been sent to you via SMS."
@ -70,6 +74,10 @@ msgstr "A code has been sent to you via SMS."
msgid "A newer version of the frontend is available."
msgstr "A newer version of the frontend is available."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A non-removable authenticator, like TouchID or Windows Hello"
msgstr "A non-removable authenticator, like TouchID or Windows Hello"
#: src/pages/policies/dummy/DummyPolicyForm.ts
msgid "A policy used for testing. Always returns the same result as specified below after waiting a random duration."
msgstr "A policy used for testing. Always returns the same result as specified below after waiting a random duration."
@ -488,6 +496,10 @@ msgstr "Authentication flow"
msgid "Authenticator"
msgstr "Authenticator"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Authenticator Attachment"
msgstr "Authenticator Attachment"
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr "Authorization"
@ -3147,6 +3159,10 @@ msgstr "No objects found."
msgid "No policies are currently bound to this object."
msgstr "No policies are currently bound to this object."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "No preference is sent"
msgstr "No preference is sent"
#: src/pages/users/UserListPage.ts
msgid "No recovery flow is configured."
msgstr "No recovery flow is configured."
@ -3940,6 +3956,10 @@ msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgid "Reset Password"
msgstr "Reset Password"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Resident key requirement"
msgstr "Resident key requirement"
#: src/interfaces/AdminInterface.ts
#~ msgid "Resources"
#~ msgstr "Resources"
@ -5045,6 +5065,18 @@ msgstr "The Host IP of the docker host"
msgid "The URL \"{0}\" was not found."
msgstr "The URL \"{0}\" was not found."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur"
msgstr "The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator can create and store a dedicated credential, but if it doesn't that's alright too"
msgstr "The authenticator can create and store a dedicated credential, but if it doesn't that's alright too"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator should not create a dedicated credential"
msgstr "The authenticator should not create a dedicated credential"
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "The external URL you'll access the application at. Include any non-standard port."

View file

@ -68,6 +68,10 @@ msgstr "6 chiffres, compatibilité large"
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr "8 chiffres, incompatible avec certaines applications telles que Google Authenticator"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A \"roaming\" authenticator, like a YubiKey"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr ""
@ -76,6 +80,10 @@ msgstr ""
msgid "A newer version of the frontend is available."
msgstr "Une nouvelle version de l'interface est disponible."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A non-removable authenticator, like TouchID or Windows Hello"
msgstr ""
#: src/pages/policies/dummy/DummyPolicyForm.ts
msgid "A policy used for testing. Always returns the same result as specified below after waiting a random duration."
msgstr "Une politique utilisée pour les tests. Retourne toujours la même valeur telle qu'indiquée ci-dessous après une attente aléatoire."
@ -493,6 +501,10 @@ msgstr "Flux d'authentification"
msgid "Authenticator"
msgstr "Authentificateur"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Authenticator Attachment"
msgstr ""
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr "Authorisation"
@ -3126,6 +3138,10 @@ msgstr "Aucun objet trouvé."
msgid "No policies are currently bound to this object."
msgstr "Aucune politique n'est actuellement lié à cet objet."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "No preference is sent"
msgstr ""
#: src/pages/users/UserListPage.ts
msgid "No recovery flow is configured."
msgstr "Aucun flux de récupération n'est configuré."
@ -3912,6 +3928,10 @@ msgstr "Obligatoire. 150 caractères ou moins. Lettres, chiffres et @/./+/-/_ un
msgid "Reset Password"
msgstr "Réinitialiser le mot de passe"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Resident key requirement"
msgstr ""
#: src/interfaces/AdminInterface.ts
#~ msgid "Resources"
#~ msgstr "Ressources"
@ -5000,6 +5020,18 @@ msgstr ""
msgid "The URL \"{0}\" was not found."
msgstr "L'URL \"{0}\" est introuvable."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator can create and store a dedicated credential, but if it doesn't that's alright too"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator should not create a dedicated credential"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "The external URL you'll access the application at. Include any non-standard port."

View file

@ -62,6 +62,10 @@ msgstr ""
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A \"roaming\" authenticator, like a YubiKey"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr ""
@ -70,6 +74,10 @@ msgstr ""
msgid "A newer version of the frontend is available."
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A non-removable authenticator, like TouchID or Windows Hello"
msgstr ""
#: src/pages/policies/dummy/DummyPolicyForm.ts
msgid "A policy used for testing. Always returns the same result as specified below after waiting a random duration."
msgstr ""
@ -484,6 +492,10 @@ msgstr ""
msgid "Authenticator"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Authenticator Attachment"
msgstr ""
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr ""
@ -3137,6 +3149,10 @@ msgstr ""
msgid "No policies are currently bound to this object."
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "No preference is sent"
msgstr ""
#: src/pages/users/UserListPage.ts
msgid "No recovery flow is configured."
msgstr ""
@ -3930,6 +3946,10 @@ msgstr ""
msgid "Reset Password"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Resident key requirement"
msgstr ""
#: src/interfaces/AdminInterface.ts
#~ msgid "Resources"
#~ msgstr ""
@ -5035,6 +5055,18 @@ msgstr ""
msgid "The URL \"{0}\" was not found."
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator can create and store a dedicated credential, but if it doesn't that's alright too"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator should not create a dedicated credential"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "The external URL you'll access the application at. Include any non-standard port."

View file

@ -65,6 +65,10 @@ msgstr "6 basamaklı, yaygın olarak uyumlu"
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr "Google Authenticator gibi uygulamalarla uyumlu olmayan 8 haneli"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A \"roaming\" authenticator, like a YubiKey"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr "SMS ile size bir kod gönderildi."
@ -73,6 +77,10 @@ msgstr "SMS ile size bir kod gönderildi."
msgid "A newer version of the frontend is available."
msgstr "Ön yüzün daha yeni bir sürümü mevcuttur."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "A non-removable authenticator, like TouchID or Windows Hello"
msgstr ""
#: src/pages/policies/dummy/DummyPolicyForm.ts
msgid "A policy used for testing. Always returns the same result as specified below after waiting a random duration."
msgstr "Test için kullanılan bir ilke. Her zaman rastgele bir süre bekledikten sonra aşağıda belirtilen sonucu döndürür."
@ -487,6 +495,10 @@ msgstr "Kimlik doğrulama akışı"
msgid "Authenticator"
msgstr "Kimlik Doğrulayıcı"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Authenticator Attachment"
msgstr ""
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr "Yetkilendirme"
@ -3096,6 +3108,10 @@ msgstr "Nesne bulunamadı."
msgid "No policies are currently bound to this object."
msgstr "Hiçbir ilke şu anda bu nesneye bağlı değildir."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "No preference is sent"
msgstr ""
#: src/pages/users/UserListPage.ts
msgid "No recovery flow is configured."
msgstr "Kurtarma akışı yapılandırılmamış."
@ -3867,6 +3883,10 @@ msgstr "Gerekli. 150 karakter veya daha az. Harfler, rakamlar ve yalnızca @/./+
msgid "Reset Password"
msgstr "Parolayı Sıfırla"
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "Resident key requirement"
msgstr ""
#~ msgid "Resources"
#~ msgstr "Kaynaklar"
@ -4938,6 +4958,18 @@ msgstr "Docker ana bilgisayarının Ana Bilgisayar IP'si"
msgid "The URL \"{0}\" was not found."
msgstr "“{0}” URL'si bulunamadı."
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator can create and store a dedicated credential, but if it doesn't that's alright too"
msgstr ""
#: src/pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts
msgid "The authenticator should not create a dedicated credential"
msgstr ""
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
msgid "The external URL you'll access the application at. Include any non-standard port."

View file

@ -9,8 +9,10 @@ import { until } from "lit/directives/until.js";
import {
AuthenticateWebAuthnStage,
AuthenticatorAttachmentEnum,
FlowsApi,
FlowsInstancesListDesignationEnum,
ResidentKeyRequirementEnum,
StagesApi,
} from "@goauthentik/api";
@ -35,6 +37,9 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth
}
send = (data: AuthenticateWebAuthnStage): Promise<AuthenticateWebAuthnStage> => {
if (data.authenticatorAttachment?.toString() === "") {
data.authenticatorAttachment = null;
}
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorWebauthnUpdate({
stageUuid: this.instance.pk || "",
@ -68,7 +73,7 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth
?required=${true}
name="userVerification"
>
<select name="users" class="pf-c-form-control">
<select class="pf-c-form-control">
<option
value="${UserVerificationEnum.Required}"
?selected=${this.instance?.userVerification ===
@ -92,6 +97,63 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Resident key requirement`}
?required=${true}
name="residentKeyRequirement"
>
<select class="pf-c-form-control">
<option
value="${ResidentKeyRequirementEnum.Discouraged}"
?selected=${this.instance?.residentKeyRequirement ===
ResidentKeyRequirementEnum.Discouraged}
>
${t`The authenticator should not create a dedicated credential`}
</option>
<option
value="${ResidentKeyRequirementEnum.Preferred}"
?selected=${this.instance?.residentKeyRequirement ===
ResidentKeyRequirementEnum.Preferred}
>
${t`The authenticator can create and store a dedicated credential, but if it doesn't that's alright too`}
</option>
<option
value="${ResidentKeyRequirementEnum.Required}"
?selected=${this.instance?.residentKeyRequirement ===
ResidentKeyRequirementEnum.Required}
>
${t`The authenticator MUST create a dedicated credential. If it cannot, the RP is prepared for an error to occur`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Authenticator Attachment`}
?required=${true}
name="authenticatorAttachment"
>
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.authenticatorAttachment === null}
>
${t`No preference is sent`}
</option>
<option
value="${AuthenticatorAttachmentEnum.Platform}"
?selected=${this.instance?.authenticatorAttachment ===
AuthenticatorAttachmentEnum.Platform}
>
${t`A non-removable authenticator, like TouchID or Windows Hello`}
</option>
<option
value="${AuthenticatorAttachmentEnum.CrossPlatform}"
?selected=${this.instance?.authenticatorAttachment ===
AuthenticatorAttachmentEnum.CrossPlatform}
>
${t`A "roaming" authenticator, like a YubiKey`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control">
<option