From 09de014a4300ab113a16db9abf3ee815d59d73aa Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Fri, 29 Aug 2025 15:59:06 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(back)=20allow=20ASCII=20characters?= =?UTF-8?q?=20in=20user=20sub=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All ASCII characters are allowed in a sub, we change the sub validator to reflect this. --- CHANGELOG.md | 1 + src/backend/core/api/serializers.py | 4 ++-- .../0024_add_is_masked_field_to_link_trace.py | 15 +++++++++++++ src/backend/core/models.py | 17 ++++---------- .../test_api_documents_create_for_owner.py | 7 ++---- src/backend/core/tests/test_models_users.py | 22 +++++++++++++++++++ src/backend/core/validators.py | 9 ++++++++ 7 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 src/backend/core/validators.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 85d2e4b2..0d947c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to - #1270 - #1282 - ♻️(backend) fallback to email identifier when no name #1298 +- 🐛(backend) allow ASCII characters in user sub field #1295 ### Fixed diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index 863db9f1..9b843908 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _ import magic from rest_framework import serializers -from core import choices, enums, models, utils +from core import choices, enums, models, utils, validators from core.services.ai_services import AI_ACTIONS from core.services.converter_services import ( ConversionError, @@ -422,7 +422,7 @@ class ServerCreateDocumentSerializer(serializers.Serializer): content = serializers.CharField(required=True) # User sub = serializers.CharField( - required=True, validators=[models.User.sub_validator], max_length=255 + required=True, validators=[validators.sub_validator], max_length=255 ) email = serializers.EmailField(required=True) language = serializers.ChoiceField( diff --git a/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py b/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py index c8a68b29..87cb2b44 100644 --- a/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py +++ b/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py @@ -2,6 +2,8 @@ from django.db import migrations, models +import core.validators + class Migration(migrations.Migration): dependencies = [ @@ -33,4 +35,17 @@ class Migration(migrations.Migration): verbose_name="language", ), ), + migrations.AlterField( + model_name="user", + name="sub", + field=models.CharField( + blank=True, + help_text="Required. 255 characters or fewer. ASCII characters only.", + max_length=255, + null=True, + unique=True, + validators=[core.validators.sub_validator], + verbose_name="sub", + ), + ), ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index a1182964..8822c813 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -14,7 +14,7 @@ from django.contrib.auth import models as auth_models from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.postgres.fields import ArrayField from django.contrib.sites.models import Site -from django.core import mail, validators +from django.core import mail from django.core.cache import cache from django.core.files.base import ContentFile from django.core.files.storage import default_storage @@ -39,6 +39,7 @@ from .choices import ( RoleChoices, get_equivalent_link_definition, ) +from .validators import sub_validator logger = getLogger(__name__) @@ -136,22 +137,12 @@ class UserManager(auth_models.UserManager): class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): """User model to work with OIDC only authentication.""" - sub_validator = validators.RegexValidator( - regex=r"^[\w.@+-:]+\Z", - message=_( - "Enter a valid sub. This value may contain only letters, " - "numbers, and @/./+/-/_/: characters." - ), - ) - sub = models.CharField( _("sub"), - help_text=_( - "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only." - ), + help_text=_("Required. 255 characters or fewer. ASCII characters only."), max_length=255, - unique=True, validators=[sub_validator], + unique=True, blank=True, null=True, ) diff --git a/src/backend/core/tests/documents/test_api_documents_create_for_owner.py b/src/backend/core/tests/documents/test_api_documents_create_for_owner.py index cbe35b99..346fe407 100644 --- a/src/backend/core/tests/documents/test_api_documents_create_for_owner.py +++ b/src/backend/core/tests/documents/test_api_documents_create_for_owner.py @@ -148,7 +148,7 @@ def test_api_documents_create_for_owner_invalid_sub(): data = { "title": "My Document", "content": "Document content", - "sub": "123!!", + "sub": "invalid süb", "email": "john.doe@example.com", } @@ -163,10 +163,7 @@ def test_api_documents_create_for_owner_invalid_sub(): assert not Document.objects.exists() assert response.json() == { - "sub": [ - "Enter a valid sub. This value may contain only letters, " - "numbers, and @/./+/-/_/: characters." - ] + "sub": ["Enter a valid sub. This value should be ASCII only."] } diff --git a/src/backend/core/tests/test_models_users.py b/src/backend/core/tests/test_models_users.py index edea5bb9..e7be9267 100644 --- a/src/backend/core/tests/test_models_users.py +++ b/src/backend/core/tests/test_models_users.py @@ -44,3 +44,25 @@ def test_models_users_send_mail_main_missing(): user.email_user("my subject", "my message") assert str(excinfo.value) == "User has no email address." + + +@pytest.mark.parametrize( + "sub,is_valid", + [ + ("valid_sub.@+-:=/", True), + ("invalid süb", False), + (12345, True), + ], +) +def test_models_users_sub_validator(sub, is_valid): + """The "sub" field should be validated.""" + user = factories.UserFactory() + user.sub = sub + if is_valid: + user.full_clean() + else: + with pytest.raises( + ValidationError, + match=("Enter a valid sub. This value should be ASCII only."), + ): + user.full_clean() diff --git a/src/backend/core/validators.py b/src/backend/core/validators.py new file mode 100644 index 00000000..4b6c7a0b --- /dev/null +++ b/src/backend/core/validators.py @@ -0,0 +1,9 @@ +"""Custom validators for the core app.""" + +from django.core.exceptions import ValidationError + + +def sub_validator(value): + """Validate that the sub is ASCII only.""" + if not value.isascii(): + raise ValidationError("Enter a valid sub. This value should be ASCII only.")