From 7c191b0984792c8e6d9378cfca65acd95a128ea0 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 30 Jun 2020 12:42:12 +0200 Subject: [PATCH] stages/otp_validate: Implement OTP Validation stage --- passbook/stages/otp_static/apps.py | 2 ++ passbook/stages/otp_validate/apps.py | 2 ++ passbook/stages/otp_validate/forms.py | 19 +++++++++++++++++- passbook/stages/otp_validate/models.py | 1 + passbook/stages/otp_validate/settings.py | 1 + passbook/stages/otp_validate/stage.py | 25 ++++++++++++------------ 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/passbook/stages/otp_static/apps.py b/passbook/stages/otp_static/apps.py index fe31c592e..31ef75c51 100644 --- a/passbook/stages/otp_static/apps.py +++ b/passbook/stages/otp_static/apps.py @@ -1,7 +1,9 @@ +"""OTP Static stage""" from django.apps import AppConfig class PassbookStageOTPStaticConfig(AppConfig): + """OTP Static stage""" name = "passbook.stages.otp_static" label = "passbook_stages_otp_static" diff --git a/passbook/stages/otp_validate/apps.py b/passbook/stages/otp_validate/apps.py index 0973a5a1c..761d24ec2 100644 --- a/passbook/stages/otp_validate/apps.py +++ b/passbook/stages/otp_validate/apps.py @@ -1,7 +1,9 @@ +"""OTP Validation Stage""" from django.apps import AppConfig class PassbookStageOTPValidateConfig(AppConfig): + """OTP Validation Stage""" name = "passbook.stages.otp_validate" label = "passbook_stages_otp_validate" diff --git a/passbook/stages/otp_validate/forms.py b/passbook/stages/otp_validate/forms.py index ee4fe096b..44a1e1d09 100644 --- a/passbook/stages/otp_validate/forms.py +++ b/passbook/stages/otp_validate/forms.py @@ -1,7 +1,10 @@ +"""OTP Validate stage forms""" from django import forms from django.core.validators import RegexValidator from django.utils.translation import gettext_lazy as _ +from django_otp import match_token +from passbook.core.models import User from passbook.stages.otp_validate.models import OTPValidateStage OTP_CODE_VALIDATOR = RegexValidator( @@ -10,6 +13,9 @@ OTP_CODE_VALIDATOR = RegexValidator( class ValidationForm(forms.Form): + """OTP Validate stage forms""" + + user: User code = forms.CharField( label=_("Code"), @@ -23,11 +29,22 @@ class ValidationForm(forms.Form): ), ) + def __init__(self, user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + def clean_code(self): - pass + """Validate code against all confirmed devices""" + code = self.cleaned_data.get("code") + device = match_token(self.user, code) + if not device: + raise forms.ValidationError(_("Invalid Token")) + return code class OTPValidateStageForm(forms.ModelForm): + """OTP Validate stage forms""" + class Meta: model = OTPValidateStage diff --git a/passbook/stages/otp_validate/models.py b/passbook/stages/otp_validate/models.py index 990e0fb45..7e8ab7d38 100644 --- a/passbook/stages/otp_validate/models.py +++ b/passbook/stages/otp_validate/models.py @@ -1,3 +1,4 @@ +"""OTP Validation Stage""" from django.db import models from django.utils.translation import gettext_lazy as _ diff --git a/passbook/stages/otp_validate/settings.py b/passbook/stages/otp_validate/settings.py index 44212ad7d..34902a427 100644 --- a/passbook/stages/otp_validate/settings.py +++ b/passbook/stages/otp_validate/settings.py @@ -1,3 +1,4 @@ +"""OTP Validate stage settings""" INSTALLED_APPS = [ "django_otp", ] diff --git a/passbook/stages/otp_validate/stage.py b/passbook/stages/otp_validate/stage.py index f9088fcee..5d378e4cc 100644 --- a/passbook/stages/otp_validate/stage.py +++ b/passbook/stages/otp_validate/stage.py @@ -1,12 +1,12 @@ -from django.contrib import messages +"""OTP Validation""" +from typing import Any, Dict + from django.http import HttpRequest, HttpResponse -from django.utils.translation import gettext as _ from django.views.generic import FormView -from django_otp import match_token, user_has_device -from django_otp.models import Device +from django_otp import user_has_device from structlog import get_logger -from passbook.flows.models import NotConfiguredAction, Stage +from passbook.flows.models import NotConfiguredAction from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER from passbook.flows.stage import StageView from passbook.stages.otp_validate.forms import ValidationForm @@ -16,9 +16,15 @@ LOGGER = get_logger() class OTPValidateStageView(FormView, StageView): + """OTP Validation""" form_class = ValidationForm + def get_form_kwargs(self, **kwargs) -> Dict[str, Any]: + kwargs = super().get_form_kwargs(**kwargs) + kwargs["user"] = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER) + return kwargs + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER) if not user: @@ -35,11 +41,6 @@ class OTPValidateStageView(FormView, StageView): def form_valid(self, form: ValidationForm) -> HttpResponse: """Verify OTP Token""" - device = match_token( - self.executor.plan.context[PLAN_CONTEXT_PENDING_USER], - form.cleaned_data.get("code"), - ) - if not device: - messages.error(self.request, _("Invalid OTP.")) - return self.form_invalid(form) + # Since we do token checking in the form, we know the token is valid here + # so we can just continue return self.executor.stage_ok()