diff --git a/passbook/core/templatetags/passbook_user_settings.py b/passbook/core/templatetags/passbook_user_settings.py
index 6cbbd29db..1af1b9c97 100644
--- a/passbook/core/templatetags/passbook_user_settings.py
+++ b/passbook/core/templatetags/passbook_user_settings.py
@@ -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
diff --git a/passbook/flows/views.py b/passbook/flows/views.py
index 0e6d0fef5..1fe969f4a 100644
--- a/passbook/flows/views.py
+++ b/passbook/flows/views.py
@@ -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(
diff --git a/passbook/stages/otp_time/forms.py b/passbook/stages/otp_time/forms.py
index 5f590d773..af991fa06 100644
--- a/passbook/stages/otp_time/forms.py
+++ b/passbook/stages/otp_time/forms.py
@@ -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'') # nosec
+ return mark_safe(f"
{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:
diff --git a/passbook/stages/otp_time/stage.py b/passbook/stages/otp_time/stage.py
index a8bea5fd7..1f756dcff 100644
--- a/passbook/stages/otp_time/stage.py
+++ b/passbook/stages/otp_time/stage.py
@@ -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
- device = TOTPDevice(user=user, confirmed=True, digits=stage.digits)
- self.executor.plan.context[PLAN_CONTEXT_TOTP_DEVICE] = device
+ if SESSION_TOTP_DEVICE not in self.request.session:
+ device = TOTPDevice(user=user, confirmed=True, digits=stage.digits)
+
+ 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()