Skip to content

Commit 292b0e0

Browse files
committed
Add tests for oidc usernames
1 parent ad97d97 commit 292b0e0

File tree

3 files changed

+101
-3
lines changed

3 files changed

+101
-3
lines changed

test/integration/oidc/galaxy-realm-export.json

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,40 @@
483483
"notBefore": 0,
484484
"groups": []
485485
},
486+
{
487+
"id": "f5e8b2c4-9a1d-4e3f-8c6a-7b2d5e4f3c1a",
488+
"createdTimestamp": 1694376671733,
489+
"username": "rincewind_test",
490+
"enabled": true,
491+
"totp": false,
492+
"emailVerified": true,
493+
"firstName": "Rincewind",
494+
"lastName": "Ankh-Morpork",
495+
"email": "[email protected]",
496+
"attributes": {
497+
"preferred_username": [
498+
"Rincewind (Ankh-Morpork)"
499+
]
500+
},
501+
"credentials": [
502+
{
503+
"id": "e5d4c3b2-a1f0-9e8d-7c6b-5a4f3e2d1c0b",
504+
"type": "password",
505+
"userLabel": "My password",
506+
"createdDate": 1694376754826,
507+
"secretData": "{\"value\":\"uNBI+UnpCLpXWHhm/tPSnnhuINiNw2MNt1XeDmImJaQ=\",\"salt\":\"fHS/FpnORylnSIco16UHwA==\",\"additionalParameters\":{}}",
508+
"credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
509+
}
510+
],
511+
"disableableCredentialTypes": [],
512+
"requiredActions": [],
513+
"realmRoles": [
514+
"galaxy-access-role",
515+
"default-roles-gxyrealm"
516+
],
517+
"notBefore": 0,
518+
"groups": []
519+
},
486520
{
487521
"id": "24ffa3ff-d351-4d5e-b10b-8d615082ec9d",
488522
"createdTimestamp": 1694376671733,
@@ -2565,4 +2599,4 @@
25652599
"clientPolicies": {
25662600
"policies": []
25672601
}
2568-
}
2602+
}

test/integration/oidc/test_auth_oidc.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,24 @@ def test_oidc_login_new_user(self):
261261
self._assert_status_code_is(response, 200)
262262
assert response.json()["email"] == "[email protected]"
263263

264+
def test_oidc_login_username_sanitization(self):
265+
"""Test that OIDC usernames with special characters are properly sanitized."""
266+
_, response = self._login_via_keycloak("rincewind_test", KEYCLOAK_TEST_PASSWORD, save_cookies=True)
267+
268+
response = self._get("users/current")
269+
self._assert_status_code_is(response, 200)
270+
assert response.json()["email"] == "[email protected]"
271+
272+
username = response.json()["username"]
273+
from galaxy.security.validate_user_input import validate_publicname_str
274+
275+
error = validate_publicname_str(username)
276+
assert error == "", f"OIDC-created username '{username}' is invalid: {error}"
277+
assert "(" not in username, f"Username '{username}' should not contain parentheses"
278+
assert ")" not in username, f"Username '{username}' should not contain parentheses"
279+
assert " " not in username, f"Username '{username}' should not contain spaces"
280+
assert username == username.lower(), f"Username '{username}' should be lowercase"
281+
264282
def test_oidc_login_repeat_no_notification(self):
265283
"""
266284
Test that repeat logins do NOT show the 'identity has been linked' notification.

test/unit/util/test_utils.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010

1111
from galaxy import util
12+
from galaxy.security.validate_user_input import validate_publicname_str
1213
from galaxy.util.json import safe_loads
1314

1415
SECTION_XML = """<?xml version="1.0" ?>
@@ -79,11 +80,13 @@ def test_parse_xml_enoent():
7980

8081

8182
def test_clean_multiline_string():
82-
x = util.clean_multiline_string("""
83+
x = util.clean_multiline_string(
84+
"""
8385
a
8486
b
8587
c
86-
""")
88+
"""
89+
)
8790
assert x == "a\nb\nc\n"
8891

8992

@@ -177,3 +180,46 @@ def test_validate_doi_fail_too_long():
177180
doi = f"doi:10.1000/{long_suffix}"
178181
assert util.validate_doi(doi)
179182
assert not util.validate_doi(doi + "a") # Increase length by 1 past max limit
183+
184+
185+
@pytest.mark.parametrize(
186+
"input_name,expected_output",
187+
[
188+
# Existing documented behavior
189+
("My Cool Object", "My-Cool-Object"),
190+
("!My Cool Object!", "My-Cool-Object"),
191+
("Hello\u20a9\u25ce\u0491\u029f\u217e", "Hello"),
192+
# Additional edge cases
193+
("simple", "simple"),
194+
("UPPERCASE", "UPPERCASE"), # Note: lowercase applied separately
195+
("with-dash", "with-dash"),
196+
("with spaces", "with-spaces"),
197+
(" multiple spaces ", "-multiple-spaces"),
198+
("trailing!", "trailing"),
199+
("!leading", "leading"),
200+
("special@#$chars", "specialchars"),
201+
("parentheses(test)", "parenthesestest"),
202+
("Rincewind (Ankh-Morpork)", "Rincewind-Ankh-Morpork"),
203+
],
204+
)
205+
def test_ready_name_for_url(input_name, expected_output):
206+
"""Test that ready_name_for_url correctly sanitizes names for URL use."""
207+
assert util.ready_name_for_url(input_name) == expected_output
208+
209+
210+
@pytest.mark.parametrize(
211+
"input_name",
212+
[
213+
"johndoe",
214+
"John Doe",
215+
"Rincewind (Ankh-Morpork)",
216+
217+
"Hello\u20a9\u25ce\u0491\u029f\u217e",
218+
" spaced ",
219+
],
220+
)
221+
def test_ready_name_for_url_produces_valid_usernames(input_name):
222+
"""Test that ready_name_for_url().lower() produces valid Galaxy usernames."""
223+
sanitized = util.ready_name_for_url(input_name).lower()
224+
error = validate_publicname_str(sanitized)
225+
assert error == "", f"'{input_name}' -> '{sanitized}' failed validation: {error}"

0 commit comments

Comments
 (0)