diff --git a/two_factor/admin.py b/two_factor/admin.py index 60a46f714..b783aac4f 100644 --- a/two_factor/admin.py +++ b/two_factor/admin.py @@ -6,7 +6,7 @@ from django.shortcuts import resolve_url from django.utils.http import is_safe_url -from .models import PhoneDevice +from .models import PhoneDevice, U2FDevice from .utils import monkeypatch_method @@ -75,4 +75,9 @@ class PhoneDeviceAdmin(admin.ModelAdmin): raw_id_fields = ('user',) +class U2FDeviceAdmin(admin.ModelAdmin): + pass + + admin.site.register(PhoneDevice, PhoneDeviceAdmin) +admin.site.register(U2FDevice, U2FDeviceAdmin) diff --git a/two_factor/forms.py b/two_factor/forms.py index 57dbc90ff..db3d80651 100644 --- a/two_factor/forms.py +++ b/two_factor/forms.py @@ -1,4 +1,5 @@ from binascii import unhexlify +import json from time import time from django import forms @@ -9,11 +10,13 @@ from django_otp.plugins.otp_totp.models import TOTPDevice from .models import ( - PhoneDevice, get_available_methods, get_available_phone_methods, + PhoneDevice, U2FDevice, get_available_methods, get_available_phone_methods, ) from .utils import totp_digits from .validators import validate_international_phonenumber +from u2flib_server import u2f + try: from otp_yubikey.models import RemoteYubikeyDevice, YubikeyDevice except ImportError: @@ -25,9 +28,9 @@ class MethodForm(forms.Form): initial='generator', widget=forms.RadioSelect) - def __init__(self, **kwargs): + def __init__(self, disabled_methods=None, **kwargs): super(MethodForm, self).__init__(**kwargs) - self.fields['method'].choices = get_available_methods() + self.fields['method'].choices = get_available_methods(disabled_methods=disabled_methods) class PhoneNumberMethodForm(ModelForm): @@ -83,6 +86,42 @@ def clean_token(self): self.device.public_id = self.cleaned_data['token'][:-32] return super(YubiKeyDeviceForm, self).clean_token() +class U2FDeviceForm(DeviceValidationForm): + token = forms.CharField(label=_("Token")) + + def __init__(self, user, device, request, **kwargs): + super(U2FDeviceForm, self).__init__(device, **kwargs) + self.request = request + self.user = user + self.u2f_device = None + self.appId = '{scheme}://{host}'.format(scheme='https' if self.request.is_secure() else 'http', host=self.request.get_host()) + + if self.data: + self.registration_request = self.request.session['u2f_registration_request'] + else: + self.registration_request = u2f.begin_registration(self.appId, [key.to_json() for key in self.request.user.u2f_keys.all()]) + self.request.session['u2f_registration_request'] = self.registration_request + + def clean_token(self): + response = self.cleaned_data['token'] + try: + request = self.request.session['u2f_registration_request'] + u2f_device, attestation_cert = u2f.complete_registration(request, response) + self.u2f_device = u2f_device + if U2FDevice.objects.filter(public_key=self.u2f_device['publicKey']).count() > 0: + raise forms.ValidationError("U2F device already exists in database: "+str(e)) + except ValueError as e: + raise forms.ValidationError("U2F device could not be verified: "+str(e)) + return response + + def save(self): + self.full_clean() + name = None + if len(self.request.user.u2f_keys.all()) == 0: + name = "default" + else: + name = "key" + return U2FDevice.objects.create(name=name, public_key=self.u2f_device['publicKey'], key_handle=self.u2f_device['keyHandle'], app_id=self.u2f_device['appId'], user=self.user) class TOTPDeviceForm(forms.Form): token = forms.IntegerField(label=_("Token"), min_value=0, max_value=int('9' * totp_digits())) @@ -152,7 +191,7 @@ class AuthenticationTokenForm(OTPAuthenticationFormMixin, Form): # its own `