From b2c20f29164ff0a6272cda6260b2b6bca55f9843 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 00000000000..c8249faa762 --- /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 d2d282f2037..65b6d737550 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 3cf70316560..8adf21079ef 100644 --- a/synapse/config/registration.py +++ b/synapse/config/registration.py @@ -162,6 +162,10 @@ def read_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 ecfea175c75..3e863499819 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -159,7 +159,10 @@ async def check_username( 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 92487692dba..39705d5e664 100644 --- a/tests/handlers/test_register.py +++ b/tests/handlers/test_register.py @@ -588,6 +588,29 @@ def test_register_not_support_user(self) -> None: 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(