mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
2FA: Registraion of U2F devices
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
{% extends "pretixcontrol/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load staticfiles %}
|
||||
{% load compress %}
|
||||
{% block title %}{% trans "Add a two-factor authentication device" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Add a two-factor authentication device" %}</h1>
|
||||
<p id="u2f-progress">
|
||||
<span class="fa fa-cog fa-spin"></span>
|
||||
{% trans "Please connect your U2F device. If it has a button, touch it now. You might have to unplug the device and plug it back in again." %}
|
||||
</p>
|
||||
<form class="form form-inline" method="post" action="" id="u2f-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="u2f-response" name="token" class="form-control" required="required">
|
||||
<button class="btn btn-primary sr-only" type="submit"></button>
|
||||
</form>
|
||||
|
||||
<div class="sr-only alert alert-danger" id="u2f-error">
|
||||
{% trans "Device registration failed." %}
|
||||
</div>
|
||||
<script type="text/json" id="u2f-enroll">
|
||||
{{ jsondata|safe }}
|
||||
|
||||
</script>
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/u2f-api.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/u2f.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
@@ -58,6 +58,8 @@
|
||||
</a>
|
||||
{% if d.devicetype == "totp" %}
|
||||
<span class="fa fa-mobile"></span>
|
||||
{% elif d.devicetype == "u2f" %}
|
||||
<span class="fa fa-usb"></span>
|
||||
{% endif %}
|
||||
{{ d.name }}
|
||||
</li>
|
||||
|
||||
@@ -22,6 +22,8 @@ urlpatterns = [
|
||||
name='user.settings.2fa.regenemergency'),
|
||||
url(r'^settings/2fa/totp/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(),
|
||||
name='user.settings.2fa.confirm.totp'),
|
||||
url(r'^settings/2fa/u2f/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmU2FView.as_view(),
|
||||
name='user.settings.2fa.confirm.u2f'),
|
||||
url(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
|
||||
name='user.settings.2fa.delete'),
|
||||
url(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import base64
|
||||
import logging
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
@@ -12,12 +13,16 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import FormView, TemplateView, UpdateView
|
||||
from django_otp.plugins.otp_static.models import StaticDevice
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from django_otp_u2f_stub.models import U2FDevice
|
||||
from django_otp_u2f_stub.utils import get_origin
|
||||
from u2flib_server.jsapi import DeviceRegistration
|
||||
from u2flib_server.u2f import complete_register, start_register
|
||||
|
||||
from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm
|
||||
from pretix.base.models import User
|
||||
|
||||
|
||||
REAL_DEVICE_TYPES = (TOTPDevice,)
|
||||
REAL_DEVICE_TYPES = (TOTPDevice, U2FDevice)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserSettings(UpdateView):
|
||||
@@ -67,6 +72,8 @@ class User2FAMainView(TemplateView):
|
||||
for obj in objs:
|
||||
if dt == TOTPDevice:
|
||||
obj.devicetype = 'totp'
|
||||
elif dt == U2FDevice:
|
||||
obj.devicetype = 'u2f'
|
||||
ctx['devices'] += objs
|
||||
|
||||
return ctx
|
||||
@@ -79,6 +86,11 @@ class User2FADeviceAddView(FormView):
|
||||
def form_valid(self, form):
|
||||
if form.cleaned_data['devicetype'] == 'totp':
|
||||
dev = TOTPDevice.objects.create(user=self.request.user, confirmed=False, name=form.cleaned_data['name'])
|
||||
elif form.cleaned_data['devicetype'] == 'u2f':
|
||||
if not self.request.is_secure():
|
||||
messages.error(self.request, _('U2F devices are only available if pretix is served via HTTPS.'))
|
||||
return self.get(self.request, self.args, self.kwargs)
|
||||
dev = U2FDevice.objects.create(user=self.request.user, confirmed=False, name=form.cleaned_data['name'])
|
||||
else:
|
||||
messages.error(self.request, _('Unknown device type'))
|
||||
return self.get(self.request, self.args, self.kwargs)
|
||||
@@ -94,6 +106,8 @@ class User2FADeviceDeleteView(TemplateView):
|
||||
def device(self):
|
||||
if self.kwargs['devicetype'] == 'totp':
|
||||
return get_object_or_404(TOTPDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=True)
|
||||
elif self.kwargs['devicetype'] == 'u2f':
|
||||
return get_object_or_404(U2FDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=True)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
@@ -109,6 +123,47 @@ class User2FADeviceDeleteView(TemplateView):
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
|
||||
|
||||
class User2FADeviceConfirmU2FView(TemplateView):
|
||||
template_name = 'pretixcontrol/user/2fa_confirm_u2f.html'
|
||||
|
||||
@property
|
||||
def app_id(self):
|
||||
return get_origin(self.request)
|
||||
|
||||
@cached_property
|
||||
def device(self):
|
||||
return get_object_or_404(U2FDevice, user=self.request.user, pk=self.kwargs['device'], confirmed=False)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['device'] = self.device
|
||||
|
||||
devices = [DeviceRegistration.wrap(device.json_data)
|
||||
for device in U2FDevice.objects.filter(confirmed=True)]
|
||||
enroll = start_register(self.app_id, devices)
|
||||
self.request.session['_u2f_enroll'] = enroll.json
|
||||
ctx['jsondata'] = enroll.json
|
||||
|
||||
return ctx
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
binding, cert = complete_register(self.request.session.pop('_u2f_enroll'),
|
||||
request.POST.get('token'),
|
||||
[self.app_id])
|
||||
self.device.json_data = binding.json
|
||||
self.device.confirmed = True
|
||||
self.device.save()
|
||||
messages.success(request, _('The device has been verified and can now be used.'))
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
except ValueError:
|
||||
messages.error(request, _('The registration could not be completed. Please try again.'))
|
||||
logger.exception('U2F registration failed')
|
||||
return redirect(reverse('control:user.settings.2fa.confirm.2fa', kwargs={
|
||||
'device': self.device.pk
|
||||
}))
|
||||
|
||||
|
||||
class User2FADeviceConfirmTOTPView(TemplateView):
|
||||
template_name = 'pretixcontrol/user/2fa_confirm_totp.html'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user