mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Override 2FA token removal form (#3240)
- Requires user to input valid token to remove 2FA for their account Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
		@@ -17,6 +17,7 @@ from allauth.account.forms import SignupForm, set_form_field_order
 | 
				
			|||||||
from allauth.exceptions import ImmediateHttpResponse
 | 
					from allauth.exceptions import ImmediateHttpResponse
 | 
				
			||||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
 | 
					from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
 | 
				
			||||||
from allauth_2fa.adapter import OTPAdapter
 | 
					from allauth_2fa.adapter import OTPAdapter
 | 
				
			||||||
 | 
					from allauth_2fa.forms import TOTPDeviceRemoveForm
 | 
				
			||||||
from allauth_2fa.utils import user_has_valid_totp_device
 | 
					from allauth_2fa.utils import user_has_valid_totp_device
 | 
				
			||||||
from crispy_forms.bootstrap import (AppendedText, Div, PrependedAppendedText,
 | 
					from crispy_forms.bootstrap import (AppendedText, Div, PrependedAppendedText,
 | 
				
			||||||
                                    PrependedText, StrictButton)
 | 
					                                    PrependedText, StrictButton)
 | 
				
			||||||
@@ -325,3 +326,36 @@ class CustomSocialAccountAdapter(RegistratonMixin, DefaultSocialAccountAdapter):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Otherwise defer to the original allauth adapter.
 | 
					        # Otherwise defer to the original allauth adapter.
 | 
				
			||||||
        return super().login(request, user)
 | 
					        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"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,9 +36,10 @@ from .views import (AppearanceSelectView, CurrencyRefreshView,
 | 
				
			|||||||
                    CustomConnectionsView, CustomEmailView,
 | 
					                    CustomConnectionsView, CustomEmailView,
 | 
				
			||||||
                    CustomPasswordResetFromKeyView,
 | 
					                    CustomPasswordResetFromKeyView,
 | 
				
			||||||
                    CustomSessionDeleteOtherView, CustomSessionDeleteView,
 | 
					                    CustomSessionDeleteOtherView, CustomSessionDeleteView,
 | 
				
			||||||
                    DatabaseStatsView, DynamicJsView, EditUserView, IndexView,
 | 
					                    CustomTwoFactorRemove, DatabaseStatsView, DynamicJsView,
 | 
				
			||||||
                    NotificationsView, SearchView, SetPasswordView,
 | 
					                    EditUserView, IndexView, NotificationsView, SearchView,
 | 
				
			||||||
                    SettingCategorySelectView, SettingsView, auth_request)
 | 
					                    SetPasswordView, SettingCategorySelectView, SettingsView,
 | 
				
			||||||
 | 
					                    auth_request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.site_header = "InvenTree Admin"
 | 
					admin.site.site_header = "InvenTree Admin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -169,6 +170,11 @@ frontendpatterns = [
 | 
				
			|||||||
    re_path(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'),
 | 
					    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/social/connections/', CustomConnectionsView.as_view(), name='socialaccount_connections'),
 | 
				
			||||||
    re_path(r"^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$", CustomPasswordResetFromKeyView.as_view(), name="account_reset_password_from_key"),
 | 
					    re_path(r"^accounts/password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$", 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_2fa.urls')),    # MFA support
 | 
				
			||||||
    re_path(r'^accounts/', include('allauth.urls')),        # included urlpatterns
 | 
					    re_path(r'^accounts/', include('allauth.urls')),        # included urlpatterns
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@ from allauth.account.models import EmailAddress
 | 
				
			|||||||
from allauth.account.views import EmailView, PasswordResetFromKeyView
 | 
					from allauth.account.views import EmailView, PasswordResetFromKeyView
 | 
				
			||||||
from allauth.socialaccount.forms import DisconnectForm
 | 
					from allauth.socialaccount.forms import DisconnectForm
 | 
				
			||||||
from allauth.socialaccount.views import ConnectionsView
 | 
					from allauth.socialaccount.views import ConnectionsView
 | 
				
			||||||
 | 
					from allauth_2fa.views import TwoFactorRemove
 | 
				
			||||||
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
 | 
					from djmoney.contrib.exchange.models import ExchangeBackend, Rate
 | 
				
			||||||
from user_sessions.views import SessionDeleteOtherView, SessionDeleteView
 | 
					from user_sessions.views import SessionDeleteOtherView, SessionDeleteView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,8 +36,8 @@ from common.settings import currency_code_default, currency_codes
 | 
				
			|||||||
from part.models import PartCategory
 | 
					from part.models import PartCategory
 | 
				
			||||||
from users.models import RuleSet, check_user_role
 | 
					from users.models import RuleSet, check_user_role
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .forms import (DeleteForm, EditUserForm, SetPasswordForm,
 | 
					from .forms import (CustomTOTPDeviceRemoveForm, DeleteForm, EditUserForm,
 | 
				
			||||||
                    SettingCategorySelectForm)
 | 
					                    SetPasswordForm, SettingCategorySelectForm)
 | 
				
			||||||
from .helpers import str2bool
 | 
					from .helpers import str2bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -880,3 +881,12 @@ class NotificationsView(TemplateView):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    template_name = "InvenTree/notifications/notifications.html"
 | 
					    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")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ coverage==5.3                           # Unit test coverage
 | 
				
			|||||||
coveralls==2.1.2                        # Coveralls linking (for Travis)
 | 
					coveralls==2.1.2                        # Coveralls linking (for Travis)
 | 
				
			||||||
cryptography==3.4.8                     # Cryptography support
 | 
					cryptography==3.4.8                     # Cryptography support
 | 
				
			||||||
django-admin-shell==0.1.2               # Python shell for the admin interface
 | 
					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==0.48.0                  # SSO for external providers via OpenID
 | 
				
			||||||
django-allauth-2fa==0.8                 # 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-cleanup==5.1.0                   # Manage deletion of old / unused uploaded files
 | 
				
			||||||
django-cors-headers==3.2.0              # CORS headers extension for DRF
 | 
					django-cors-headers==3.2.0              # CORS headers extension for DRF
 | 
				
			||||||
django-crispy-forms==1.11.2             # Form helpers
 | 
					django-crispy-forms==1.11.2             # Form helpers
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user