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

Implement MSC 2367 - Membership Reasons #6434

Merged
merged 4 commits into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/6434.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for MSC 2367, which allows specifying a reason on all membership events.
4 changes: 2 additions & 2 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1428,9 +1428,9 @@ def on_invite_request(self, origin, pdu):
return event

@defer.inlineCallbacks
def do_remotely_reject_invite(self, target_hosts, room_id, user_id):
def do_remotely_reject_invite(self, target_hosts, room_id, user_id, content):
origin, event, event_format_version = yield self._make_and_verify_event(
target_hosts, room_id, user_id, "leave"
target_hosts, room_id, user_id, "leave", content=content,
)
# Mark as outlier as we don't have any state for this event; we're not
# even in the room.
Expand Down
13 changes: 9 additions & 4 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
raise NotImplementedError()

@abc.abstractmethod
def _remote_reject_invite(self, requester, remote_room_hosts, room_id, target):
def _remote_reject_invite(
self, requester, remote_room_hosts, room_id, target, content
):
"""Attempt to reject an invite for a room this server is not in. If we
fail to do so we locally mark the invite as rejected.

Expand All @@ -104,6 +106,7 @@ def _remote_reject_invite(self, requester, remote_room_hosts, room_id, target):
reject invite
room_id (str)
target (UserID): The user rejecting the invite
content (dict): The content for the rejection event

Returns:
Deferred[dict]: A dictionary to be returned to the client, may
Expand Down Expand Up @@ -471,7 +474,7 @@ def _update_membership(
# send the rejection to the inviter's HS.
remote_room_hosts = remote_room_hosts + [inviter.domain]
res = yield self._remote_reject_invite(
requester, remote_room_hosts, room_id, target
requester, remote_room_hosts, room_id, target, content,
)
return res

Expand Down Expand Up @@ -971,13 +974,15 @@ def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
)

@defer.inlineCallbacks
def _remote_reject_invite(self, requester, remote_room_hosts, room_id, target):
def _remote_reject_invite(
self, requester, remote_room_hosts, room_id, target, content
):
"""Implements RoomMemberHandler._remote_reject_invite
"""
fed_handler = self.federation_handler
try:
ret = yield fed_handler.do_remotely_reject_invite(
remote_room_hosts, room_id, target.to_string()
remote_room_hosts, room_id, target.to_string(), content=content,
)
return ret
except Exception as e:
Expand Down
5 changes: 4 additions & 1 deletion synapse/handlers/room_member_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,17 @@ def _remote_join(self, requester, remote_room_hosts, room_id, user, content):

return ret

def _remote_reject_invite(self, requester, remote_room_hosts, room_id, target):
def _remote_reject_invite(
self, requester, remote_room_hosts, room_id, target, content
):
"""Implements RoomMemberHandler._remote_reject_invite
"""
return self._remote_reject_client(
requester=requester,
remote_room_hosts=remote_room_hosts,
room_id=room_id,
user_id=target.to_string(),
content=content,
)

def _user_joined_room(self, target, room_id):
Expand Down
7 changes: 5 additions & 2 deletions synapse/replication/http/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class ReplicationRemoteRejectInviteRestServlet(ReplicationEndpoint):
{
"requester": ...,
"remote_room_hosts": [...],
"content": { ... }
}
"""

Expand All @@ -107,7 +108,7 @@ def __init__(self, hs):
self.clock = hs.get_clock()

@staticmethod
def _serialize_payload(requester, room_id, user_id, remote_room_hosts):
def _serialize_payload(requester, room_id, user_id, remote_room_hosts, content):
"""
Args:
requester(Requester)
Expand All @@ -118,12 +119,14 @@ def _serialize_payload(requester, room_id, user_id, remote_room_hosts):
return {
"requester": requester.serialize(),
"remote_room_hosts": remote_room_hosts,
"content": content,
}

async def _handle_request(self, request, room_id, user_id):
content = parse_json_object_from_request(request)

remote_room_hosts = content["remote_room_hosts"]
event_content = content["content"]

requester = Requester.deserialize(self.store, content["requester"])

Expand All @@ -134,7 +137,7 @@ async def _handle_request(self, request, room_id, user_id):

try:
event = await self.federation_handler.do_remotely_reject_invite(
remote_room_hosts, room_id, user_id
remote_room_hosts, room_id, user_id, event_content,
)
ret = event.get_pdu_json()
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ async def on_POST(self, request, room_id, membership_action, txn_id=None):
target = UserID.from_string(content["user_id"])

event_content = None
if "reason" in content and membership_action in ["kick", "ban"]:
if "reason" in content:
event_content = {"reason": content["reason"]}

await self.room_member_handler.update_membership(
Expand Down
140 changes: 140 additions & 0 deletions tests/rest/client/v1/test_rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1180,3 +1180,143 @@ def test_per_room_profile_forbidden(self):

res_displayname = channel.json_body["content"]["displayname"]
self.assertEqual(res_displayname, self.displayname, channel.result)


class RoomMembershipReasonTestCase(unittest.HomeserverTestCase):
"""Tests that clients can add a "reason" field to membership events and
that they get correctly added to the generated events and propagated.
"""

servlets = [
synapse.rest.admin.register_servlets_for_client_rest_resource,
room.register_servlets,
login.register_servlets,
]

def prepare(self, reactor, clock, homeserver):
self.creator = self.register_user("creator", "test")
self.creator_tok = self.login("creator", "test")

self.second_user_id = self.register_user("second", "test")
self.second_tok = self.login("second", "test")

self.room_id = self.helper.create_room_as(self.creator, tok=self.creator_tok)

def test_join_reason(self):
reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/join".format(self.room_id),
content={"reason": reason},
access_token=self.second_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def test_leave_reason(self):
self.helper.join(self.room_id, user=self.second_user_id, tok=self.second_tok)

reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/leave".format(self.room_id),
content={"reason": reason},
access_token=self.second_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def test_kick_reason(self):
self.helper.join(self.room_id, user=self.second_user_id, tok=self.second_tok)

reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/kick".format(self.room_id),
content={"reason": reason, "user_id": self.second_user_id},
access_token=self.second_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def test_ban_reason(self):
self.helper.join(self.room_id, user=self.second_user_id, tok=self.second_tok)

reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/ban".format(self.room_id),
content={"reason": reason, "user_id": self.second_user_id},
access_token=self.creator_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def test_unban_reason(self):
reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/unban".format(self.room_id),
content={"reason": reason, "user_id": self.second_user_id},
access_token=self.creator_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def test_invite_reason(self):
reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/invite".format(self.room_id),
content={"reason": reason, "user_id": self.second_user_id},
access_token=self.creator_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def test_reject_invite_reason(self):
self.helper.invite(
self.room_id,
src=self.creator,
targ=self.second_user_id,
tok=self.creator_tok,
)

reason = "hello"
request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/{}/leave".format(self.room_id),
content={"reason": reason},
access_token=self.second_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

self._check_for_reason(reason)

def _check_for_reason(self, reason):
request, channel = self.make_request(
"GET",
"/_matrix/client/r0/rooms/{}/state/m.room.member/{}".format(
self.room_id, self.second_user_id
),
access_token=self.creator_tok,
)
self.render(request)
self.assertEqual(channel.code, 200, channel.result)

event_content = channel.json_body

self.assertEqual(event_content.get("reason"), reason, channel.result)