stages/authenticator_webauthn: fix incorrect response being sent

This commit is contained in:
Jens Langhammer 2021-02-22 19:54:05 +01:00
parent 388c8c8bec
commit 451c117ea4
8 changed files with 161 additions and 9 deletions

View File

@ -0,0 +1,86 @@
# Generated by Django 3.1.6 on 2021-02-22 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0009_auto_20210215_2159"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.events", "authentik Events"),
("authentik.crypto", "authentik Crypto"),
("authentik.flows", "authentik Flows"),
("authentik.outposts", "authentik Outpost"),
("authentik.lib", "authentik lib"),
("authentik.policies", "authentik Policies"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
(
"authentik.policies.event_matcher",
"authentik Policies.Event Matcher",
),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
(
"authentik.policies.group_membership",
"authentik Policies.Group Membership",
),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.saml", "authentik Sources.SAML"),
(
"authentik.stages.authenticator_static",
"authentik Stages.Authenticator.Static",
),
(
"authentik.stages.authenticator_totp",
"authentik Stages.Authenticator.TOTP",
),
(
"authentik.stages.authenticator_validate",
"authentik Stages.Authenticator.Validate",
),
(
"authentik.stages.authenticator_webauthn",
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
(
"authentik.stages.identification",
"authentik Stages.Identification",
),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.managed", "authentik Managed"),
("authentik.core", "authentik Core"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View File

@ -22,7 +22,7 @@ def create_default_setup_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEdito
designation=FlowDesignation.STAGE_CONFIGURATION,
defaults={
"name": "default-authenticator-webuahtn-setup",
"title": "Setup Static OTP Tokens",
"title": "Setup WebAuthn",
},
)

View File

@ -0,0 +1,20 @@
# Generated by Django 3.1.6 on 2021-02-22 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_webauthn", "0002_default_setup_flow"),
]
operations = [
migrations.AddField(
model_name="webauthndevice",
name="confirmed",
field=models.BooleanField(
default=True, help_text="Is this device ready for use?"
),
),
]

View File

@ -8,6 +8,7 @@ from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views import View
from django_otp.models import Device
from rest_framework.serializers import BaseSerializer
from authentik.flows.models import ConfigurableStage, Stage
@ -56,7 +57,7 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
verbose_name_plural = _("WebAuthn Authenticator Setup Stages")
class WebAuthnDevice(models.Model):
class WebAuthnDevice(Device):
"""WebAuthn Device for a single user"""
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)

View File

@ -7,6 +7,7 @@ from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
from webauthn.webauthn import (
RegistrationRejectedException,
WebAuthnCredential,
WebAuthnMakeCredentialOptions,
WebAuthnRegistrationResponse,
)
@ -87,7 +88,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
)
webauthn_credential.public_key = str(webauthn_credential.public_key, "utf-8")
return webauthn_registration_response
return webauthn_credential
class AuthenticatorWebAuthnStageView(ChallengeStageView):
@ -145,7 +146,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
# Webauthn Challenge has already been validated
webauthn_credential = response.validated_data["response"]
webauthn_credential: WebAuthnCredential = response.validated_data["response"]
existing_device = WebAuthnDevice.objects.filter(
credential_id=webauthn_credential.credential_id
).first()

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.6 on 2021-02-22 18:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_prompt", "0002_auto_20200920_1859"),
]
operations = [
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text: Simple Text input"),
(
"username",
"Username: Same as Text input, but checks for and prevents duplicate usernames.",
),
("email", "Email: Text field with Email type."),
(
"password",
"Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical.",
),
("number", "Number"),
("checkbox", "Checkbox"),
("date", "Date"),
("date-time", "Date Time"),
("separator", "Separator: Static Separator Line"),
(
"hidden",
"Hidden: Hidden field, can be used to insert data into form.",
),
("static", "Static: Static value, displayed as-is."),
],
max_length=100,
),
),
]

View File

@ -57,10 +57,9 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
// post the transformed credential data to the server for validation
// and storing the public key
try {
const response = <WebAuthnAuthenticatorRegisterChallengeResponse>{
response: newAssertionForServer
};
await this.host?.submit(JSON.stringify(response));
const formData = new FormData();
formData.set("response", JSON.stringify(newAssertionForServer))
await this.host?.submit(formData);
} catch (err) {
throw new Error(gettext(`Server validation of credential failed: ${err}`));
}
@ -100,6 +99,9 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage {
</div>`:
html`
<div class="pf-c-form__group pf-m-action">
${this.challenge?.response_errors ?
html`<p class="pf-m-block">${this.challenge.response_errors["response"][0].string}</p>`:
html``}
<p class="pf-m-block">${this.registerMessage}</p>
<button class="pf-c-button pf-m-primary pf-m-block" @click=${() => {
this.registerWrapper();

View File

@ -63,7 +63,7 @@ export class FlowExecutor extends LitElement {
});
}
submit(formData?: string | FormData): Promise<void> {
submit(formData?: FormData): Promise<void> {
const csrftoken = getCookie("authentik_csrf");
const request = new Request(DefaultClient.makeUrl(["flows", "executor", this.flowSlug]), {
headers: {