2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-02 13:28:49 +00:00

Fix REST registration endpoint (#8738)

* Re-add html account base
Fixes #8690

* fix base template

* override dj-rest-auth pattern to fix fixed token model reference

* pin req

* fix urls.py

* move definition out to separate file

* fix possible issues where email is not enabled but UI shows that registration is enabled

* fix import order

* fix token recovery

* make sure registration redirects

* fix name change

* fix import name

* adjust description

* cleanup

* bum api version

* add test for registration

* add test for registration requirements
This commit is contained in:
Matthias Mair 2024-12-24 01:53:25 +01:00 committed by GitHub
parent 8fcebefa0b
commit 7f1cc4658b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 147 additions and 16 deletions

View File

@ -1,13 +1,16 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 293 INVENTREE_API_VERSION = 294
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v294 - 2024-12-23 : https://github.com/inventree/InvenTree/pull/8738
- Extends registration API documentation
v293 - 2024-12-14 : https://github.com/inventree/InvenTree/pull/8658 v293 - 2024-12-14 : https://github.com/inventree/InvenTree/pull/8658
- Adds new fields to the supplier barcode API endpoints - Adds new fields to the supplier barcode API endpoints

View File

@ -0,0 +1,40 @@
"""Overrides for registration view."""
from django.utils.translation import gettext_lazy as _
from allauth.account import app_settings as allauth_account_settings
from dj_rest_auth.app_settings import api_settings
from dj_rest_auth.registration.views import RegisterView
class CustomRegisterView(RegisterView):
"""Registers a new user.
Accepts the following POST parameters: username, email, password1, password2.
"""
# Fixes https://github.com/inventree/InvenTree/issues/8707
# This contains code from dj-rest-auth 7.0 - therefore the version was pinned
def get_response_data(self, user):
"""Override to fix check for auth_model."""
if (
allauth_account_settings.EMAIL_VERIFICATION
== allauth_account_settings.EmailVerificationMethod.MANDATORY
):
return {'detail': _('Verification e-mail sent.')}
if api_settings.USE_JWT:
data = {
'user': user,
'access': self.access_token,
'refresh': self.refresh_token,
}
return api_settings.JWT_SERIALIZER(
data, context=self.get_serializer_context()
).data
elif self.token_model:
# Only change in this block is below
return api_settings.TOKEN_SERIALIZER(
user.api_tokens.last(), context=self.get_serializer_context()
).data
return None

View File

@ -17,7 +17,9 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth_2fa.adapter import OTPAdapter from allauth_2fa.adapter import OTPAdapter
from allauth_2fa.forms import TOTPDeviceForm from allauth_2fa.forms import TOTPDeviceForm
from allauth_2fa.utils import user_has_valid_totp_device from allauth_2fa.utils import user_has_valid_totp_device
from dj_rest_auth.registration.serializers import RegisterSerializer from dj_rest_auth.registration.serializers import (
RegisterSerializer as DjRestRegisterSerializer,
)
from rest_framework import serializers from rest_framework import serializers
import InvenTree.helpers_model import InvenTree.helpers_model
@ -264,16 +266,11 @@ class CustomSocialAccountAdapter(
# override dj-rest-auth # override dj-rest-auth
class CustomRegisterSerializer(RegisterSerializer): class RegisterSerializer(DjRestRegisterSerializer):
"""Override of serializer to use dynamic settings.""" """Registration requires email, password (twice) and username."""
email = serializers.EmailField() 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): def save(self, request):
"""Override to check if registration is open.""" """Override to check if registration is open."""
if registration_enabled(): if registration_enabled():

View File

@ -585,12 +585,10 @@ REST_AUTH = {
'TOKEN_MODEL': 'users.models.ApiToken', 'TOKEN_MODEL': 'users.models.ApiToken',
'TOKEN_CREATOR': 'users.models.default_create_token', 'TOKEN_CREATOR': 'users.models.default_create_token',
'USE_JWT': USE_JWT, 'USE_JWT': USE_JWT,
'REGISTER_SERIALIZER': 'InvenTree.auth_overrides.RegisterSerializer',
} }
OLD_PASSWORD_FIELD_ENABLED = True OLD_PASSWORD_FIELD_ENABLED = True
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'InvenTree.auth_overrides.CustomRegisterSerializer'
}
# JWT settings - rest_framework_simplejwt # JWT settings - rest_framework_simplejwt
if USE_JWT: if USE_JWT:

View File

@ -18,6 +18,7 @@ from rest_framework.response import Response
import InvenTree.sso import InvenTree.sso
from common.settings import get_global_setting from common.settings import get_global_setting
from InvenTree.auth_overrides import registration_enabled
from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI
from InvenTree.serializers import EmptySerializer, InvenTreeModelSerializer from InvenTree.serializers import EmptySerializer, InvenTreeModelSerializer
@ -204,7 +205,7 @@ class SocialProviderListView(ListAPI):
and get_global_setting('LOGIN_ENFORCE_MFA'), and get_global_setting('LOGIN_ENFORCE_MFA'),
'mfa_enabled': settings.MFA_ENABLED, 'mfa_enabled': settings.MFA_ENABLED,
'providers': provider_list, 'providers': provider_list,
'registration_enabled': get_global_setting('LOGIN_ENABLE_REG'), 'registration_enabled': registration_enabled(),
'password_forgotten_enabled': get_global_setting('LOGIN_ENABLE_PWD_FORGOT'), 'password_forgotten_enabled': get_global_setting('LOGIN_ENABLE_PWD_FORGOT'),
} }
return Response(data) return Response(data)

View File

@ -1,6 +1,8 @@
"""Test the sso module functionality.""" """Test the sso and auth module functionality."""
from django.conf import settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.test import override_settings from django.test import override_settings
from django.test.testcases import TransactionTestCase from django.test.testcases import TransactionTestCase
@ -9,6 +11,7 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
from InvenTree import sso from InvenTree import sso
from InvenTree.auth_overrides import RegistratonMixin from InvenTree.auth_overrides import RegistratonMixin
from InvenTree.unit_test import InvenTreeAPITestCase
class Dummy: class Dummy:
@ -119,3 +122,90 @@ class TestSsoGroupSync(TransactionTestCase):
self.assertEqual(Group.objects.filter(name='inventree_group').count(), 0) self.assertEqual(Group.objects.filter(name='inventree_group').count(), 0)
sso.ensure_sso_groups(None, self.sociallogin) sso.ensure_sso_groups(None, self.sociallogin)
self.assertEqual(Group.objects.filter(name='inventree_group').count(), 1) self.assertEqual(Group.objects.filter(name='inventree_group').count(), 1)
class EmailSettingsContext:
"""Context manager to enable email settings for tests."""
def __enter__(self):
"""Enable stuff."""
InvenTreeSetting.set_setting('LOGIN_ENABLE_REG', True)
settings.EMAIL_HOST = 'localhost'
def __exit__(self, type, value, traceback):
"""Exit stuff."""
InvenTreeSetting.set_setting('LOGIN_ENABLE_REG', False)
settings.EMAIL_HOST = ''
class TestAuth(InvenTreeAPITestCase):
"""Test authentication functionality."""
def email_args(self, user=None, email=None):
"""Generate registration arguments."""
return {
'username': user or 'user1',
'email': email or 'test@example.com',
'password1': '#asdf1234',
'password2': '#asdf1234',
}
def test_registration(self):
"""Test the registration process."""
self.logout()
# Duplicate username
resp = self.post(
'/api/auth/registration/',
self.email_args(user='testuser'),
expected_code=400,
)
self.assertIn(
'A user with that username already exists.', resp.data['username']
)
# Registration is disabled
resp = self.post(
'/api/auth/registration/', self.email_args(), expected_code=400
)
self.assertIn('Registration is disabled.', resp.data['non_field_errors'])
# Enable registration - now it should work
with EmailSettingsContext():
resp = self.post(
'/api/auth/registration/', self.email_args(), expected_code=201
)
self.assertIn('key', resp.data)
def test_registration_email(self):
"""Test that LOGIN_SIGNUP_MAIL_RESTRICTION works."""
self.logout()
# Check the setting validation is working
with self.assertRaises(ValidationError):
InvenTreeSetting.set_setting(
'LOGIN_SIGNUP_MAIL_RESTRICTION', 'example.com,inventree.org'
)
# Setting setting correctly
correct_setting = '@example.com,@inventree.org'
InvenTreeSetting.set_setting('LOGIN_SIGNUP_MAIL_RESTRICTION', correct_setting)
self.assertEqual(
InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_RESTRICTION'),
correct_setting,
)
# Wrong email format
resp = self.post(
'/api/auth/registration/',
self.email_args(email='admin@invenhost.com'),
expected_code=400,
)
self.assertIn('The provided email domain is not approved.', resp.data['email'])
# Right format should work
with EmailSettingsContext():
resp = self.post(
'/api/auth/registration/', self.email_args(), expected_code=201
)
self.assertIn('key', resp.data)

View File

@ -29,6 +29,7 @@ import plugin.api
import report.api import report.api
import stock.api import stock.api
import users.api import users.api
from InvenTree.auth_override_views import CustomRegisterView
from plugin.urls import get_plugin_urls from plugin.urls import get_plugin_urls
from web.urls import api_urls as web_api_urls from web.urls import api_urls as web_api_urls
from web.urls import urlpatterns as platform_urls from web.urls import urlpatterns as platform_urls
@ -113,6 +114,7 @@ apipatterns = [
ConfirmEmailView.as_view(), ConfirmEmailView.as_view(),
name='account_confirm_email', name='account_confirm_email',
), ),
path('registration/', CustomRegisterView.as_view(), name='rest_register'),
path('registration/', include('dj_rest_auth.registration.urls')), path('registration/', include('dj_rest_auth.registration.urls')),
path( path(
'providers/', SocialProviderListView.as_view(), name='social_providers' 'providers/', SocialProviderListView.as_view(), name='social_providers'

View File

@ -31,7 +31,7 @@ django-weasyprint # django weasyprint integration
djangorestframework<3.15 # DRF framework # FIXED 2024-06-26 see https://github.com/inventree/InvenTree/pull/7521 djangorestframework<3.15 # DRF framework # FIXED 2024-06-26 see https://github.com/inventree/InvenTree/pull/7521
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 dj-rest-auth==7.0.0 # Authentication API endpoints # FIXED 2024-12-22 due to https://github.com/inventree/InvenTree/issues/8707
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

View File

@ -198,7 +198,7 @@ export function RegistrationForm() {
headers: { Authorization: '' } headers: { Authorization: '' }
}) })
.then((ret) => { .then((ret) => {
if (ret?.status === 204) { if (ret?.status === 204 || ret?.status === 201) {
setIsRegistering(false); setIsRegistering(false);
showLoginNotification({ showLoginNotification({
title: t`Registration successful`, title: t`Registration successful`,