From 7a871881cf0c5fd170c963d33e5c1128ad54b6ea Mon Sep 17 00:00:00 2001 From: MattC Date: Mon, 1 Aug 2022 16:18:42 +1000 Subject: [PATCH 01/13] Add - module API method to create a room. --- synapse/module_api/__init__.py | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 6d8bf5408364..2e3af436e754 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1452,6 +1452,65 @@ async def get_monthly_active_users_by_service( start_timestamp, end_timestamp ) + async def create_room( + self, + user_id: str, + config: JsonDict, + ratelimit: bool = True, + creator_join_profile: Optional[JsonDict] = None, + ) -> Tuple[dict, int]: + """Creates a new room. + + Args: + user_id: + The user who requested the room creation. + config : A dict of configuration options. See "Request body" of: + https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + ratelimit: set to False to disable the rate limiter. + + creator_join_profile: + Set to override the displayname and avatar for the creating + user in this room. If unset, displayname and avatar will be + derived from the user's profile. If set, should contain the + values to go in the body of the 'join' event (typically + `avatar_url` and/or `displayname`. + + Returns: + First, a dict containing the keys `room_id` and, if an alias + was, requested, `room_alias`. Secondly, the stream_id of the + last persisted event. + Raises: + SynapseError if the user does not exist, room ID couldn't be stored, or + something went horribly wrong. + ResourceLimitError if server is blocked to some resource being + exceeded. + """ + user_info = await self.get_userinfo_by_id(user_id) + if user_info is None: + raise SynapseError(400, f"User ({user_id}) not found") + + if user_info.appservice_id is not None: + app_service = self._store.get_app_service_by_id( + str(user_info.appservice_id) + ) + else: + app_service = None + + requester = create_requester( + user_id=user_id, + is_guest=user_info.is_guest, + shadow_banned=user_info.is_shadow_banned, + app_service=app_service, + authenticated_entity=self.server_name, + ) + + return await self._hs.get_room_creation_handler().create_room( + requester=requester, + config=config, + ratelimit=ratelimit, + creator_join_profile=creator_join_profile, + ) + class PublicRoomListManager: """Contains methods for adding to, removing from and querying whether a room From 2c934f622f3364179bc2659999de23cad966f087 Mon Sep 17 00:00:00 2001 From: MattC Date: Mon, 1 Aug 2022 16:39:42 +1000 Subject: [PATCH 02/13] Document - Add changelog entry for 13429. --- changelog.d/13429.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/13429.feature diff --git a/changelog.d/13429.feature b/changelog.d/13429.feature new file mode 100644 index 000000000000..f4f347e54e12 --- /dev/null +++ b/changelog.d/13429.feature @@ -0,0 +1 @@ +Add a module API method to create a room. From 876bb8384206b75381536bb995f1690fc45a2550 Mon Sep 17 00:00:00 2001 From: MattC Date: Tue, 2 Aug 2022 12:34:48 +1000 Subject: [PATCH 03/13] Add - unit test for module API "create_room" method. --- tests/module_api/test_api.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index 169e29b59001..0f2c1443ba25 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -16,12 +16,13 @@ from twisted.internet import defer from synapse.api.constants import EduTypes, EventTypes +from synapse.api.errors import SynapseError from synapse.events import EventBase from synapse.federation.units import Transaction from synapse.handlers.presence import UserPresenceState from synapse.handlers.push_rules import InvalidRuleException from synapse.rest import admin -from synapse.rest.client import login, notifications, presence, profile, room +from synapse.rest.client import directory, login, notifications, presence, profile, room from synapse.types import create_requester from tests.events.test_presence_router import send_presence_update, sync_presence @@ -40,6 +41,7 @@ class ModuleApiTestCase(HomeserverTestCase): presence.register_servlets, profile.register_servlets, notifications.register_servlets, + directory.register_servlets, ] def prepare(self, reactor, clock, homeserver): @@ -635,6 +637,30 @@ def test_check_push_rules_actions(self) -> None: [{"set_tweak": "sound", "value": "default"}] ) + def test_create_room(self) -> None: + """Test that modules can create a room.""" + # First test user existence verification. + self.get_failure( + self.module_api.create_room( + user_id="@user:test", config={}, ratelimit=False + ), + SynapseError, + ) + + # Now do the happy path. + user_id = self.register_user("user", "password") + + (result, _) = self.get_success( + self.module_api.create_room(user_id=user_id, config={}, ratelimit=False) + ) + room_id = result["room_id"] + + channel = self.make_request( + "GET", + f"/_matrix/client/r0/directory/list/room/{room_id}", + ) + self.assertEqual(channel.code, 200, channel.result) + class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase): """For testing ModuleApi functionality in a multi-worker setup""" From 916c5830b94c9ec0f0cc49cb0e087657e6845b8a Mon Sep 17 00:00:00 2001 From: MattC Date: Wed, 3 Aug 2022 11:59:47 +1000 Subject: [PATCH 04/13] Alter - module API "create_room" method to relax constraints on user that creates the room. Align more with logic of pre-existing module API methods (e.g. "update_room_membership"). --- synapse/module_api/__init__.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 2e3af436e754..d1d80b997435 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1480,29 +1480,18 @@ async def create_room( was, requested, `room_alias`. Secondly, the stream_id of the last persisted event. Raises: - SynapseError if the user does not exist, room ID couldn't be stored, or + SynapseError if the user_id is invalid, room ID couldn't be stored, or something went horribly wrong. ResourceLimitError if server is blocked to some resource being exceeded. + RuntimeError if the user_id does not refer to a local user. """ - user_info = await self.get_userinfo_by_id(user_id) - if user_info is None: - raise SynapseError(400, f"User ({user_id}) not found") - - if user_info.appservice_id is not None: - app_service = self._store.get_app_service_by_id( - str(user_info.appservice_id) + if not self.is_mine(user_id): + raise RuntimeError( + "Tried to create a room as a user that isn't local to this homeserver", ) - else: - app_service = None - requester = create_requester( - user_id=user_id, - is_guest=user_info.is_guest, - shadow_banned=user_info.is_shadow_banned, - app_service=app_service, - authenticated_entity=self.server_name, - ) + requester = create_requester(user_id) return await self._hs.get_room_creation_handler().create_room( requester=requester, From 8116429b85ee8d8b859543f00c9e1c0e4671c453 Mon Sep 17 00:00:00 2001 From: MattC Date: Wed, 3 Aug 2022 12:03:33 +1000 Subject: [PATCH 05/13] Document - Synapse version in which "create_room" module API method is likely to be released in. --- synapse/module_api/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index d1d80b997435..31c2a8e4df19 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1461,6 +1461,8 @@ async def create_room( ) -> Tuple[dict, int]: """Creates a new room. + Added in Synapse v1.66.0. + Args: user_id: The user who requested the room creation. From 4ebd44a46f609a72e7eac545e2da27b947b540b4 Mon Sep 17 00:00:00 2001 From: MattC Date: Wed, 3 Aug 2022 12:09:45 +1000 Subject: [PATCH 06/13] Alter - return value of "create_room" module API method so that it doesn't expose Synapse internals. Modules shouldn't be concerned with Synapse internals. --- synapse/module_api/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 31c2a8e4df19..b6cc995f6b50 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1458,7 +1458,7 @@ async def create_room( config: JsonDict, ratelimit: bool = True, creator_join_profile: Optional[JsonDict] = None, - ) -> Tuple[dict, int]: + ) -> Tuple[str, Optional[str]]: """Creates a new room. Added in Synapse v1.66.0. @@ -1478,9 +1478,8 @@ async def create_room( `avatar_url` and/or `displayname`. Returns: - First, a dict containing the keys `room_id` and, if an alias - was, requested, `room_alias`. Secondly, the stream_id of the - last persisted event. + A tuple containing: 1) the room ID (str), 2) if an alias was requested, + the room alias (str), otherwise None if no alias was requested. Raises: SynapseError if the user_id is invalid, room ID couldn't be stored, or something went horribly wrong. @@ -1494,14 +1493,15 @@ async def create_room( ) requester = create_requester(user_id) - - return await self._hs.get_room_creation_handler().create_room( + room_id_and_alias, _ = await self._hs.get_room_creation_handler().create_room( requester=requester, config=config, ratelimit=ratelimit, creator_join_profile=creator_join_profile, ) + return room_id_and_alias["room_id"], room_id_and_alias.get("room_alias", None) + class PublicRoomListManager: """Contains methods for adding to, removing from and querying whether a room From 3b53122b6dccd654324778e22e6f684c99b20b26 Mon Sep 17 00:00:00 2001 From: MattC Date: Wed, 3 Aug 2022 12:11:22 +1000 Subject: [PATCH 07/13] Document - Tidy up the docstring for the "create_room" module API method. --- synapse/module_api/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index b6cc995f6b50..5884e65fa227 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1480,12 +1480,13 @@ async def create_room( Returns: A tuple containing: 1) the room ID (str), 2) if an alias was requested, the room alias (str), otherwise None if no alias was requested. + Raises: - SynapseError if the user_id is invalid, room ID couldn't be stored, or - something went horribly wrong. ResourceLimitError if server is blocked to some resource being exceeded. RuntimeError if the user_id does not refer to a local user. + SynapseError if the user_id is invalid, room ID couldn't be stored, or + something went horribly wrong. """ if not self.is_mine(user_id): raise RuntimeError( From a330777690903688627575a9b663b3ea2480ea17 Mon Sep 17 00:00:00 2001 From: MattC Date: Wed, 3 Aug 2022 12:21:05 +1000 Subject: [PATCH 08/13] Alter - unit tests for the module API "create_room" method to align with new API (of the unit under test) and to reduce the chances of false negatives during test execution. --- tests/module_api/test_api.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index 0f2c1443ba25..c6e7f91d86bd 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -16,7 +16,6 @@ from twisted.internet import defer from synapse.api.constants import EduTypes, EventTypes -from synapse.api.errors import SynapseError from synapse.events import EventBase from synapse.federation.units import Transaction from synapse.handlers.presence import UserPresenceState @@ -639,27 +638,31 @@ def test_check_push_rules_actions(self) -> None: def test_create_room(self) -> None: """Test that modules can create a room.""" - # First test user existence verification. + # First test user validation (i.e. user is local). self.get_failure( self.module_api.create_room( - user_id="@user:test", config={}, ratelimit=False + user_id=f"@user:{self.module_api.server_name}abc", + config={}, + ratelimit=False, ), - SynapseError, + RuntimeError, ) # Now do the happy path. user_id = self.register_user("user", "password") + access_token = self.login(user_id, "password") - (result, _) = self.get_success( + room_id, _ = self.get_success( self.module_api.create_room(user_id=user_id, config={}, ratelimit=False) ) - room_id = result["room_id"] channel = self.make_request( "GET", - f"/_matrix/client/r0/directory/list/room/{room_id}", + f"/_matrix/client/v3/rooms/{room_id}/state/m.room.create", + access_token=access_token, ) self.assertEqual(channel.code, 200, channel.result) + self.assertEqual(channel.json_body["creator"], user_id) class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase): From 7618a8dc624f03437e4d414e692fffab8314f03b Mon Sep 17 00:00:00 2001 From: MattC Date: Thu, 4 Aug 2022 10:01:21 +1000 Subject: [PATCH 09/13] Document - Fix expected Synapse version release. --- synapse/module_api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 5884e65fa227..1c8c909f9cdb 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1461,7 +1461,7 @@ async def create_room( ) -> Tuple[str, Optional[str]]: """Creates a new room. - Added in Synapse v1.66.0. + Added in Synapse v1.65.0. Args: user_id: From f7df217877e9e4733d14a2242ed2b3d0cf9c29fa Mon Sep 17 00:00:00 2001 From: MattC Date: Thu, 4 Aug 2022 10:02:52 +1000 Subject: [PATCH 10/13] Document - Add clarity to the "ratelimit" parameter description. --- synapse/module_api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 1c8c909f9cdb..cd905e212d29 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1468,7 +1468,7 @@ async def create_room( The user who requested the room creation. config : A dict of configuration options. See "Request body" of: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom - ratelimit: set to False to disable the rate limiter. + ratelimit: set to False to disable the rate limiter for this specific join. creator_join_profile: Set to override the displayname and avatar for the creating From bbc85b61c60a471559a83a23b4774e46c7d304eb Mon Sep 17 00:00:00 2001 From: MattC Date: Thu, 4 Aug 2022 10:13:52 +1000 Subject: [PATCH 11/13] Add - test coverage for ensuring room alias matches what is expected. --- tests/module_api/test_api.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index c6e7f91d86bd..0dd399365a6f 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -652,10 +652,30 @@ def test_create_room(self) -> None: user_id = self.register_user("user", "password") access_token = self.login(user_id, "password") - room_id, _ = self.get_success( + room_id, room_alias = self.get_success( + self.module_api.create_room( + user_id=user_id, config={"room_alias_name": "foo-bar"}, ratelimit=False + ) + ) + + # Check room creator. + channel = self.make_request( + "GET", + f"/_matrix/client/v3/rooms/{room_id}/state/m.room.create", + access_token=access_token, + ) + self.assertEqual(channel.code, 200, channel.result) + self.assertEqual(channel.json_body["creator"], user_id) + + # Check room alias. + self.assertEquals(room_alias, f"#foo-bar:{self.module_api.server_name}") + + # Let's try a room with no alias. + room_id, room_alias = self.get_success( self.module_api.create_room(user_id=user_id, config={}, ratelimit=False) ) + # Check room creator. channel = self.make_request( "GET", f"/_matrix/client/v3/rooms/{room_id}/state/m.room.create", @@ -664,6 +684,9 @@ def test_create_room(self) -> None: self.assertEqual(channel.code, 200, channel.result) self.assertEqual(channel.json_body["creator"], user_id) + # Check room alias. + self.assertIsNone(room_alias) + class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase): """For testing ModuleApi functionality in a multi-worker setup""" From 698967d6faadfe4f4e5d5bba69e184f481741972 Mon Sep 17 00:00:00 2001 From: MattC Date: Thu, 4 Aug 2022 10:30:29 +1000 Subject: [PATCH 12/13] Drop - redundant servlet registration for module API unit tests. --- tests/module_api/test_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index 0d107c1e7eac..9bf95472e1d0 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -21,7 +21,7 @@ from synapse.handlers.presence import UserPresenceState from synapse.handlers.push_rules import InvalidRuleException from synapse.rest import admin -from synapse.rest.client import directory, login, notifications, presence, profile, room +from synapse.rest.client import login, notifications, presence, profile, room from synapse.types import create_requester from tests.events.test_presence_router import send_presence_update, sync_presence @@ -40,7 +40,6 @@ class ModuleApiTestCase(HomeserverTestCase): presence.register_servlets, profile.register_servlets, notifications.register_servlets, - directory.register_servlets, ] def prepare(self, reactor, clock, homeserver): From 2b170f9808835edc83ec687b1c3bf8eb15e2d7ec Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 4 Aug 2022 10:58:59 +0200 Subject: [PATCH 13/13] Update synapse/module_api/__init__.py --- synapse/module_api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 53a896d54092..71145870eeda 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -1492,7 +1492,7 @@ async def create_room( The user who requested the room creation. config : A dict of configuration options. See "Request body" of: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom - ratelimit: set to False to disable the rate limiter for this specific join. + ratelimit: set to False to disable the rate limiter for this specific operation. creator_join_profile: Set to override the displayname and avatar for the creating