Initial mfa support (for internal users) #93591
@ -12,10 +12,16 @@ Multi-factor Authentication Setup
|
||||
<div class="col-md-6">
|
||||
{% with form=form|add_form_classes %}
|
||||
<form method="post" id="u2f-register-form">{% csrf_token %}
|
||||
{% with field=form.credential %}
|
||||
{% include "components/forms/field.html" %}
|
||||
{% endwith %}
|
||||
{% with field=form.name %}
|
||||
{% include "components/forms/field.html" %}
|
||||
{% endwith %}
|
||||
{% with field=form.credential %}
|
||||
{% with field=form.signature %}
|
||||
{% include "components/forms/field.html" %}
|
||||
{% endwith %}
|
||||
{% with field=form.state %}
|
||||
{% include "components/forms/field.html" %}
|
||||
{% endwith %}
|
||||
<button type="submit" class="btn">Add security key</button>
|
||||
|
@ -135,24 +135,19 @@ class U2fView(mixins.MfaRequiredIfConfiguredMixin, FormView):
|
||||
template_name = "bid_main/mfa/u2f.html"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
credentials = [
|
||||
AttestedCredentialData(d.credential)
|
||||
for d in U2fDevice.objects.filter(user=self.request.user).all()
|
||||
]
|
||||
rp_id = self.request.get_host().split(':')[0] # remove port, required by webauthn
|
||||
Oleg-Komarov marked this conversation as resolved
|
||||
credential_creation_options, state = register_begin(
|
||||
rp_id, self.request.user, credentials,
|
||||
)
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['credential_creation_options'] = json.dumps(dict(credential_creation_options))
|
||||
kwargs['rp_id'] = rp_id
|
||||
kwargs['state'] = dict(state)
|
||||
kwargs['user'] = self.request.user
|
||||
|
||||
if self.request.method == 'GET':
|
||||
credentials = [
|
||||
AttestedCredentialData(d.credential)
|
||||
for d in U2fDevice.objects.filter(user=self.request.user).all()
|
||||
]
|
||||
credential_creation_options, state = register_begin(
|
||||
rp_id, self.request.user, credentials,
|
||||
)
|
||||
self.request.session['u2f_register_state'] = dict(state)
|
||||
kwargs['credential_creation_options'] = json.dumps(dict(credential_creation_options))
|
||||
if self.request.method == 'POST':
|
||||
kwargs['state'] = self.request.session.pop('u2f_register_state', None)
|
||||
|
||||
return kwargs
|
||||
|
||||
@transaction.atomic
|
||||
|
@ -111,8 +111,8 @@ class LoginView(mixins.RedirectToPrivacyAgreeMixin, mixins.PageIdMixin, otp_agen
|
||||
if self.request.user.is_authenticated:
|
||||
devices = self.request.user.mfa_devices_per_category().get('u2f', None)
|
||||
if devices and not self._use_recovery() and not self._use_totp():
|
||||
rp_id = self.request.get_host().split(':')[0] # remove port, required by webauthn
|
||||
credentials = [AttestedCredentialData(d.credential) for d in devices]
|
||||
rp_id = self.request.get_host().split(':')[0] # remove port, required by webauthn
|
||||
request_options, state = authenticate_begin(rp_id, credentials)
|
||||
kwargs = self.get_form_kwargs()
|
||||
kwargs['credentials'] = credentials
|
||||
|
@ -21,7 +21,7 @@ def get_fido2server(rp_id):
|
||||
)
|
||||
|
||||
|
||||
def register_begin(rp_id, user, credentials=[]):
|
||||
def register_begin(rp_id, user, credentials):
|
||||
return get_fido2server(rp_id).register_begin(
|
||||
PublicKeyCredentialUserEntity(
|
||||
display_name=user.email,
|
||||
|
17
mfa/forms.py
17
mfa/forms.py
@ -198,21 +198,34 @@ class U2fMfaForm(forms.Form):
|
||||
attrs={"placeholder": "device name (for your convenience)"},
|
||||
),
|
||||
)
|
||||
signature = forms.CharField(widget=forms.HiddenInput)
|
||||
state = forms.JSONField(widget=forms.HiddenInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
credential_creation_options = kwargs.pop('credential_creation_options', None)
|
||||
self.rp_id = kwargs.pop('rp_id', None)
|
||||
self.state = kwargs.pop('state', None)
|
||||
state = kwargs.pop('state', None)
|
||||
self.user = kwargs.pop('user', None)
|
||||
kwargs['initial']['signature'] = _sign(f"{self.user.email}_{state['challenge']}")
|
||||
kwargs['initial']['state'] = state
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['credential'].widget.attrs['creation-options'] = credential_creation_options
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
signature = self.cleaned_data.get('signature')
|
||||
state = self.cleaned_data.get('state')
|
||||
if not _verify_signature(f"{self.user.email}_{state['challenge']}", signature):
|
||||
raise forms.ValidationError(_('Invalid signature'))
|
||||
return self.cleaned_data
|
||||
|
||||
def save(self):
|
||||
credential = self.cleaned_data.get('credential')
|
||||
name = self.cleaned_data.get('name')
|
||||
state = self.cleaned_data.get('state')
|
||||
auth_data = None
|
||||
try:
|
||||
auth_data = register_complete(self.rp_id, self.state, credential)
|
||||
auth_data = register_complete(self.rp_id, state, credential)
|
||||
except Exception:
|
||||
raise forms.ValidationError(_('Verification failed'))
|
||||
U2fDevice.objects.create(
|
||||
|
Loading…
Reference in New Issue
Block a user
is
context['first_device'] = not devices_for_user(self.request.user)
necessary here as well?