From ae482e750bc50aaba4518d65253da35383dea3fa Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 9 Oct 2026 00:20:29 +0200 Subject: [PATCH] fix(web-app-keycloak): render the username cap instead of raising on it 7916c0177e wrote the cap as {{ IAM_USERNAME_MAX_LENGTH }} inside the {% set _base_attrs %} block. A set body is already a Jinja expression, so the nested delimiters are a syntax error rather than an interpolation, and jinja2 answers TemplateSyntaxError: expected token :, got }. realm.json.j2 includes the component unconditionally, so every realm render raised, not just the username path: that single typo is the whole of the 9 unit and 7 integration failures the gate reported. The test context gains the value because render() builds its environment with StrictUndefined, which raises on an absent name instead of rendering empty, and a new case asserts the rendered validations.length.max equals it so the cap cannot fall back to the Keycloak default of 255 unnoticed. Co-Authored-By: Claude Opus 5 (1M context) --- ...loak.userprofile.UserProfileProvider.json.j2 | 2 +- .../templates/test_realm_import_json.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/roles/web-app-keycloak/templates/import/components/org.keycloak.userprofile.UserProfileProvider.json.j2 b/roles/web-app-keycloak/templates/import/components/org.keycloak.userprofile.UserProfileProvider.json.j2 index 165eef7f8f..195a7f733d 100644 --- a/roles/web-app-keycloak/templates/import/components/org.keycloak.userprofile.UserProfileProvider.json.j2 +++ b/roles/web-app-keycloak/templates/import/components/org.keycloak.userprofile.UserProfileProvider.json.j2 @@ -26,7 +26,7 @@ "name": "username", "displayName": "${username}", "validations": { - "length": { "min": 3, "max": {{ IAM_USERNAME_MAX_LENGTH }} }, + "length": { "min": 3, "max": IAM_USERNAME_MAX_LENGTH }, "pattern": { "pattern": "^(?!(?:" ~ KEYCLOAK_RESERVED_USERNAMES_REGEX | replace('\\', '\\\\') ~ ")$)[a-z0-9]+$", "error-message": "Username is reserved or contains invalid characters. Only lowercase letters (a–z) and digits (0–9) are allowed." diff --git a/tests/unit/python/roles/web-app-keycloak/templates/test_realm_import_json.py b/tests/unit/python/roles/web-app-keycloak/templates/test_realm_import_json.py index 249189de47..bbfeff2dcf 100644 --- a/tests/unit/python/roles/web-app-keycloak/templates/test_realm_import_json.py +++ b/tests/unit/python/roles/web-app-keycloak/templates/test_realm_import_json.py @@ -41,6 +41,7 @@ CONTEXT = { "KEYCLOAK_LDAP_ENABLED": True, "KEYCLOAK_MOODLE_ENABLED": True, "KEYCLOAK_RESERVED_USERNAMES_REGEX": "^(root|admin)$", + "IAM_USERNAME_MAX_LENGTH": 20, "KEYCLOAK_LDAP_USER_OBJECT_CLASSES": "inetOrgPerson, organizationalPerson", "KEYCLOAK_LDAP_CMP_NAME": "ldap", "KEYCLOAK_LDAP_URL": "ldap://openldap:389", @@ -150,6 +151,22 @@ class TestRealmImportJson(unittest.TestCase): declared = {attribute["name"] for attribute in profile["attributes"]} self.assertIn("uidNumber", declared) + def test_the_username_length_follows_the_platform_limit(self) -> None: + """Registration must refuse what the tightest consumer refuses, so the + profile carries IAM_USERNAME_MAX_LENGTH rather than Keycloak's own 255.""" + realm = json.loads(render([])) + provider = realm["components"]["org.keycloak.userprofile.UserProfileProvider"] + profile = json.loads(provider[0]["config"]["kc.user.profile.config"][0]) + username = next( + attribute + for attribute in profile["attributes"] + if attribute["name"] == "username" + ) + self.assertEqual( + username["validations"]["length"]["max"], + CONTEXT["IAM_USERNAME_MAX_LENGTH"], + ) + def test_realm_is_valid_json_with_one_saml_app(self) -> None: realm = json.loads(render(["web-app-suitecrm"])) clients = {c["clientId"]: c for c in realm["clients"]}