From 08dc17799adec0cb464746c6fd635a62ad8832c8 Mon Sep 17 00:00:00 2001 From: Eliana Rosselli Date: Fri, 17 Apr 2026 11:48:24 -0300 Subject: [PATCH 1/4] ENG-3466: Add username character validation on user creation Restrict usernames to alphanumeric characters, underscores, and hyphens (1-100 chars) to prevent special characters from breaking invite links. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/fides/api/schemas/user.py | 11 ++++++--- tests/lib/test_oauth_schemas_user.py | 37 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/fides/api/schemas/user.py b/src/fides/api/schemas/user.py index bf35a54cde7..ae1b0ed59c9 100644 --- a/src/fides/api/schemas/user.py +++ b/src/fides/api/schemas/user.py @@ -15,6 +15,8 @@ from fides.api.models.fides_user import FidesUser from fides.api.models.fides_user_invite import FidesUserInvite +USERNAME_PATTERN = r"[a-zA-Z0-9_-]{1,100}" + class PrivacyRequestUser(FidesSchema): """Data we can expose via PrivacyRequest user relations (reviewer, submitter, etc.)""" @@ -38,9 +40,12 @@ class UserCreate(FidesSchema): @field_validator("username") @classmethod def validate_username(cls, username: str) -> str: - """Ensure username does not have spaces.""" - if " " in username: - raise ValueError("Usernames cannot have spaces.") + """Ensure username contains only valid characters and is within length limits.""" + if not re.fullmatch(USERNAME_PATTERN, username): + raise ValueError( + "Usernames must be 1-100 characters and may only contain " + "letters, numbers, underscores, and hyphens." + ) return username @field_validator("password") diff --git a/tests/lib/test_oauth_schemas_user.py b/tests/lib/test_oauth_schemas_user.py index b6a196103eb..0b7b3b8260e 100644 --- a/tests/lib/test_oauth_schemas_user.py +++ b/tests/lib/test_oauth_schemas_user.py @@ -30,10 +30,21 @@ def test_bad_password(password, message): assert message in str(excinfo.value) -def test_user_create_user_name_with_spaces(): +@pytest.mark.parametrize( + "username", + [ + "some user", + "user&name", + "user=name", + "user Date: Fri, 17 Apr 2026 11:49:36 -0300 Subject: [PATCH 2/4] Add changelog for PR #7953 Co-Authored-By: Claude Opus 4.6 (1M context) --- changelog/7953-username-validation.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/7953-username-validation.yaml diff --git a/changelog/7953-username-validation.yaml b/changelog/7953-username-validation.yaml new file mode 100644 index 00000000000..3cb3cc5d925 --- /dev/null +++ b/changelog/7953-username-validation.yaml @@ -0,0 +1,4 @@ +type: Fixed +description: Added character and length validation to usernames on user creation +pr: 7953 +labels: [] From 54beadc3c044007a3f2d169840535c882558ed19 Mon Sep 17 00:00:00 2001 From: Eliana Rosselli Date: Fri, 17 Apr 2026 12:21:18 -0300 Subject: [PATCH 3/4] Allow dots in usernames and add @ to invalid test cases Co-Authored-By: Claude Opus 4.6 (1M context) --- src/fides/api/schemas/user.py | 2 +- tests/lib/test_oauth_schemas_user.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fides/api/schemas/user.py b/src/fides/api/schemas/user.py index ae1b0ed59c9..f48660a88a8 100644 --- a/src/fides/api/schemas/user.py +++ b/src/fides/api/schemas/user.py @@ -15,7 +15,7 @@ from fides.api.models.fides_user import FidesUser from fides.api.models.fides_user_invite import FidesUserInvite -USERNAME_PATTERN = r"[a-zA-Z0-9_-]{1,100}" +USERNAME_PATTERN = r"[a-zA-Z0-9._-]{1,100}" class PrivacyRequestUser(FidesSchema): diff --git a/tests/lib/test_oauth_schemas_user.py b/tests/lib/test_oauth_schemas_user.py index 0b7b3b8260e..9097a6640c5 100644 --- a/tests/lib/test_oauth_schemas_user.py +++ b/tests/lib/test_oauth_schemas_user.py @@ -37,6 +37,7 @@ def test_bad_password(password, message): "user&name", "user=name", "user Date: Fri, 17 Apr 2026 13:10:21 -0300 Subject: [PATCH 4/4] Address review comments: update error message and tighten test assertion - Include periods in the validation error message to match the pattern - Add match="Usernames must be" to pytest.raises for specificity Co-Authored-By: Claude Opus 4.6 (1M context) --- src/fides/api/schemas/user.py | 2 +- tests/lib/test_oauth_schemas_user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fides/api/schemas/user.py b/src/fides/api/schemas/user.py index f48660a88a8..bbc086bbe8a 100644 --- a/src/fides/api/schemas/user.py +++ b/src/fides/api/schemas/user.py @@ -44,7 +44,7 @@ def validate_username(cls, username: str) -> str: if not re.fullmatch(USERNAME_PATTERN, username): raise ValueError( "Usernames must be 1-100 characters and may only contain " - "letters, numbers, underscores, and hyphens." + "letters, numbers, periods, underscores, and hyphens." ) return username diff --git a/tests/lib/test_oauth_schemas_user.py b/tests/lib/test_oauth_schemas_user.py index 9097a6640c5..5463013f66b 100644 --- a/tests/lib/test_oauth_schemas_user.py +++ b/tests/lib/test_oauth_schemas_user.py @@ -43,7 +43,7 @@ def test_bad_password(password, message): ], ) def test_user_create_invalid_username(username): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Usernames must be"): UserCreate( username=username, password=str_to_b64_str("Testtest1!"),