From 65522186f1f92a8a9ba22b0cb165a85ef1de9837 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 23 May 2021 21:44:52 +0200 Subject: [PATCH] stages/authenticator_duo: improve setup Signed-off-by: Jens Langhammer --- authentik/stages/authenticator_duo/api.py | 7 +++--- authentik/stages/authenticator_duo/models.py | 23 ++++++----------- authentik/stages/authenticator_duo/stage.py | 19 ++++++-------- .../stages/authenticator_webauthn/stage.py | 4 +-- .../AuthenticatorDuoStage.ts | 25 +++++++++++-------- web/src/flows/stages/captcha/CaptchaStage.ts | 10 +++----- 6 files changed, 37 insertions(+), 51 deletions(-) diff --git a/authentik/stages/authenticator_duo/api.py b/authentik/stages/authenticator_duo/api.py index 8af02e185..6b9c6b475 100644 --- a/authentik/stages/authenticator_duo/api.py +++ b/authentik/stages/authenticator_duo/api.py @@ -47,7 +47,7 @@ class AuthenticatorDuoStageViewSet(ModelViewSet): request=OpenApiTypes.NONE, responses={ 204: OpenApiResponse(description="Enrollment successful"), - 400: OpenApiResponse(description="Enrollment pending/failed"), + 420: OpenApiResponse(description="Enrollment pending/failed"), }, ) @action(methods=["POST"], detail=True, permission_classes=[]) @@ -57,10 +57,9 @@ class AuthenticatorDuoStageViewSet(ModelViewSet): user_id = self.request.session.get(SESSION_KEY_DUO_USER_ID) activation_code = self.request.session.get(SESSION_KEY_DUO_ACTIVATION_CODE) status = client.enroll_status(user_id, activation_code) - print(status) - if status["response"] == "success": + if status == "success": return Response(status=204) - return Response(status=400) + return Response(status=420) class DuoDeviceSerializer(ModelSerializer): diff --git a/authentik/stages/authenticator_duo/models.py b/authentik/stages/authenticator_duo/models.py index baa7e6c84..d0f9bbcb4 100644 --- a/authentik/stages/authenticator_duo/models.py +++ b/authentik/stages/authenticator_duo/models.py @@ -36,24 +36,15 @@ class AuthenticatorDuoStage(ConfigurableStage, Stage): return AuthenticatorDuoStageView - _client: Optional[Auth] = None - @property def client(self) -> Auth: - if not self._client: - self._client = Auth( - self.client_id, - self.client_secret, - self.api_hostname, - user_agent=f"authentik {__version__}", - ) - try: - self._client.ping() - except RuntimeError: - # Either allow login without 2FA, or abort the login process - # TODO: Define action when duo unavailable - raise - return self._client + client = Auth( + self.client_id, + self.client_secret, + self.api_hostname, + user_agent=f"authentik {__version__}", + ) + return client @property def component(self) -> str: diff --git a/authentik/stages/authenticator_duo/stage.py b/authentik/stages/authenticator_duo/stage.py index b3719e62c..a014e709c 100644 --- a/authentik/stages/authenticator_duo/stage.py +++ b/authentik/stages/authenticator_duo/stage.py @@ -1,13 +1,9 @@ """Duo stage""" from django.http import HttpRequest, HttpResponse -from django.http.request import QueryDict -from duo_client.auth import Auth -from rest_framework.fields import CharField, JSONField -from rest_framework.serializers import ValidationError +from rest_framework.fields import CharField from structlog.stdlib import get_logger -from authentik.core.models import User -from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes +from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes, WithUserInfoChallenge from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import ChallengeStageView from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice @@ -18,7 +14,7 @@ SESSION_KEY_DUO_USER_ID = "authentik_stages_authenticator_duo_user_id" SESSION_KEY_DUO_ACTIVATION_CODE = "authentik_stages_authenticator_duo_activation_code" -class AuthenticatorDuoChallenge(Challenge): +class AuthenticatorDuoChallenge(WithUserInfoChallenge): """Duo Challenge""" activation_barcode = CharField() @@ -60,13 +56,12 @@ class AuthenticatorDuoStageView(ChallengeStageView): stage: AuthenticatorDuoStage = self.executor.current_stage user_id = self.request.session.get(SESSION_KEY_DUO_USER_ID) activation_code = self.request.session.get(SESSION_KEY_DUO_ACTIVATION_CODE) - enroll_status = stage.client.enroll_status(user_id, activation_code).get( - "response" - ) + enroll_status = stage.client.enroll_status(user_id, activation_code) if enroll_status != "success": - # TODO: Find a better response - return HttpResponse(status=503) + return HttpResponse(status=420) existing_device = DuoDevice.objects.filter(duo_user_id=user_id).first() + self.request.session.pop(SESSION_KEY_DUO_USER_ID) + self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE) if not existing_device: DuoDevice.objects.create( user=self.get_pending_user(), diff --git a/authentik/stages/authenticator_webauthn/stage.py b/authentik/stages/authenticator_webauthn/stage.py index 8da09c44a..0feb7713d 100644 --- a/authentik/stages/authenticator_webauthn/stage.py +++ b/authentik/stages/authenticator_webauthn/stage.py @@ -13,7 +13,7 @@ from webauthn.webauthn import ( ) from authentik.core.models import User -from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes +from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes, WithUserInfoChallenge from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import ChallengeStageView from authentik.stages.authenticator_webauthn.models import WebAuthnDevice @@ -32,7 +32,7 @@ SESSION_KEY_WEBAUTHN_AUTHENTICATED = ( ) -class AuthenticatorWebAuthnChallenge(Challenge): +class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge): """WebAuthn Challenge""" registration = JSONField() diff --git a/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts b/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts index b261d4705..ce249d95c 100644 --- a/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts +++ b/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts @@ -34,16 +34,19 @@ export class AuthenticatorDuoStage extends BaseStage { firstUpdated(): void { const i = setInterval(() => { - new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoEnrollmentStatusCreate({ - stageUuid: this.challenge?.stage_uuid || "", - }).then(r => { - console.log("success"); + this.checkEnrollStatus().then(() => { clearInterval(i); - this.host?.submit(new FormData()); - }).catch(e => { - console.log("error"); }); - }, 500); + }, 3000); + } + + checkEnrollStatus(): Promise { + return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoEnrollmentStatusCreate({ + stageUuid: this.challenge?.stage_uuid || "", + }).then(r => { + this.host?.submit({}); + }).catch(e => { + }); } render(): TemplateResult { @@ -75,8 +78,10 @@ export class AuthenticatorDuoStage extends BaseStage { ${t`Duo activation`}
-
diff --git a/web/src/flows/stages/captcha/CaptchaStage.ts b/web/src/flows/stages/captcha/CaptchaStage.ts index e622ee661..1adab3dec 100644 --- a/web/src/flows/stages/captcha/CaptchaStage.ts +++ b/web/src/flows/stages/captcha/CaptchaStage.ts @@ -29,12 +29,6 @@ export class CaptchaStage extends BaseStage { return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal]; } - submitFormAlt(token: string): void { - const form = new FormData(); - form.set("token", token); - this.host?.submit(form); - } - firstUpdated(): void { const script = document.createElement("script"); script.src = "https://www.google.com/recaptcha/api.js"; @@ -50,7 +44,9 @@ export class CaptchaStage extends BaseStage { const captchaId = grecaptcha.render(captchaContainer, { sitekey: this.challenge.site_key, callback: (token) => { - this.submitFormAlt(token); + this.host?.submit({ + "token": token, + }); }, size: "invisible", });