stages/otp_time: Cleanup, use django_otp's URL generator

This commit is contained in:
Jens Langhammer 2020-06-30 12:42:39 +02:00
parent 7c191b0984
commit a76eb4d30f
3 changed files with 12 additions and 28 deletions

View File

@ -30,17 +30,24 @@ class SetupForm(forms.Form):
code = forms.CharField( code = forms.CharField(
label=_("Code"), label=_("Code"),
validators=[OTP_CODE_VALIDATOR], validators=[OTP_CODE_VALIDATOR],
widget=forms.TextInput(attrs={"placeholder": _("One-Time Password")}), widget=forms.TextInput(
attrs={
"autocomplete": "off",
"placeholder": "Code",
"autofocus": "autofocus",
}
),
) )
def __init__(self, device, qr_code, *args, **kwargs): def __init__(self, device, qr_code, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.device = device
self.fields["qr_code"].initial = qr_code self.fields["qr_code"].initial = qr_code
def clean_code(self): def clean_code(self):
"""Check code with new otp device""" """Check code with new otp device"""
if self.device is not None: if self.device is not None:
if not self.device.verify_token(int(self.cleaned_data.get("code"))): if not self.device.verify_token(self.cleaned_data.get("code")):
raise forms.ValidationError(_("OTP Code does not match")) raise forms.ValidationError(_("OTP Code does not match"))
return self.cleaned_data.get("code") return self.cleaned_data.get("code")

View File

@ -3,3 +3,4 @@
INSTALLED_APPS = [ INSTALLED_APPS = [
"django_otp.plugins.otp_totp", "django_otp.plugins.otp_totp",
] ]
OTP_TOTP_ISSUER = "passbook"

View File

@ -1,20 +1,15 @@
"""TOTP Setup stage""" """TOTP Setup stage"""
from base64 import b32encode
from binascii import unhexlify
from typing import Any, Dict from typing import Any, Dict
import lxml.etree as ET # nosec import lxml.etree as ET # nosec
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.encoding import force_text 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.views.generic import FormView
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
from qrcode import QRCode from qrcode import QRCode
from qrcode.image.svg import SvgFillImage from qrcode.image.svg import SvgFillImage
from structlog import get_logger from structlog import get_logger
from passbook.flows.models import NotConfiguredAction, Stage
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
from passbook.flows.stage import StageView from passbook.flows.stage import StageView
from passbook.stages.otp_time.forms import SetupForm from passbook.stages.otp_time.forms import SetupForm
@ -24,26 +19,8 @@ LOGGER = get_logger()
SESSION_TOTP_DEVICE = "totp_device" SESSION_TOTP_DEVICE = "totp_device"
def otp_auth_url(device: TOTPDevice) -> str:
"""Create otpauth according to
https://github.com/google/google-authenticator/wiki/Key-Uri-Format"""
# Ensure that the secret parameter is the FIRST parameter of the URI, this
# allows Microsoft Authenticator to work.
issuer = "passbook"
rawkey = unhexlify(device.key.encode("ascii"))
secret = b32encode(rawkey).decode("utf-8")
query = [
("secret", secret),
("digits", device.digits),
("issuer", issuer),
]
return "otpauth://totp/%s:%s?%s" % (issuer, device.user.username, urlencode(query))
class OTPTimeStageView(FormView, StageView): class OTPTimeStageView(FormView, StageView):
"""OTP totp Setup stage"""
form_class = SetupForm form_class = SetupForm
@ -56,9 +33,8 @@ class OTPTimeStageView(FormView, StageView):
def _get_qr_code(self, device: TOTPDevice) -> str: def _get_qr_code(self, device: TOTPDevice) -> str:
"""Get QR Code SVG as string based on `device`""" """Get QR Code SVG as string based on `device`"""
url = otp_auth_url(device)
qr_code = QRCode(image_factory=SvgFillImage) qr_code = QRCode(image_factory=SvgFillImage)
qr_code.add_data(url) qr_code.add_data(device.config_url)
return force_text(ET.tostring(qr_code.make_image().get_image())) return force_text(ET.tostring(qr_code.make_image().get_image()))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: