🐛(back) allow ASCII characters in user sub field

All ASCII characters are allowed in a sub, we change the sub validator
to reflect this.
This commit is contained in:
Manuel Raynaud
2025-08-29 15:59:06 +02:00
committed by GitHub
parent 8d42149304
commit 09de014a43
7 changed files with 55 additions and 20 deletions

View File

@@ -25,6 +25,7 @@ and this project adheres to
- #1270 - #1270
- #1282 - #1282
- ♻️(backend) fallback to email identifier when no name #1298 - ♻️(backend) fallback to email identifier when no name #1298
- 🐛(backend) allow ASCII characters in user sub field #1295
### Fixed ### Fixed

View File

@@ -13,7 +13,7 @@ from django.utils.translation import gettext_lazy as _
import magic import magic
from rest_framework import serializers 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.ai_services import AI_ACTIONS
from core.services.converter_services import ( from core.services.converter_services import (
ConversionError, ConversionError,
@@ -422,7 +422,7 @@ class ServerCreateDocumentSerializer(serializers.Serializer):
content = serializers.CharField(required=True) content = serializers.CharField(required=True)
# User # User
sub = serializers.CharField( 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) email = serializers.EmailField(required=True)
language = serializers.ChoiceField( language = serializers.ChoiceField(

View File

@@ -2,6 +2,8 @@
from django.db import migrations, models from django.db import migrations, models
import core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
@@ -33,4 +35,17 @@ class Migration(migrations.Migration):
verbose_name="language", 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",
),
),
] ]

View File

@@ -14,7 +14,7 @@ from django.contrib.auth import models as auth_models
from django.contrib.auth.base_user import AbstractBaseUser from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.contrib.sites.models import Site 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.cache import cache
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
@@ -39,6 +39,7 @@ from .choices import (
RoleChoices, RoleChoices,
get_equivalent_link_definition, get_equivalent_link_definition,
) )
from .validators import sub_validator
logger = getLogger(__name__) logger = getLogger(__name__)
@@ -136,22 +137,12 @@ class UserManager(auth_models.UserManager):
class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin): class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
"""User model to work with OIDC only authentication.""" """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 = models.CharField(
_("sub"), _("sub"),
help_text=_( help_text=_("Required. 255 characters or fewer. ASCII characters only."),
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
),
max_length=255, max_length=255,
unique=True,
validators=[sub_validator], validators=[sub_validator],
unique=True,
blank=True, blank=True,
null=True, null=True,
) )

View File

@@ -148,7 +148,7 @@ def test_api_documents_create_for_owner_invalid_sub():
data = { data = {
"title": "My Document", "title": "My Document",
"content": "Document content", "content": "Document content",
"sub": "123!!", "sub": "invalid süb",
"email": "john.doe@example.com", "email": "john.doe@example.com",
} }
@@ -163,10 +163,7 @@ def test_api_documents_create_for_owner_invalid_sub():
assert not Document.objects.exists() assert not Document.objects.exists()
assert response.json() == { assert response.json() == {
"sub": [ "sub": ["Enter a valid sub. This value should be ASCII only."]
"Enter a valid sub. This value may contain only letters, "
"numbers, and @/./+/-/_/: characters."
]
} }

View File

@@ -44,3 +44,25 @@ def test_models_users_send_mail_main_missing():
user.email_user("my subject", "my message") user.email_user("my subject", "my message")
assert str(excinfo.value) == "User has no email address." 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()

View File

@@ -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.")