Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add a ModuleApi method to update a user's membership in a room #11147

Merged
merged 16 commits into from
Oct 28, 2021
Merged
1 change: 1 addition & 0 deletions changelog.d/11147.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a module API method to update a user's membership in a room.
80 changes: 79 additions & 1 deletion synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,8 +560,86 @@ def get_state_events_in_room(
state = yield defer.ensureDeferred(self._store.get_events(state_ids.values()))
return state.values()

async def update_room_membership(
self,
sender: str,
target: str,
room_id: str,
new_membership: str,
content: Optional[JsonDict] = None,
) -> EventBase:
"""Updates the membership of a user to the given value.
babolivier marked this conversation as resolved.
Show resolved Hide resolved

Added in Synapse v1.46.0.

Args:
sender: The user performing the membership change. Must be a user local to
this homeserver.
target: The user whose membership is changing. This is often the same value
as `sender`, but it might differ in some cases (e.g. when kicking a user,
the `sender` is the user performing the kick and the `target` is the user
being kicked).
room_id: The room in which to change the membership.
new_membership: The new membership state of `target` after this operation. See
https://spec.matrix.org/unstable/client-server-api/#mroommember for the
list of allowed values.
content: Additional values to include in the resulting event's content.

Returns:
The newly created membership event.

Raises:
RuntimeError if the `sender` isn't a local user.
ShadowBanError if a shadow-banned requester attempts to send an invite.
SynapseError if the module attempts to send a membership event that isn't
allowed, either by the server's configuration (e.g. trying to set a
per-room display name that's too long) or by the validation rules around
membership updates (e.g. the `membership` value is invalid).
"""
if not self.is_mine(sender):
raise RuntimeError(
"Tried to send an event as a user that isn't local to this homeserver",
)

requester = create_requester(sender)
target_user_id = UserID.from_string(target)

if content is None:
content = {}

# Set the profile if not already done by the module.
if "avatar_url" not in content:
content["avatar_url"] = self._hs.get_profile_handler().get_avatar_url(
requester.user,
)

if "displayname" not in content:
content["displayname"] = self._hs.get_profile_handler().get_displayname(
target_user_id,
)

event_id, _ = await self._hs.get_room_member_handler().update_membership(
requester=requester,
target=target_user_id,
room_id=room_id,
action=new_membership,
content=content,
)

# Try to retrieve the resulting event.
event = await self._hs.get_datastore().get_event(event_id)

# update_membership is supposed to always return after the event has been
# successfully persisted.
assert event is not None

return event

async def create_and_send_event_into_room(self, event_dict: JsonDict) -> EventBase:
"""Create and send an event into a room. Membership events are currently not supported.
"""Create and send an event into a room.

Membership events are not supported by this method. To update a user's membership
in a room, please use the `update_room_membership` method instead.

Args:
event_dict: A dictionary representing the event to send.
Expand Down
57 changes: 57 additions & 0 deletions tests/module_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,63 @@ def test_send_local_online_presence_to_federation(self):

self.assertTrue(found_update)

def test_update_membership(self):
babolivier marked this conversation as resolved.
Show resolved Hide resolved
"""Tests that the module API can update the membership of a user in a room."""
peter = self.register_user("peter", "hackme")
lesley = self.register_user("lesley", "hackme")
tok = self.login("peter", "hackme")

# Make peter create a public room.
room_id = self.helper.create_room_as(
room_creator=peter, is_public=True, tok=tok
)

# Make lesley join it.
self.get_success(
defer.ensureDeferred(
self.module_api.update_room_membership(lesley, lesley, room_id, "join")
)
)

# Check that the membership of lesley in the room is "join".
res = self.helper.get_state(
room_id=room_id,
event_type="m.room.member",
state_key=lesley,
tok=tok,
)

self.assertEqual(res["membership"], "join")

# Make peter kick lesley from the room.
self.get_success(
defer.ensureDeferred(
self.module_api.update_room_membership(peter, lesley, room_id, "leave")
)
)

# Check that the membership of lesley in the room is "leave".
res = self.helper.get_state(
room_id=room_id,
event_type="m.room.member",
state_key=lesley,
tok=tok,
)

self.assertEqual(res["membership"], "leave")

# Try to send a membership update from a non-local user and check that it fails.
d = defer.ensureDeferred(
self.module_api.update_room_membership(
"@nicolas:otherserver.com",
lesley,
room_id,
"invite",
)
)

self.get_failure(d, RuntimeError)

babolivier marked this conversation as resolved.
Show resolved Hide resolved

class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase):
"""For testing ModuleApi functionality in a multi-worker setup"""
Expand Down