diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 50e2d26fffa..dc7945fb1df 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -17,6 +17,7 @@ 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, Div, PrependedAppendedText, PrependedText, StrictButton) @@ -325,3 +326,36 @@ def login(self, request, user): # 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 c3f5b871690..5ad3b325dbd 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -36,9 +36,10 @@ CustomConnectionsView, CustomEmailView, CustomPasswordResetFromKeyView, CustomSessionDeleteOtherView, CustomSessionDeleteView, - DatabaseStatsView, DynamicJsView, EditUserView, IndexView, - NotificationsView, SearchView, SetPasswordView, - SettingCategorySelectView, SettingsView, auth_request) + CustomTwoFactorRemove, DatabaseStatsView, DynamicJsView, + EditUserView, IndexView, NotificationsView, SearchView, + SetPasswordView, SettingCategorySelectView, SettingsView, + auth_request) admin.site.site_header = "InvenTree Admin" @@ -169,6 +170,11 @@ 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 b29ea7cd441..b45b7317ffd 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -27,6 +27,7 @@ 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 @@ -35,8 +36,8 @@ from part.models import PartCategory from users.models import RuleSet, check_user_role -from .forms import (DeleteForm, EditUserForm, SetPasswordForm, - SettingCategorySelectForm) +from .forms import (CustomTOTPDeviceRemoveForm, DeleteForm, EditUserForm, + SetPasswordForm, SettingCategorySelectForm) from .helpers import str2bool @@ -880,3 +881,12 @@ class NotificationsView(TemplateView): """ 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 822d40fc54e..7fd7e0ee60f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,8 +7,8 @@ coverage==5.3 # Unit test coverage 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.45.0 # SSO for external providers via OpenID -django-allauth-2fa==0.8 # MFA / 2FA +django-allauth==0.48.0 # SSO for external providers via OpenID +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