stages/otp_time: implement TOTP Setup stage
This commit is contained in:
parent
285a69d91f
commit
b270fb0742
|
@ -16,7 +16,7 @@ register = template.Library()
|
|||
# pylint: disable=unused-argument
|
||||
def user_stages(context: RequestContext) -> List[UIUserSettings]:
|
||||
"""Return list of all stages which apply to user"""
|
||||
_all_stages: Iterable[Stage] = Stage.__subclasses__()
|
||||
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses()
|
||||
matching_stages: List[UIUserSettings] = []
|
||||
for stage in _all_stages:
|
||||
user_settings = stage.ui_user_settings
|
||||
|
|
|
@ -111,6 +111,7 @@ class FlowExecutorView(View):
|
|||
stage_response = self.current_stage_view.get(request, *args, **kwargs)
|
||||
return to_stage_response(request, stage_response)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
LOGGER.exception(exc)
|
||||
return to_stage_response(
|
||||
request,
|
||||
render(
|
||||
|
@ -132,6 +133,7 @@ class FlowExecutorView(View):
|
|||
stage_response = self.current_stage_view.post(request, *args, **kwargs)
|
||||
return to_stage_response(request, stage_response)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
LOGGER.exception(exc)
|
||||
return to_stage_response(
|
||||
request,
|
||||
render(
|
||||
|
|
|
@ -12,7 +12,7 @@ class PictureWidget(forms.widgets.Widget):
|
|||
"""Widget to render value as img-tag"""
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
return mark_safe(f'<img src="{value}" />') # nosec
|
||||
return mark_safe(f"<br>{value}") # nosec
|
||||
|
||||
|
||||
class SetupForm(forms.Form):
|
||||
|
@ -33,6 +33,10 @@ class SetupForm(forms.Form):
|
|||
widget=forms.TextInput(attrs={"placeholder": _("One-Time Password")}),
|
||||
)
|
||||
|
||||
def __init__(self, device, qr_code, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["qr_code"].initial = qr_code
|
||||
|
||||
def clean_code(self):
|
||||
"""Check code with new otp device"""
|
||||
if self.device is not None:
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
"""TOTP Setup stage"""
|
||||
from base64 import b32encode
|
||||
from binascii import unhexlify
|
||||
from typing import Any, Dict
|
||||
|
||||
from django.contrib import messages
|
||||
import lxml.etree as ET # nosec
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.http import urlencode
|
||||
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.plugins.otp_totp.models import TOTPDevice
|
||||
from qrcode import make
|
||||
from qrcode.image.svg import SvgPathImage
|
||||
from qrcode import QRCode
|
||||
from qrcode.image.svg import SvgFillImage
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.flows.models import NotConfiguredAction, Stage
|
||||
|
@ -20,7 +21,7 @@ from passbook.stages.otp_time.forms import SetupForm
|
|||
from passbook.stages.otp_time.models import OTPTimeStage
|
||||
|
||||
LOGGER = get_logger()
|
||||
PLAN_CONTEXT_TOTP_DEVICE = "totp_device"
|
||||
SESSION_TOTP_DEVICE = "totp_device"
|
||||
|
||||
|
||||
def otp_auth_url(device: TOTPDevice) -> str:
|
||||
|
@ -48,7 +49,7 @@ class OTPTimeStageView(FormView, StageView):
|
|||
|
||||
def get_form_kwargs(self, **kwargs) -> Dict[str, Any]:
|
||||
kwargs = super().get_form_kwargs(**kwargs)
|
||||
device: TOTPDevice = self.executor.plan.context[PLAN_CONTEXT_TOTP_DEVICE]
|
||||
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
||||
kwargs["device"] = device
|
||||
kwargs["qr_code"] = self._get_qr_code(device)
|
||||
return kwargs
|
||||
|
@ -56,9 +57,9 @@ class OTPTimeStageView(FormView, StageView):
|
|||
def _get_qr_code(self, device: TOTPDevice) -> str:
|
||||
"""Get QR Code SVG as string based on `device`"""
|
||||
url = otp_auth_url(device)
|
||||
# Make and return QR code
|
||||
img = make(url, image_factory=SvgPathImage)
|
||||
return img._img
|
||||
qr_code = QRCode(image_factory=SvgFillImage)
|
||||
qr_code.add_data(url)
|
||||
return force_text(ET.tostring(qr_code.make_image().get_image()))
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||
|
@ -67,13 +68,16 @@ class OTPTimeStageView(FormView, StageView):
|
|||
return self.executor.stage_ok()
|
||||
|
||||
stage: OTPTimeStage = self.executor.current_stage
|
||||
|
||||
if SESSION_TOTP_DEVICE not in self.request.session:
|
||||
device = TOTPDevice(user=user, confirmed=True, digits=stage.digits)
|
||||
|
||||
self.executor.plan.context[PLAN_CONTEXT_TOTP_DEVICE] = device
|
||||
self.request.session[SESSION_TOTP_DEVICE] = device
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form: SetupForm) -> HttpResponse:
|
||||
"""Verify OTP Token"""
|
||||
device: TOTPDevice = self.executor.plan.context[PLAN_CONTEXT_TOTP_DEVICE]
|
||||
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
||||
device.save()
|
||||
del self.request.session[SESSION_TOTP_DEVICE]
|
||||
return self.executor.stage_ok()
|
||||
|
|
Reference in New Issue