From 7283197bac66bf2c76195b8702f69ff31e523385 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 23 Jun 2022 04:21:10 +0200 Subject: [PATCH] MFA remove improvement (#3239) * temporary fix for GHSA-8j76-mm54-52xq * return to setting afterwards --- InvenTree/InvenTree/forms.py | 34 ++++++++++++++++++++++++++++++++++ InvenTree/InvenTree/urls.py | 11 ++++++++--- InvenTree/InvenTree/views.py | 12 +++++++++++- requirements.txt | 2 +- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index d0d725c2f8..f4157fdad9 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -15,6 +15,7 @@ from allauth.account.forms import SignupForm, set_form_field_order from allauth.exceptions import ImmediateHttpResponse from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from allauth_2fa.adapter import OTPAdapter +from allauth_2fa.forms import TOTPDeviceRemoveForm from allauth_2fa.utils import user_has_valid_totp_device from crispy_forms.bootstrap import (AppendedText, PrependedAppendedText, PrependedText) @@ -269,3 +270,36 @@ class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter): # Otherwise defer to the original allauth adapter. return super().login(request, user) + + +# Temporary fix for django-allauth-2fa # TODO remove +# See https://github.com/inventree/InvenTree/security/advisories/GHSA-8j76-mm54-52xq + +class CustomTOTPDeviceRemoveForm(TOTPDeviceRemoveForm): + """Custom Form to ensure a token is provided before removing MFA""" + # User must input a valid token so 2FA can be removed + token = forms.CharField( + label=_('Token'), + ) + + def __init__(self, user, **kwargs): + """Add token field.""" + super().__init__(user, **kwargs) + self.fields['token'].widget.attrs.update( + { + 'autofocus': 'autofocus', + 'autocomplete': 'off', + } + ) + + def clean_token(self): + """Ensure at least one valid token is provided.""" + # Ensure that the user has provided a valid token + token = self.cleaned_data.get('token') + + # Verify that the user has provided a valid token + for device in self.user.totpdevice_set.filter(confirmed=True): + if device.verify_token(token): + return token + + raise forms.ValidationError(_("The entered token is not valid")) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 2ccbe6d259..3f32152a5f 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -35,9 +35,9 @@ from .views import (AboutView, AppearanceSelectView, CurrencyRefreshView, CustomConnectionsView, CustomEmailView, CustomPasswordResetFromKeyView, CustomSessionDeleteOtherView, CustomSessionDeleteView, - DatabaseStatsView, DynamicJsView, EditUserView, IndexView, - NotificationsView, SearchView, SetPasswordView, - SettingsView, auth_request) + CustomTwoFactorRemove, DatabaseStatsView, DynamicJsView, + EditUserView, IndexView, NotificationsView, SearchView, + SetPasswordView, SettingsView, auth_request) admin.site.site_header = "InvenTree Admin" @@ -164,6 +164,11 @@ frontendpatterns = [ re_path(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'), re_path(r'^accounts/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'), re_path(r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"), + + # Temporary fix for django-allauth-2fa # TODO remove + # See https://github.com/inventree/InvenTree/security/advisories/GHSA-8j76-mm54-52xq + re_path(r'^accounts/two_factor/remove/?$', CustomTwoFactorRemove.as_view(), name='two-factor-remove'), + re_path(r'^accounts/', include('allauth_2fa.urls')), # MFA support re_path(r'^accounts/', include('allauth.urls')), # included urlpatterns ] diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index f2821813af..f18bf73106 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -28,6 +28,7 @@ from allauth.account.models import EmailAddress from allauth.account.views import EmailView, PasswordResetFromKeyView from allauth.socialaccount.forms import DisconnectForm from allauth.socialaccount.views import ConnectionsView +from allauth_2fa.views import TwoFactorRemove from djmoney.contrib.exchange.models import ExchangeBackend, Rate from user_sessions.views import SessionDeleteOtherView, SessionDeleteView @@ -36,7 +37,7 @@ from common.settings import currency_code_default, currency_codes from part.models import PartCategory from users.models import RuleSet, check_user_role -from .forms import EditUserForm, SetPasswordForm +from .forms import CustomTOTPDeviceRemoveForm, EditUserForm, SetPasswordForm def auth_request(request): @@ -761,3 +762,12 @@ class NotificationsView(TemplateView): """View for showing notifications.""" template_name = "InvenTree/notifications/notifications.html" + + +# Temporary fix for django-allauth-2fa # TODO remove +# See https://github.com/inventree/InvenTree/security/advisories/GHSA-8j76-mm54-52xq + +class CustomTwoFactorRemove(TwoFactorRemove): + """Use custom form.""" + form_class = CustomTOTPDeviceRemoveForm + success_url = reverse_lazy("settings") diff --git a/requirements.txt b/requirements.txt index 9684c63b35..9a9a93cfd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ coveralls==2.1.2 # Coveralls linking (for Travis) cryptography==3.4.8 # Cryptography support django-admin-shell==0.1.2 # Python shell for the admin interface django-allauth==0.48.0 # SSO for external providers via OpenID -django-allauth-2fa==0.9 # MFA / 2FA +django-allauth-2fa==0.9 # MFA / 2FA # IMPORTANT: Do only change after reviewing GHSA-8j76-mm54-52xq django-cleanup==5.1.0 # Manage deletion of old / unused uploaded files django-cors-headers==3.2.0 # CORS headers extension for DRF django-crispy-forms==1.11.2 # Form helpers