From 8fcb3c2506ba7981c40f3e41b9e7fb7de63b263e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 17 Dec 2024 12:20:21 +0100 Subject: [PATCH] Refactor auth adaptations into dedicated file (#8687) * Update docker.yaml (#278) * reset * rename functions to better reflect function * move authentication behaviour overrides to explicit file * fix import order * fix import path --- .../InvenTree/{forms.py => auth_overrides.py} | 125 +----------------- src/backend/InvenTree/InvenTree/settings.py | 12 +- .../InvenTree/InvenTree/social_auth_urls.py | 4 +- src/backend/InvenTree/InvenTree/sso.py | 4 +- src/backend/InvenTree/InvenTree/test_sso.py | 2 +- 5 files changed, 18 insertions(+), 129 deletions(-) rename src/backend/InvenTree/InvenTree/{forms.py => auth_overrides.py} (72%) diff --git a/src/backend/InvenTree/InvenTree/forms.py b/src/backend/InvenTree/InvenTree/auth_overrides.py similarity index 72% rename from src/backend/InvenTree/InvenTree/forms.py rename to src/backend/InvenTree/InvenTree/auth_overrides.py index d446ee0cd9..16f2aeb7a1 100644 --- a/src/backend/InvenTree/InvenTree/forms.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -1,11 +1,11 @@ -"""Helper forms which subclass Django forms to provide additional functionality.""" +"""Overrides for allauth and adjacent packages to enforce InvenTree specific auth settings and restirctions.""" import logging from urllib.parse import urlencode from django import forms from django.conf import settings -from django.contrib.auth.models import Group, User +from django.contrib.auth.models import Group from django.http import HttpResponseRedirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -17,9 +17,6 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from allauth_2fa.adapter import OTPAdapter from allauth_2fa.forms import TOTPDeviceForm from allauth_2fa.utils import user_has_valid_totp_device -from crispy_forms.bootstrap import AppendedText, PrependedAppendedText, PrependedText -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Field, Layout from dj_rest_auth.registration.serializers import RegisterSerializer from rest_framework import serializers @@ -31,115 +28,6 @@ from InvenTree.exceptions import log_error logger = logging.getLogger('inventree') -class HelperForm(forms.ModelForm): - """Provides simple integration of crispy_forms extension.""" - - # Custom field decorations can be specified here, per form class - field_prefix = {} - field_suffix = {} - field_placeholder = {} - - def __init__(self, *args, **kwargs): - """Setup layout.""" - super(forms.ModelForm, self).__init__(*args, **kwargs) - self.helper = FormHelper() - - self.helper.form_tag = False - self.helper.form_show_errors = True - - """ - Create a default 'layout' for this form. - Ref: https://django-crispy-forms.readthedocs.io/en/latest/layouts.html - This is required to do fancy things later (like adding PrependedText, etc). - - Simply create a 'blank' layout for each available field. - """ - - self.rebuild_layout() - - def rebuild_layout(self): - """Build crispy layout out of current fields.""" - layouts = [] - - for field in self.fields: - prefix = self.field_prefix.get(field, None) - suffix = self.field_suffix.get(field, None) - placeholder = self.field_placeholder.get(field, '') - - # Look for font-awesome icons - if prefix and prefix.startswith('fa-'): - prefix = f"" - - if suffix and suffix.startswith('fa-'): - suffix = f"" - - if prefix and suffix: - layouts.append( - Field( - PrependedAppendedText( - field, - prepended_text=prefix, - appended_text=suffix, - placeholder=placeholder, - ) - ) - ) - - elif prefix: - layouts.append( - Field(PrependedText(field, prefix, placeholder=placeholder)) - ) - - elif suffix: - layouts.append( - Field(AppendedText(field, suffix, placeholder=placeholder)) - ) - - else: - layouts.append(Field(field, placeholder=placeholder)) - - self.helper.layout = Layout(*layouts) - - -class SetPasswordForm(HelperForm): - """Form for setting user password.""" - - class Meta: - """Metaclass options.""" - - model = User - fields = ['enter_password', 'confirm_password', 'old_password'] - - enter_password = forms.CharField( - max_length=100, - min_length=8, - required=True, - initial='', - widget=forms.PasswordInput(attrs={'autocomplete': 'off'}), - label=_('Enter password'), - help_text=_('Enter new password'), - ) - - confirm_password = forms.CharField( - max_length=100, - min_length=8, - required=True, - initial='', - widget=forms.PasswordInput(attrs={'autocomplete': 'off'}), - label=_('Confirm password'), - help_text=_('Confirm new password'), - ) - - old_password = forms.CharField( - label=_('Old password'), - strip=False, - required=False, - widget=forms.PasswordInput( - attrs={'autocomplete': 'current-password', 'autofocus': True} - ), - ) - - # override allauth class CustomLoginForm(LoginForm): """Custom login form to override default allauth behaviour.""" @@ -214,7 +102,10 @@ class CustomTOTPDeviceForm(TOTPDeviceForm): def registration_enabled(): """Determine whether user registration is enabled.""" - if get_global_setting('LOGIN_ENABLE_REG') or InvenTree.sso.registration_enabled(): + if ( + get_global_setting('LOGIN_ENABLE_REG') + or InvenTree.sso.sso_registration_enabled() + ): if settings.EMAIL_HOST: return True else: @@ -316,10 +207,8 @@ class CustomAccountAdapter( def get_email_confirmation_url(self, request, emailconfirmation): """Construct the email confirmation url.""" - from InvenTree.helpers_model import construct_absolute_url - url = super().get_email_confirmation_url(request, emailconfirmation) - url = construct_absolute_url(url) + url = InvenTree.helpers_model.construct_absolute_url(url) return url diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index fea09801ac..bd2829b933 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -592,7 +592,7 @@ REST_AUTH = { OLD_PASSWORD_FIELD_ENABLED = True REST_AUTH_REGISTER_SERIALIZERS = { - 'REGISTER_SERIALIZER': 'InvenTree.forms.CustomRegisterSerializer' + 'REGISTER_SERIALIZER': 'InvenTree.auth_overrides.CustomRegisterSerializer' } # JWT settings - rest_framework_simplejwt @@ -1312,8 +1312,8 @@ REMOVE_SUCCESS_URL = 'settings' # override forms / adapters ACCOUNT_FORMS = { - 'login': 'InvenTree.forms.CustomLoginForm', - 'signup': 'InvenTree.forms.CustomSignupForm', + 'login': 'InvenTree.auth_overrides.CustomLoginForm', + 'signup': 'InvenTree.auth_overrides.CustomSignupForm', 'add_email': 'allauth.account.forms.AddEmailForm', 'change_password': 'allauth.account.forms.ChangePasswordForm', 'set_password': 'allauth.account.forms.SetPasswordForm', @@ -1321,12 +1321,12 @@ ACCOUNT_FORMS = { 'reset_password_from_key': 'allauth.account.forms.ResetPasswordKeyForm', 'disconnect': 'allauth.socialaccount.forms.DisconnectForm', } -ALLAUTH_2FA_FORMS = {'setup': 'InvenTree.forms.CustomTOTPDeviceForm'} +ALLAUTH_2FA_FORMS = {'setup': 'InvenTree.auth_overrides.CustomTOTPDeviceForm'} # Determine if multi-factor authentication is enabled for this server (default = True) MFA_ENABLED = get_boolean_setting('INVENTREE_MFA_ENABLED', 'mfa_enabled', True) -SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' -ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' +SOCIALACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomSocialAccountAdapter' +ACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomAccountAdapter' # Markdownify configuration # Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html diff --git a/src/backend/InvenTree/InvenTree/social_auth_urls.py b/src/backend/InvenTree/InvenTree/social_auth_urls.py index a7ad13dd6f..27660d2502 100644 --- a/src/backend/InvenTree/InvenTree/social_auth_urls.py +++ b/src/backend/InvenTree/InvenTree/social_auth_urls.py @@ -198,8 +198,8 @@ class SocialProviderListView(ListAPI): provider_list.append(provider_data) data = { - 'sso_enabled': InvenTree.sso.login_enabled(), - 'sso_registration': InvenTree.sso.registration_enabled(), + 'sso_enabled': InvenTree.sso.sso_login_enabled(), + 'sso_registration': InvenTree.sso.sso_registration_enabled(), 'mfa_required': settings.MFA_ENABLED and get_global_setting('LOGIN_ENFORCE_MFA'), 'mfa_enabled': settings.MFA_ENABLED, diff --git a/src/backend/InvenTree/InvenTree/sso.py b/src/backend/InvenTree/InvenTree/sso.py index b77e9c136e..31de8dd167 100644 --- a/src/backend/InvenTree/InvenTree/sso.py +++ b/src/backend/InvenTree/InvenTree/sso.py @@ -69,12 +69,12 @@ def provider_display_name(provider): return provider.name -def login_enabled() -> bool: +def sso_login_enabled() -> bool: """Return True if SSO login is enabled.""" return str2bool(get_global_setting('LOGIN_ENABLE_SSO')) -def registration_enabled() -> bool: +def sso_registration_enabled() -> bool: """Return True if SSO registration is enabled.""" return str2bool(get_global_setting('LOGIN_ENABLE_SSO_REG')) diff --git a/src/backend/InvenTree/InvenTree/test_sso.py b/src/backend/InvenTree/InvenTree/test_sso.py index e20f11d3ae..31481bc09d 100644 --- a/src/backend/InvenTree/InvenTree/test_sso.py +++ b/src/backend/InvenTree/InvenTree/test_sso.py @@ -8,7 +8,7 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin from common.models import InvenTreeSetting from InvenTree import sso -from InvenTree.forms import RegistratonMixin +from InvenTree.auth_overrides import RegistratonMixin class Dummy: