From be183791cd4e2aa0130d709d4fabe9d14858d6c3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 13:51:01 +0200 Subject: [PATCH] remove more old stuff --- src/backend/InvenTree/InvenTree/forms.py | 20 -- src/backend/InvenTree/InvenTree/settings.py | 29 -- .../InvenTree/InvenTree/social_auth_urls.py | 267 ------------------ src/backend/InvenTree/InvenTree/sso.py | 9 - src/backend/InvenTree/InvenTree/urls.py | 60 ---- src/backend/InvenTree/InvenTree/views.py | 52 ---- src/backend/InvenTree/users/api.py | 70 +---- src/backend/requirements.in | 1 - src/backend/requirements.txt | 4 - 9 files changed, 3 insertions(+), 509 deletions(-) delete mode 100644 src/backend/InvenTree/InvenTree/social_auth_urls.py diff --git a/src/backend/InvenTree/InvenTree/forms.py b/src/backend/InvenTree/InvenTree/forms.py index d2d2278f64..4d6de19d59 100644 --- a/src/backend/InvenTree/InvenTree/forms.py +++ b/src/backend/InvenTree/InvenTree/forms.py @@ -15,8 +15,6 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 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 import InvenTree.helpers_model import InvenTree.sso @@ -341,21 +339,3 @@ class CustomSocialAccountAdapter( # Log the error to the database log_error(path, error_name=error, error_data=exception) logger.error("SSO error for provider '%s' - check admin error log", provider_id) - - -# override dj-rest-auth -class CustomRegisterSerializer(RegisterSerializer): - """Override of serializer to use dynamic settings.""" - - email = serializers.EmailField() - - def __init__(self, instance=None, data=..., **kwargs): - """Check settings to influence which fields are needed.""" - kwargs['email_required'] = get_global_setting('LOGIN_MAIL_REQUIRED') - super().__init__(instance, data, **kwargs) - - def save(self, request): - """Override to check if registration is open.""" - if registration_enabled(): - return super().save(request) - raise forms.ValidationError(_('Registration is disabled.')) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index cf10fa394d..6fe0448e31 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -235,8 +235,6 @@ INSTALLED_APPS = [ 'django_otp', # OTP is needed for MFA - base package 'django_otp.plugins.otp_totp', # Time based OTP 'django_otp.plugins.otp_static', # Backup codes - 'dj_rest_auth', # Authentication APIs - dj-rest-auth - 'dj_rest_auth.registration', # Registration APIs - dj-rest-auth' 'drf_spectacular', # API documentation 'django_ical', # For exporting calendars ] @@ -484,33 +482,6 @@ if DEBUG: 'rest_framework.renderers.BrowsableAPIRenderer' ) -# JWT switch -USE_JWT = get_boolean_setting('INVENTREE_USE_JWT', 'use_jwt', False) -REST_USE_JWT = USE_JWT - -# dj-rest-auth -REST_AUTH = { - 'SESSION_LOGIN': True, - 'TOKEN_MODEL': 'users.models.ApiToken', - 'TOKEN_CREATOR': 'users.models.default_create_token', - 'USE_JWT': USE_JWT, -} - -OLD_PASSWORD_FIELD_ENABLED = True -REST_AUTH_REGISTER_SERIALIZERS = { - 'REGISTER_SERIALIZER': 'InvenTree.forms.CustomRegisterSerializer' -} - -# JWT settings - rest_framework_simplejwt -if USE_JWT: - JWT_AUTH_COOKIE = 'inventree-auth' - JWT_AUTH_REFRESH_COOKIE = 'inventree-token' - REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append( - 'dj_rest_auth.jwt_auth.JWTCookieAuthentication' - ) - INSTALLED_APPS.append('rest_framework_simplejwt') - - # WSGI default setting WSGI_APPLICATION = 'InvenTree.wsgi.application' diff --git a/src/backend/InvenTree/InvenTree/social_auth_urls.py b/src/backend/InvenTree/InvenTree/social_auth_urls.py deleted file mode 100644 index 49d9e461ee..0000000000 --- a/src/backend/InvenTree/InvenTree/social_auth_urls.py +++ /dev/null @@ -1,267 +0,0 @@ -"""API endpoints for social authentication with allauth.""" - -import logging -from importlib import import_module - -from django.urls import NoReverseMatch, include, path, reverse - -from allauth.account.models import EmailAddress -from allauth.socialaccount import providers -from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter, OAuth2LoginView -from drf_spectacular.utils import OpenApiResponse, extend_schema -from rest_framework import serializers -from rest_framework.exceptions import NotFound -from rest_framework.permissions import AllowAny, IsAuthenticated -from rest_framework.response import Response - -import InvenTree.sso -from common.settings import get_global_setting -from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI -from InvenTree.serializers import EmptySerializer, InvenTreeModelSerializer - -logger = logging.getLogger('inventree') - - -class GenericOAuth2ApiLoginView(OAuth2LoginView): - """Api view to login a user with a social account.""" - - def dispatch(self, request, *args, **kwargs): - """Dispatch the regular login view directly.""" - return self.login(request, *args, **kwargs) - - -class GenericOAuth2ApiConnectView(GenericOAuth2ApiLoginView): - """Api view to connect a social account to the current user.""" - - def dispatch(self, request, *args, **kwargs): - """Dispatch the connect request directly.""" - # Override the request method be in connection mode - request.GET = request.GET.copy() - request.GET['process'] = 'connect' - - # Resume the dispatch - return super().dispatch(request, *args, **kwargs) - - -def handle_oauth2(adapter: OAuth2Adapter): - """Define urls for oauth2 endpoints.""" - return [ - path( - 'login/', - GenericOAuth2ApiLoginView.adapter_view(adapter), - name=f'{provider.id}_api_login', - ), - path( - 'connect/', - GenericOAuth2ApiConnectView.adapter_view(adapter), - name=f'{provider.id}_api_connect', - ), - ] - - -legacy = { - 'twitter': 'twitter_oauth2', - 'bitbucket': 'bitbucket_oauth2', - 'linkedin': 'linkedin_oauth2', - 'vimeo': 'vimeo_oauth2', - 'openid': 'openid_connect', -} # legacy connectors - - -# Collect urls for all loaded providers -social_auth_urlpatterns = [] - -provider_urlpatterns = [] - -for name, provider in providers.registry.provider_map.items(): - try: - prov_mod = import_module(provider.get_package() + '.views') - except ImportError: - logger.exception('Could not import authentication provider %s', name) - continue - - # Try to extract the adapter class - adapters = [ - cls - for cls in prov_mod.__dict__.values() - if isinstance(cls, type) - and cls != OAuth2Adapter - and issubclass(cls, OAuth2Adapter) - ] - - # Get urls - urls = [] - if len(adapters) == 1: - urls = handle_oauth2(adapter=adapters[0]) - else: - if provider.id in legacy: - logger.warning( - '`%s` is not supported on platform UI. Use `%s` instead.', - provider.id, - legacy[provider.id], - ) - continue - else: - logger.error( - 'Found handler that is not yet ready for platform UI: `%s`. Open an feature request on GitHub if you need it implemented.', - provider.id, - ) - continue - provider_urlpatterns += [path(f'{provider.id}/', include(urls))] - - -social_auth_urlpatterns += provider_urlpatterns - - -class SocialProviderListResponseSerializer(serializers.Serializer): - """Serializer for the SocialProviderListView.""" - - class SocialProvider(serializers.Serializer): - """Serializer for the SocialProviderListResponseSerializer.""" - - id = serializers.CharField() - name = serializers.CharField() - configured = serializers.BooleanField() - login = serializers.URLField() - connect = serializers.URLField() - display_name = serializers.CharField() - - sso_enabled = serializers.BooleanField() - sso_registration = serializers.BooleanField() - mfa_required = serializers.BooleanField() - providers = SocialProvider(many=True) - registration_enabled = serializers.BooleanField() - password_forgotten_enabled = serializers.BooleanField() - - -class SocialProviderListView(ListAPI): - """List of available social providers.""" - - permission_classes = (AllowAny,) - serializer_class = EmptySerializer - - @extend_schema( - responses={200: OpenApiResponse(response=SocialProviderListResponseSerializer)} - ) - def get(self, request, *args, **kwargs): - """Get the list of providers.""" - provider_list = [] - for provider in providers.registry.provider_map.values(): - provider_data = { - 'id': provider.id, - 'name': provider.name, - 'configured': False, - } - - try: - provider_data['login'] = request.build_absolute_uri( - reverse(f'{provider.id}_api_login') - ) - except NoReverseMatch: - provider_data['login'] = None - - try: - provider_data['connect'] = request.build_absolute_uri( - reverse(f'{provider.id}_api_connect') - ) - except NoReverseMatch: - provider_data['connect'] = None - - provider_data['configured'] = InvenTree.sso.check_provider(provider) - provider_data['display_name'] = InvenTree.sso.provider_display_name( - provider - ) - - provider_list.append(provider_data) - - data = { - 'sso_enabled': InvenTree.sso.login_enabled(), - 'sso_registration': InvenTree.sso.registration_enabled(), - 'mfa_required': get_global_setting('LOGIN_ENFORCE_MFA'), - 'providers': provider_list, - 'registration_enabled': get_global_setting('LOGIN_ENABLE_REG'), - 'password_forgotten_enabled': get_global_setting('LOGIN_ENABLE_PWD_FORGOT'), - } - return Response(data) - - -class EmailAddressSerializer(InvenTreeModelSerializer): - """Serializer for the EmailAddress model.""" - - class Meta: - """Meta options for EmailAddressSerializer.""" - - model = EmailAddress - fields = '__all__' - - -class EmptyEmailAddressSerializer(InvenTreeModelSerializer): - """Empty Serializer for the EmailAddress model.""" - - class Meta: - """Meta options for EmailAddressSerializer.""" - - model = EmailAddress - fields = [] - - -class EmailListView(ListCreateAPI): - """List of registered email addresses for current users.""" - - permission_classes = (IsAuthenticated,) - serializer_class = EmailAddressSerializer - - def get_queryset(self): - """Only return data for current user.""" - return EmailAddress.objects.filter(user=self.request.user) - - -class EmailActionMixin(CreateAPI): - """Mixin to modify email addresses for current users.""" - - serializer_class = EmptyEmailAddressSerializer - permission_classes = (IsAuthenticated,) - - def get_queryset(self): - """Filter queryset for current user.""" - return EmailAddress.objects.filter( - user=self.request.user, pk=self.kwargs['pk'] - ).first() - - @extend_schema(responses={200: OpenApiResponse(response=EmailAddressSerializer)}) - def post(self, request, *args, **kwargs): - """Filter item, run action and return data.""" - email = self.get_queryset() - if not email: - raise NotFound - - self.special_action(email, request, *args, **kwargs) - return Response(EmailAddressSerializer(email).data) - - -class EmailVerifyView(EmailActionMixin): - """Re-verify an email for a currently logged in user.""" - - def special_action(self, email, request, *args, **kwargs): - """Send confirmation.""" - if email.verified: - return - email.send_confirmation(request) - - -class EmailPrimaryView(EmailActionMixin): - """Make an email for a currently logged in user primary.""" - - def special_action(self, email, *args, **kwargs): - """Mark email as primary.""" - if email.primary: - return - email.set_as_primary() - - -class EmailRemoveView(EmailActionMixin): - """Remove an email for a currently logged in user.""" - - def special_action(self, email, *args, **kwargs): - """Delete email.""" - email.delete() diff --git a/src/backend/InvenTree/InvenTree/sso.py b/src/backend/InvenTree/InvenTree/sso.py index b3fb551cf2..86d5c3346d 100644 --- a/src/backend/InvenTree/InvenTree/sso.py +++ b/src/backend/InvenTree/InvenTree/sso.py @@ -53,15 +53,6 @@ def check_provider(provider): return True -def provider_display_name(provider): - """Return the 'display name' for the given provider.""" - if app := get_provider_app(provider): - return app.name - - # Fallback value if app not found - return provider.name - - def login_enabled() -> bool: """Return True if SSO login is enabled.""" return str2bool(get_global_setting('LOGIN_ENABLE_SSO')) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index a9fa1cd0e0..a0a3ce58c3 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -10,11 +10,6 @@ from django.urls import include, path, re_path from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import RedirectView -from dj_rest_auth.registration.views import ( - ConfirmEmailView, - SocialAccountDisconnectView, - SocialAccountListView, -) from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView from sesame.views import LoginView @@ -47,14 +42,6 @@ from .api import ( VersionView, ) from .magic_login import GetSimpleLoginView -from .social_auth_urls import ( - EmailListView, - EmailPrimaryView, - EmailRemoveView, - EmailVerifyView, - SocialProviderListView, - social_auth_urlpatterns, -) from .views import ( AboutView, AppearanceSelectView, @@ -130,58 +117,12 @@ apipatterns = [ path( 'auth/', include([ - re_path( - r'^registration/account-confirm-email/(?P[-:\w]+)/$', - ConfirmEmailView.as_view(), - name='account_confirm_email', - ), - path('registration/', include('dj_rest_auth.registration.urls')), - path( - 'providers/', SocialProviderListView.as_view(), name='social_providers' - ), - path( - 'emails/', - include([ - path( - '/', - include([ - path( - 'primary/', - EmailPrimaryView.as_view(), - name='email-primary', - ), - path( - 'verify/', - EmailVerifyView.as_view(), - name='email-verify', - ), - path( - 'remove/', - EmailRemoveView().as_view(), - name='email-remove', - ), - ]), - ), - path('', EmailListView.as_view(), name='email-list'), - ]), - ), - path('social/', include(social_auth_urlpatterns)), - path( - 'social/', SocialAccountListView.as_view(), name='social_account_list' - ), - path( - 'social//disconnect/', - SocialAccountDisconnectView.as_view(), - name='social_account_disconnect', - ), - path('login/', users.api.Login.as_view(), name='api-login'), path('logout/', users.api.Logout.as_view(), name='api-logout'), path( 'login-redirect/', users.api.LoginRedirect.as_view(), name='api-login-redirect', ), - path('', include('dj_rest_auth.urls')), ]), ), # Magic login URLs @@ -410,7 +351,6 @@ classic_frontendpatterns = [ path('stock/', include(stock_urls)), path('supplier-part/', include(supplier_part_urls)), path('edit-user/', EditUserView.as_view(), name='edit-user'), - path('set-password/', SetPasswordView.as_view(), name='set-password'), path('index/', IndexView.as_view(), name='index'), path('notifications/', include(notifications_urls)), path('search/', SearchView.as_view(), name='search'), diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index 176e704b19..931520a40f 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -405,58 +405,6 @@ class EditUserView(AjaxUpdateView): return self.request.user -class SetPasswordView(AjaxUpdateView): - """View for setting user password.""" - - ajax_template_name = 'InvenTree/password.html' - ajax_form_title = _('Set Password') - form_class = SetPasswordForm - - def get_object(self): - """Set form to edit current user.""" - return self.request.user - - def post(self, request, *args, **kwargs): - """Validate inputs and change password.""" - form = self.get_form() - - valid = form.is_valid() - - p1 = request.POST.get('enter_password', '') - p2 = request.POST.get('confirm_password', '') - old_password = request.POST.get('old_password', '') - user = self.request.user - - if valid: - # Passwords must match - - if p1 != p2: - error = _('Password fields must match') - form.add_error('enter_password', error) - form.add_error('confirm_password', error) - valid = False - - if valid: - # Old password must be correct - if user.has_usable_password() and not user.check_password(old_password): - form.add_error('old_password', _('Wrong password provided')) - valid = False - - if valid: - try: - # Validate password - password_validation.validate_password(p1, user) - - # Update the user - user.set_password(p1) - user.save() - except ValidationError as error: - form.add_error('confirm_password', str(error)) - valid = False - - return self.renderJsonResponse(request, form, data={'form_valid': valid}) - - class IndexView(TemplateView): """View for InvenTree index page.""" diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index bbdc3be975..c0b8e11220 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -3,26 +3,17 @@ import datetime import logging -from django.contrib.auth import authenticate, get_user, login, logout +from django.contrib.auth import get_user, login from django.contrib.auth.models import Group, User -from django.http.response import HttpResponse -from django.shortcuts import redirect -from django.urls import include, path, re_path, reverse +from django.urls import include, path, re_path from django.views.generic.base import RedirectView -from allauth.account import app_settings -from allauth.account.adapter import get_adapter -from allauth_2fa.utils import user_has_valid_totp_device -from dj_rest_auth.views import LoginView, LogoutView from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view from rest_framework import exceptions, permissions -from rest_framework.authentication import BasicAuthentication -from rest_framework.decorators import authentication_classes from rest_framework.response import Response from rest_framework.views import APIView import InvenTree.helpers -from common.models import InvenTreeSetting from InvenTree.filters import SEARCH_ORDER_FILTER from InvenTree.mixins import ( ListAPI, @@ -184,67 +175,12 @@ class GroupList(ListCreateAPI): ordering_fields = ['name'] -@authentication_classes([BasicAuthentication]) -@extend_schema_view( - post=extend_schema( - responses={200: OpenApiResponse(description='User successfully logged in')} - ) -) -class Login(LoginView): - """API view for logging in via API.""" - - def post(self, request, *args, **kwargs): - """API view for logging in via API.""" - _data = request.data.copy() - _data.update(request.POST.copy()) - - if not _data.get('mfa', None): - return super().post(request, *args, **kwargs) - - # Check if login credentials valid - user = authenticate( - request, username=_data.get('username'), password=_data.get('password') - ) - if user is None: - return HttpResponse(status=401) - - # Check if user has mfa set up - if not user_has_valid_totp_device(user): - return super().post(request, *args, **kwargs) - - # Stage login and redirect to 2fa - request.session['allauth_2fa_user_id'] = str(user.id) - request.session['allauth_2fa_login'] = { - 'email_verification': app_settings.EMAIL_VERIFICATION, - 'signal_kwargs': None, - 'signup': False, - 'email': None, - 'redirect_url': reverse('platform'), - } - return redirect(reverse('two-factor-authenticate')) - - def process_login(self): - """Process the login request, ensure that MFA is enforced if required.""" - # Normal login process - ret = super().process_login() - user = self.request.user - adapter = get_adapter(self.request) - - # User requires 2FA or MFA is enforced globally - no logins via API - if adapter.has_2fa_enabled(user) or InvenTreeSetting.get_setting( - 'LOGIN_ENFORCE_MFA' - ): - logout(self.request) - raise exceptions.PermissionDenied('MFA required for this user') - return ret - - @extend_schema_view( post=extend_schema( responses={200: OpenApiResponse(description='User successfully logged out')} ) ) -class Logout(LogoutView): +class Logout(APIView): """API view for logging out via API.""" serializer_class = None diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 4dd4af8b4f..4de3ab4a4c 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -33,7 +33,6 @@ django-weasyprint # django weasyprint integration djangorestframework # DRF framework djangorestframework-simplejwt[crypto] # JWT authentication django-xforwardedfor-middleware # IP forwarding metadata -dj-rest-auth # Authentication API endpoints dulwich # pure Python git integration drf-spectacular # DRF API documentation feedparser # RSS newsfeed parser diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 1dca2b204d..81012cc7cb 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -327,13 +327,10 @@ diff-match-patch==20230430 \ --hash=sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c \ --hash=sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93 # via django-import-export -dj-rest-auth==6.0.0 \ - --hash=sha256:760b45f3a07cd6182e6a20fe07d0c55230c5f950167df724d7914d0dd8c50133 django==4.2.12 \ --hash=sha256:6a6b4aff8a2db2dc7dcc5650cb2c7a7a0d1eb38e2aa2335fdf001e41801e9797 \ --hash=sha256:7640e86835d44ae118c2916a803d8081f40e214ee18a5a92a0202994ca60a4b4 # via - # dj-rest-auth # django-allauth # django-cors-headers # django-dbbackup @@ -455,7 +452,6 @@ djangorestframework==3.14.0 \ --hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \ --hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08 # via - # dj-rest-auth # djangorestframework-simplejwt # drf-spectacular djangorestframework-simplejwt[crypto]==5.3.1 \