mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
remove more old stuff
This commit is contained in:
@ -15,8 +15,6 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
|||||||
from crispy_forms.bootstrap import AppendedText, PrependedAppendedText, PrependedText
|
from crispy_forms.bootstrap import AppendedText, PrependedAppendedText, PrependedText
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Field, Layout
|
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.helpers_model
|
||||||
import InvenTree.sso
|
import InvenTree.sso
|
||||||
@ -341,21 +339,3 @@ class CustomSocialAccountAdapter(
|
|||||||
# Log the error to the database
|
# Log the error to the database
|
||||||
log_error(path, error_name=error, error_data=exception)
|
log_error(path, error_name=error, error_data=exception)
|
||||||
logger.error("SSO error for provider '%s' - check admin error log", provider_id)
|
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.'))
|
|
||||||
|
@ -235,8 +235,6 @@ INSTALLED_APPS = [
|
|||||||
'django_otp', # OTP is needed for MFA - base package
|
'django_otp', # OTP is needed for MFA - base package
|
||||||
'django_otp.plugins.otp_totp', # Time based OTP
|
'django_otp.plugins.otp_totp', # Time based OTP
|
||||||
'django_otp.plugins.otp_static', # Backup codes
|
'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
|
'drf_spectacular', # API documentation
|
||||||
'django_ical', # For exporting calendars
|
'django_ical', # For exporting calendars
|
||||||
]
|
]
|
||||||
@ -484,33 +482,6 @@ if DEBUG:
|
|||||||
'rest_framework.renderers.BrowsableAPIRenderer'
|
'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 default setting
|
||||||
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
||||||
|
|
||||||
|
@ -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()
|
|
@ -53,15 +53,6 @@ def check_provider(provider):
|
|||||||
return True
|
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:
|
def login_enabled() -> bool:
|
||||||
"""Return True if SSO login is enabled."""
|
"""Return True if SSO login is enabled."""
|
||||||
return str2bool(get_global_setting('LOGIN_ENABLE_SSO'))
|
return str2bool(get_global_setting('LOGIN_ENABLE_SSO'))
|
||||||
|
@ -10,11 +10,6 @@ from django.urls import include, path, re_path
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.generic.base import RedirectView
|
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 drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
|
||||||
from sesame.views import LoginView
|
from sesame.views import LoginView
|
||||||
|
|
||||||
@ -47,14 +42,6 @@ from .api import (
|
|||||||
VersionView,
|
VersionView,
|
||||||
)
|
)
|
||||||
from .magic_login import GetSimpleLoginView
|
from .magic_login import GetSimpleLoginView
|
||||||
from .social_auth_urls import (
|
|
||||||
EmailListView,
|
|
||||||
EmailPrimaryView,
|
|
||||||
EmailRemoveView,
|
|
||||||
EmailVerifyView,
|
|
||||||
SocialProviderListView,
|
|
||||||
social_auth_urlpatterns,
|
|
||||||
)
|
|
||||||
from .views import (
|
from .views import (
|
||||||
AboutView,
|
AboutView,
|
||||||
AppearanceSelectView,
|
AppearanceSelectView,
|
||||||
@ -130,58 +117,12 @@ apipatterns = [
|
|||||||
path(
|
path(
|
||||||
'auth/',
|
'auth/',
|
||||||
include([
|
include([
|
||||||
re_path(
|
|
||||||
r'^registration/account-confirm-email/(?P<key>[-:\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(
|
|
||||||
'<int:pk>/',
|
|
||||||
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/<int:pk>/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('logout/', users.api.Logout.as_view(), name='api-logout'),
|
||||||
path(
|
path(
|
||||||
'login-redirect/',
|
'login-redirect/',
|
||||||
users.api.LoginRedirect.as_view(),
|
users.api.LoginRedirect.as_view(),
|
||||||
name='api-login-redirect',
|
name='api-login-redirect',
|
||||||
),
|
),
|
||||||
path('', include('dj_rest_auth.urls')),
|
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
# Magic login URLs
|
# Magic login URLs
|
||||||
@ -410,7 +351,6 @@ classic_frontendpatterns = [
|
|||||||
path('stock/', include(stock_urls)),
|
path('stock/', include(stock_urls)),
|
||||||
path('supplier-part/', include(supplier_part_urls)),
|
path('supplier-part/', include(supplier_part_urls)),
|
||||||
path('edit-user/', EditUserView.as_view(), name='edit-user'),
|
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('index/', IndexView.as_view(), name='index'),
|
||||||
path('notifications/', include(notifications_urls)),
|
path('notifications/', include(notifications_urls)),
|
||||||
path('search/', SearchView.as_view(), name='search'),
|
path('search/', SearchView.as_view(), name='search'),
|
||||||
|
@ -405,58 +405,6 @@ class EditUserView(AjaxUpdateView):
|
|||||||
return self.request.user
|
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):
|
class IndexView(TemplateView):
|
||||||
"""View for InvenTree index page."""
|
"""View for InvenTree index page."""
|
||||||
|
|
||||||
|
@ -3,26 +3,17 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
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.contrib.auth.models import Group, User
|
||||||
from django.http.response import HttpResponse
|
from django.urls import include, path, re_path
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.urls import include, path, re_path, reverse
|
|
||||||
from django.views.generic.base import RedirectView
|
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 drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view
|
||||||
from rest_framework import exceptions, permissions
|
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.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
from common.models import InvenTreeSetting
|
|
||||||
from InvenTree.filters import SEARCH_ORDER_FILTER
|
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||||
from InvenTree.mixins import (
|
from InvenTree.mixins import (
|
||||||
ListAPI,
|
ListAPI,
|
||||||
@ -184,67 +175,12 @@ class GroupList(ListCreateAPI):
|
|||||||
ordering_fields = ['name']
|
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(
|
@extend_schema_view(
|
||||||
post=extend_schema(
|
post=extend_schema(
|
||||||
responses={200: OpenApiResponse(description='User successfully logged out')}
|
responses={200: OpenApiResponse(description='User successfully logged out')}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
class Logout(LogoutView):
|
class Logout(APIView):
|
||||||
"""API view for logging out via API."""
|
"""API view for logging out via API."""
|
||||||
|
|
||||||
serializer_class = None
|
serializer_class = None
|
||||||
|
@ -33,7 +33,6 @@ django-weasyprint # django weasyprint integration
|
|||||||
djangorestframework # DRF framework
|
djangorestframework # DRF framework
|
||||||
djangorestframework-simplejwt[crypto] # JWT authentication
|
djangorestframework-simplejwt[crypto] # JWT authentication
|
||||||
django-xforwardedfor-middleware # IP forwarding metadata
|
django-xforwardedfor-middleware # IP forwarding metadata
|
||||||
dj-rest-auth # Authentication API endpoints
|
|
||||||
dulwich # pure Python git integration
|
dulwich # pure Python git integration
|
||||||
drf-spectacular # DRF API documentation
|
drf-spectacular # DRF API documentation
|
||||||
feedparser # RSS newsfeed parser
|
feedparser # RSS newsfeed parser
|
||||||
|
@ -327,13 +327,10 @@ diff-match-patch==20230430 \
|
|||||||
--hash=sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c \
|
--hash=sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c \
|
||||||
--hash=sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93
|
--hash=sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93
|
||||||
# via django-import-export
|
# via django-import-export
|
||||||
dj-rest-auth==6.0.0 \
|
|
||||||
--hash=sha256:760b45f3a07cd6182e6a20fe07d0c55230c5f950167df724d7914d0dd8c50133
|
|
||||||
django==4.2.12 \
|
django==4.2.12 \
|
||||||
--hash=sha256:6a6b4aff8a2db2dc7dcc5650cb2c7a7a0d1eb38e2aa2335fdf001e41801e9797 \
|
--hash=sha256:6a6b4aff8a2db2dc7dcc5650cb2c7a7a0d1eb38e2aa2335fdf001e41801e9797 \
|
||||||
--hash=sha256:7640e86835d44ae118c2916a803d8081f40e214ee18a5a92a0202994ca60a4b4
|
--hash=sha256:7640e86835d44ae118c2916a803d8081f40e214ee18a5a92a0202994ca60a4b4
|
||||||
# via
|
# via
|
||||||
# dj-rest-auth
|
|
||||||
# django-allauth
|
# django-allauth
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
# django-dbbackup
|
# django-dbbackup
|
||||||
@ -455,7 +452,6 @@ djangorestframework==3.14.0 \
|
|||||||
--hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \
|
--hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \
|
||||||
--hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08
|
--hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08
|
||||||
# via
|
# via
|
||||||
# dj-rest-auth
|
|
||||||
# djangorestframework-simplejwt
|
# djangorestframework-simplejwt
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
djangorestframework-simplejwt[crypto]==5.3.1 \
|
djangorestframework-simplejwt[crypto]==5.3.1 \
|
||||||
|
Reference in New Issue
Block a user