Initial mfa support (for internal users) #93591

Merged
Oleg-Komarov merged 46 commits from mfa into main 2024-08-29 11:44:06 +02:00
4 changed files with 13 additions and 12 deletions
Showing only changes of commit 9d92e5f807 - Show all commits

View File

@ -18,7 +18,7 @@ import qrcode
from . import mixins from . import mixins
from mfa.fido2 import register_begin from mfa.fido2 import register_begin
from mfa.forms import DisableMfaForm, TotpMfaForm, U2fMfaForm from mfa.forms import DisableMfaForm, TotpRegisterForm, U2fRegisterForm
from mfa.models import EncryptedRecoveryDevice, EncryptedTOTPDevice, U2fDevice, devices_for_user from mfa.models import EncryptedRecoveryDevice, EncryptedTOTPDevice, U2fDevice, devices_for_user
import bid_main.tasks import bid_main.tasks
@ -97,7 +97,7 @@ class InvalidateRecoveryView(mixins.MfaRequiredIfConfiguredMixin, View):
Oleg-Komarov marked this conversation as resolved Outdated

same as above

same as above
class TotpView(mixins.MfaRequiredIfConfiguredMixin, FormView): class TotpView(mixins.MfaRequiredIfConfiguredMixin, FormView):
form_class = TotpMfaForm form_class = TotpRegisterForm
success_url = reverse_lazy('bid_main:mfa') success_url = reverse_lazy('bid_main:mfa')
template_name = "bid_main/mfa/totp.html" template_name = "bid_main/mfa/totp.html"
@ -135,7 +135,7 @@ class TotpView(mixins.MfaRequiredIfConfiguredMixin, FormView):
class U2fView(mixins.MfaRequiredIfConfiguredMixin, FormView): class U2fView(mixins.MfaRequiredIfConfiguredMixin, FormView):
form_class = U2fMfaForm form_class = U2fRegisterForm
success_url = reverse_lazy('bid_main:mfa') success_url = reverse_lazy('bid_main:mfa')
template_name = "bid_main/mfa/u2f.html" template_name = "bid_main/mfa/u2f.html"

View File

@ -32,7 +32,7 @@ from .. import forms, email
from . import mixins from . import mixins
from bid_main.email import send_verify_address from bid_main.email import send_verify_address
from mfa.fido2 import authenticate_begin from mfa.fido2 import authenticate_begin
from mfa.forms import MfaForm, U2fForm from mfa.forms import MfaAuthenticateForm, U2fAuthenticateForm
import bid_main.file_utils import bid_main.file_utils
User = get_user_model() User = get_user_model()
@ -78,7 +78,7 @@ class LoginView(mixins.RedirectToPrivacyAgreeMixin, mixins.PageIdMixin, otp_agen
"""Shows the login view.""" """Shows the login view."""
otp_authentication_form = forms.AuthenticationForm otp_authentication_form = forms.AuthenticationForm
otp_token_form = MfaForm otp_token_form = MfaAuthenticateForm
page_id = "login" page_id = "login"
redirect_authenticated_user = False redirect_authenticated_user = False
success_url_allowed_hosts = settings.NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS success_url_allowed_hosts = settings.NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS
@ -96,10 +96,10 @@ class LoginView(mixins.RedirectToPrivacyAgreeMixin, mixins.PageIdMixin, otp_agen
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
self.find_oauth_flow(ctx) self.find_oauth_flow(ctx)
form = self.get_form() form = self.get_form()
if isinstance(form, U2fForm): if isinstance(form, U2fAuthenticateForm):
ctx["devices"] = self.request.user.mfa_devices_per_category() ctx["devices"] = self.request.user.mfa_devices_per_category()
ctx["is_u2f_form"] = True ctx["is_u2f_form"] = True
elif isinstance(form, MfaForm): elif isinstance(form, MfaAuthenticateForm):
ctx["devices"] = self.request.user.mfa_devices_per_category() ctx["devices"] = self.request.user.mfa_devices_per_category()
ctx["is_mfa_form"] = True ctx["is_mfa_form"] = True
ctx["use_recovery"] = self._use_recovery() ctx["use_recovery"] = self._use_recovery()
@ -119,7 +119,7 @@ class LoginView(mixins.RedirectToPrivacyAgreeMixin, mixins.PageIdMixin, otp_agen
kwargs['request_options'] = json.dumps(dict(request_options)) kwargs['request_options'] = json.dumps(dict(request_options))
kwargs['rp_id'] = rp_id kwargs['rp_id'] = rp_id
kwargs['state'] = dict(state) kwargs['state'] = dict(state)
return U2fForm(**kwargs) return U2fAuthenticateForm(**kwargs)
# this will switch between MfaForm and AuthenticationForm # this will switch between MfaForm and AuthenticationForm
return super().get_form() return super().get_form()

View File

@ -0,0 +1 @@
default_app_config = "%s.apps.MfaConfig" % __name__

View File

@ -27,7 +27,7 @@ def _verify_signature(payload, signature, max_age=3600):
return False return False
class MfaForm(OTPTokenForm): class MfaAuthenticateForm(OTPTokenForm):
"""Restyle the form widgets to do less work in the template.""" """Restyle the form widgets to do less work in the template."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -73,7 +73,7 @@ class MfaForm(OTPTokenForm):
) )
class U2fForm(OTPAgentFormMixin, forms.Form): class U2fAuthenticateForm(OTPAgentFormMixin, forms.Form):
otp_trust_agent = forms.BooleanField( otp_trust_agent = forms.BooleanField(
help_text=_( help_text=_(
f"We won't ask for MFA on this device in the next {settings.AGENT_TRUST_DAYS} days. " f"We won't ask for MFA on this device in the next {settings.AGENT_TRUST_DAYS} days. "
@ -145,7 +145,7 @@ class DisableMfaForm(forms.Form):
) )
class TotpMfaForm(forms.Form): class TotpRegisterForm(forms.Form):
code = forms.CharField( code = forms.CharField(
validators=[RegexValidator(r'^[0-9]{6}$')], validators=[RegexValidator(r'^[0-9]{6}$')],
widget=forms.TextInput( widget=forms.TextInput(
@ -190,7 +190,7 @@ class TotpMfaForm(forms.Form):
EncryptedTOTPDevice.objects.create(encrypted_key=key, key='', name=name, user=self.user) EncryptedTOTPDevice.objects.create(encrypted_key=key, key='', name=name, user=self.user)
class U2fMfaForm(forms.Form): class U2fRegisterForm(forms.Form):
credential = forms.JSONField(widget=forms.HiddenInput) credential = forms.JSONField(widget=forms.HiddenInput)
name = forms.CharField( name = forms.CharField(
max_length=U2fDevice._meta.get_field('name').max_length, max_length=U2fDevice._meta.get_field('name').max_length,