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

Commit 2e6c90f

Browse files
authored
Do not propagate profile changes of shadow-banned users into rooms. (#8157)
1 parent e3c91a3 commit 2e6c90f

File tree

5 files changed

+291
-160
lines changed

5 files changed

+291
-160
lines changed

changelog.d/8157.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for shadow-banning users (ignoring any message send requests).

synapse/handlers/profile.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License.
1515

1616
import logging
17+
import random
1718

1819
from synapse.api.errors import (
1920
AuthError,
@@ -213,8 +214,14 @@ async def get_avatar_url(self, target_user):
213214
async def set_avatar_url(
214215
self, target_user, requester, new_avatar_url, by_admin=False
215216
):
216-
"""target_user is the user whose avatar_url is to be changed;
217-
auth_user is the user attempting to make this change."""
217+
"""Set a new avatar URL for a user.
218+
219+
Args:
220+
target_user (UserID): the user whose avatar URL is to be changed.
221+
requester (Requester): The user attempting to make this change.
222+
new_avatar_url (str): The avatar URL to give this user.
223+
by_admin (bool): Whether this change was made by an administrator.
224+
"""
218225
if not self.hs.is_mine(target_user):
219226
raise SynapseError(400, "User is not hosted on this homeserver")
220227

@@ -278,6 +285,12 @@ async def _update_join_states(self, requester, target_user):
278285

279286
await self.ratelimit(requester)
280287

288+
# Do not actually update the room state for shadow-banned users.
289+
if requester.shadow_banned:
290+
# We randomly sleep a bit just to annoy the requester.
291+
await self.clock.sleep(random.randint(1, 10))
292+
return
293+
281294
room_ids = await self.store.get_rooms_for_user(target_user.to_string())
282295

283296
for room_id in room_ids:

synapse/handlers/room_member.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ async def _update_membership(
380380
# later on.
381381
content = dict(content)
382382

383-
if not self.allow_per_room_profiles:
383+
if not self.allow_per_room_profiles or requester.shadow_banned:
384384
# Strip profile data, knowing that new profile data will be added to the
385385
# event's content in event_creation_handler.create_event() using the target's
386386
# global profile.
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# Copyright 2020 The Matrix.org Foundation C.I.C.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from mock import Mock, patch
16+
17+
import synapse.rest.admin
18+
from synapse.api.constants import EventTypes
19+
from synapse.rest.client.v1 import directory, login, profile, room
20+
from synapse.rest.client.v2_alpha import room_upgrade_rest_servlet
21+
22+
from tests import unittest
23+
24+
25+
class _ShadowBannedBase(unittest.HomeserverTestCase):
26+
def prepare(self, reactor, clock, homeserver):
27+
# Create two users, one of which is shadow-banned.
28+
self.banned_user_id = self.register_user("banned", "test")
29+
self.banned_access_token = self.login("banned", "test")
30+
31+
self.store = self.hs.get_datastore()
32+
33+
self.get_success(
34+
self.store.db_pool.simple_update(
35+
table="users",
36+
keyvalues={"name": self.banned_user_id},
37+
updatevalues={"shadow_banned": True},
38+
desc="shadow_ban",
39+
)
40+
)
41+
42+
self.other_user_id = self.register_user("otheruser", "pass")
43+
self.other_access_token = self.login("otheruser", "pass")
44+
45+
46+
# To avoid the tests timing out don't add a delay to "annoy the requester".
47+
@patch("random.randint", new=lambda a, b: 0)
48+
class RoomTestCase(_ShadowBannedBase):
49+
servlets = [
50+
synapse.rest.admin.register_servlets_for_client_rest_resource,
51+
directory.register_servlets,
52+
login.register_servlets,
53+
room.register_servlets,
54+
room_upgrade_rest_servlet.register_servlets,
55+
]
56+
57+
def test_invite(self):
58+
"""Invites from shadow-banned users don't actually get sent."""
59+
60+
# The create works fine.
61+
room_id = self.helper.create_room_as(
62+
self.banned_user_id, tok=self.banned_access_token
63+
)
64+
65+
# Inviting the user completes successfully.
66+
self.helper.invite(
67+
room=room_id,
68+
src=self.banned_user_id,
69+
tok=self.banned_access_token,
70+
targ=self.other_user_id,
71+
)
72+
73+
# But the user wasn't actually invited.
74+
invited_rooms = self.get_success(
75+
self.store.get_invited_rooms_for_local_user(self.other_user_id)
76+
)
77+
self.assertEqual(invited_rooms, [])
78+
79+
def test_invite_3pid(self):
80+
"""Ensure that a 3PID invite does not attempt to contact the identity server."""
81+
identity_handler = self.hs.get_handlers().identity_handler
82+
identity_handler.lookup_3pid = Mock(
83+
side_effect=AssertionError("This should not get called")
84+
)
85+
86+
# The create works fine.
87+
room_id = self.helper.create_room_as(
88+
self.banned_user_id, tok=self.banned_access_token
89+
)
90+
91+
# Inviting the user completes successfully.
92+
request, channel = self.make_request(
93+
"POST",
94+
"/rooms/%s/invite" % (room_id,),
95+
{"id_server": "test", "medium": "email", "address": "test@test.test"},
96+
access_token=self.banned_access_token,
97+
)
98+
self.render(request)
99+
self.assertEquals(200, channel.code, channel.result)
100+
101+
# This should have raised an error earlier, but double check this wasn't called.
102+
identity_handler.lookup_3pid.assert_not_called()
103+
104+
def test_create_room(self):
105+
"""Invitations during a room creation should be discarded, but the room still gets created."""
106+
# The room creation is successful.
107+
request, channel = self.make_request(
108+
"POST",
109+
"/_matrix/client/r0/createRoom",
110+
{"visibility": "public", "invite": [self.other_user_id]},
111+
access_token=self.banned_access_token,
112+
)
113+
self.render(request)
114+
self.assertEquals(200, channel.code, channel.result)
115+
room_id = channel.json_body["room_id"]
116+
117+
# But the user wasn't actually invited.
118+
invited_rooms = self.get_success(
119+
self.store.get_invited_rooms_for_local_user(self.other_user_id)
120+
)
121+
self.assertEqual(invited_rooms, [])
122+
123+
# Since a real room was created, the other user should be able to join it.
124+
self.helper.join(room_id, self.other_user_id, tok=self.other_access_token)
125+
126+
# Both users should be in the room.
127+
users = self.get_success(self.store.get_users_in_room(room_id))
128+
self.assertCountEqual(users, ["@banned:test", "@otheruser:test"])
129+
130+
def test_message(self):
131+
"""Messages from shadow-banned users don't actually get sent."""
132+
133+
room_id = self.helper.create_room_as(
134+
self.other_user_id, tok=self.other_access_token
135+
)
136+
137+
# The user should be in the room.
138+
self.helper.join(room_id, self.banned_user_id, tok=self.banned_access_token)
139+
140+
# Sending a message should complete successfully.
141+
result = self.helper.send_event(
142+
room_id=room_id,
143+
type=EventTypes.Message,
144+
content={"msgtype": "m.text", "body": "with right label"},
145+
tok=self.banned_access_token,
146+
)
147+
self.assertIn("event_id", result)
148+
event_id = result["event_id"]
149+
150+
latest_events = self.get_success(
151+
self.store.get_latest_event_ids_in_room(room_id)
152+
)
153+
self.assertNotIn(event_id, latest_events)
154+
155+
def test_upgrade(self):
156+
"""A room upgrade should fail, but look like it succeeded."""
157+
158+
# The create works fine.
159+
room_id = self.helper.create_room_as(
160+
self.banned_user_id, tok=self.banned_access_token
161+
)
162+
163+
request, channel = self.make_request(
164+
"POST",
165+
"/_matrix/client/r0/rooms/%s/upgrade" % (room_id,),
166+
{"new_version": "6"},
167+
access_token=self.banned_access_token,
168+
)
169+
self.render(request)
170+
self.assertEquals(200, channel.code, channel.result)
171+
# A new room_id should be returned.
172+
self.assertIn("replacement_room", channel.json_body)
173+
174+
new_room_id = channel.json_body["replacement_room"]
175+
176+
# It doesn't really matter what API we use here, we just want to assert
177+
# that the room doesn't exist.
178+
summary = self.get_success(self.store.get_room_summary(new_room_id))
179+
# The summary should be empty since the room doesn't exist.
180+
self.assertEqual(summary, {})
181+
182+
183+
# To avoid the tests timing out don't add a delay to "annoy the requester".
184+
@patch("random.randint", new=lambda a, b: 0)
185+
class ProfileTestCase(_ShadowBannedBase):
186+
servlets = [
187+
synapse.rest.admin.register_servlets_for_client_rest_resource,
188+
login.register_servlets,
189+
profile.register_servlets,
190+
room.register_servlets,
191+
]
192+
193+
def test_displayname(self):
194+
"""Profile changes should succeed, but don't end up in a room."""
195+
original_display_name = "banned"
196+
new_display_name = "new name"
197+
198+
# Join a room.
199+
room_id = self.helper.create_room_as(
200+
self.banned_user_id, tok=self.banned_access_token
201+
)
202+
203+
# The update should succeed.
204+
request, channel = self.make_request(
205+
"PUT",
206+
"/_matrix/client/r0/profile/%s/displayname" % (self.banned_user_id,),
207+
{"displayname": new_display_name},
208+
access_token=self.banned_access_token,
209+
)
210+
self.render(request)
211+
self.assertEquals(200, channel.code, channel.result)
212+
self.assertEqual(channel.json_body, {})
213+
214+
# The user's display name should be updated.
215+
request, channel = self.make_request(
216+
"GET", "/profile/%s/displayname" % (self.banned_user_id,)
217+
)
218+
self.render(request)
219+
self.assertEqual(channel.code, 200, channel.result)
220+
self.assertEqual(channel.json_body["displayname"], new_display_name)
221+
222+
# But the display name in the room should not be.
223+
message_handler = self.hs.get_message_handler()
224+
event = self.get_success(
225+
message_handler.get_room_data(
226+
self.banned_user_id,
227+
room_id,
228+
"m.room.member",
229+
self.banned_user_id,
230+
False,
231+
)
232+
)
233+
self.assertEqual(
234+
event.content, {"membership": "join", "displayname": original_display_name}
235+
)
236+
237+
def test_room_displayname(self):
238+
"""Changes to state events for a room should be processed, but not end up in the room."""
239+
original_display_name = "banned"
240+
new_display_name = "new name"
241+
242+
# Join a room.
243+
room_id = self.helper.create_room_as(
244+
self.banned_user_id, tok=self.banned_access_token
245+
)
246+
247+
# The update should succeed.
248+
request, channel = self.make_request(
249+
"PUT",
250+
"/_matrix/client/r0/rooms/%s/state/m.room.member/%s"
251+
% (room_id, self.banned_user_id),
252+
{"membership": "join", "displayname": new_display_name},
253+
access_token=self.banned_access_token,
254+
)
255+
self.render(request)
256+
self.assertEquals(200, channel.code, channel.result)
257+
self.assertIn("event_id", channel.json_body)
258+
259+
# The display name in the room should not be changed.
260+
message_handler = self.hs.get_message_handler()
261+
event = self.get_success(
262+
message_handler.get_room_data(
263+
self.banned_user_id,
264+
room_id,
265+
"m.room.member",
266+
self.banned_user_id,
267+
False,
268+
)
269+
)
270+
self.assertEqual(
271+
event.content, {"membership": "join", "displayname": original_display_name}
272+
)

0 commit comments

Comments
 (0)