From f94b0836222f395d2984410acfcd39a09e58bea2 Mon Sep 17 00:00:00 2001 From: _ <_@devhack.net> Date: Sun, 23 Mar 2025 05:04:41 +0000 Subject: [PATCH] Add option to allow registrations that begin with `_` This commit resolves a long-standing question dating back to Jira. > leonerd: Should this be a configuration option? We don't want to allow > new user signups beginning with `_` on matrix.org, because of clashes > with ASes; but maybe other sites wouldn't mind that? As this may result in clashes with AppService namespaces, it is disallowed by default. Homeservers which provision users from an external identity provider may find this useful. Fixes: SYN-738 --- changelog.d/18262.feature | 1 + .../configuration/config_documentation.md | 14 +++++++++++ synapse/config/registration.py | 4 ++++ synapse/handlers/register.py | 5 +++- tests/handlers/test_register.py | 23 +++++++++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 changelog.d/18262.feature diff --git a/changelog.d/18262.feature b/changelog.d/18262.feature new file mode 100644 index 0000000000..c8249faa76 --- /dev/null +++ b/changelog.d/18262.feature @@ -0,0 +1 @@ +Add option to allow registrations that begin with `_`. Contributed by `_` (@hex5f). diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md index d2d282f203..65b6d73755 100644 --- a/docs/usage/configuration/config_documentation.md +++ b/docs/usage/configuration/config_documentation.md @@ -2887,6 +2887,20 @@ Example configuration: inhibit_user_in_use_error: true ``` --- +### `allow_underscore_prefixed_registration` + +Whether users are allowed to register with a underscore-prefixed localpart. +By default, AppServices use prefixes like `_example` to namespace their +associated ghost users. If turned on, this may result in clashes or confusion. +Useful when provisioning users from an external identity provider. + +Defaults to false. + +Example configuration: +```yaml +allow_underscore_prefixed_registration: false +``` +--- ## User session management --- ### `session_lifetime` diff --git a/synapse/config/registration.py b/synapse/config/registration.py index 3cf7031656..8adf21079e 100644 --- a/synapse/config/registration.py +++ b/synapse/config/registration.py @@ -162,6 +162,10 @@ class RegistrationConfig(Config): "disable_msisdn_registration", False ) + self.allow_underscore_prefixed_localpart = config.get( + "allow_underscore_prefixed_localpart", False + ) + session_lifetime = config.get("session_lifetime") if session_lifetime is not None: session_lifetime = self.parse_duration(session_lifetime) diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index ecfea175c7..3e86349981 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -159,7 +159,10 @@ class RegistrationHandler: if not localpart: raise SynapseError(400, "User ID cannot be empty", Codes.INVALID_USERNAME) - if localpart[0] == "_": + if ( + localpart[0] == "_" + and not self.hs.config.registration.allow_underscore_prefixed_localpart + ): raise SynapseError( 400, "User ID may not begin with _", Codes.INVALID_USERNAME ) diff --git a/tests/handlers/test_register.py b/tests/handlers/test_register.py index 92487692db..39705d5e66 100644 --- a/tests/handlers/test_register.py +++ b/tests/handlers/test_register.py @@ -588,6 +588,29 @@ class RegistrationTestCase(unittest.HomeserverTestCase): d = self.store.is_support_user(user_id) self.assertFalse(self.get_success(d)) + def test_underscore_localpart_rejected_by_default(self) -> None: + for invalid_user_id in ("_", "_prefixed"): + with self.subTest(invalid_user_id=invalid_user_id): + self.get_failure( + self.handler.register_user(localpart=invalid_user_id), + SynapseError, + ) + + @override_config( + { + "allow_underscore_prefixed_localpart": "true", + } + ) + def test_underscore_localpart_allowed_if_configured(self) -> None: + for valid_user_id in ("_", "_prefixed"): + with self.subTest(valid_user_id=valid_user_id): + user_id = self.get_success( + self.handler.register_user( + localpart=valid_user_id, + ), + ) + self.assertEqual(user_id, f"@{valid_user_id}:test") + def test_invalid_user_id(self) -> None: invalid_user_id = "^abcd" self.get_failure(