diff --git a/roles/user-administrator/meta/users.yml b/roles/user-administrator/meta/users.yml index 5c04da06f2..68d9afce49 100644 --- a/roles/user-administrator/meta/users.yml +++ b/roles/user-administrator/meta/users.yml @@ -3,7 +3,7 @@ platform_administrator: description: Application administrator for {{ ORGANIZATION }} on {{ DOMAIN_PRIMARY }} firstname: Platform lastname: Administrator - username: platformadministrator + username: platformadmin authorized_keys: [] accounts: [mailbox, identity] diff --git a/tests/lint/ansible/roles/meta/test_identity_username_charset.py b/tests/lint/ansible/roles/meta/test_identity_username_charset.py index 968acb2cd1..98ea25d70e 100644 --- a/tests/lint/ansible/roles/meta/test_identity_username_charset.py +++ b/tests/lint/ansible/roles/meta/test_identity_username_charset.py @@ -8,6 +8,7 @@ from utils.cache.users import get_user_defaults from . import PROJECT_ROOT IDENTITY_USERNAME = re.compile(r"^[a-z0-9]+$") +IDENTITY_USERNAME_MAX = 20 class TestIdentityUsernameCharset(unittest.TestCase): @@ -29,20 +30,35 @@ class TestIdentityUsernameCharset(unittest.TestCase): for a compound account name and every other hyphenated user in the repository is exempt by having no ``identity`` account, so nothing catches it until a realm import or a tootctl call runs. + + The length ceiling is Discourse's, which answers a longer name with + ``Validation failed: Username must be no more than 20 characters + (ActiveRecord::RecordInvalid)``. It sits behind the character rule: + dropping a hyphen shortens a name by one, so a compound name that + was too long stays too long, and the second consumer only speaks up + once the first is satisfied. """ users = get_user_defaults(roles_dir=str(PROJECT_ROOT / "roles")) - offenders = sorted( - f"{key}: {entry['username']}" - for key, entry in users.items() - if "identity" in (entry.get("accounts") or []) - and not IDENTITY_USERNAME.match(str(entry.get("username", ""))) - ) + offenders: list[str] = [] + for key, entry in sorted(users.items()): + if "identity" not in (entry.get("accounts") or []): + continue + username = str(entry.get("username", "")) + if not IDENTITY_USERNAME.match(username): + offenders.append( + f"{key}: {username!r} is not lowercase alphanumeric" + ) + elif len(username) > IDENTITY_USERNAME_MAX: + offenders.append( + f"{key}: {username!r} is {len(username)} characters, " + f"over the {IDENTITY_USERNAME_MAX} Discourse allows" + ) if offenders: self.fail( "These users are registered as an identity but carry a " - "username Keycloak and Mastodon both refuse (lowercase " - "letters and digits only):\n - " + "\n - ".join(offenders) + "username one of Keycloak, Mastodon or Discourse refuses:\n" + " - " + "\n - ".join(offenders) )