-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Allow marking users as shadow banned #8028
Changes from all commits
17549a2
b9402e3
190eca1
fc7bbd2
d55f499
58aad73
c858898
1d2ebf8
c002070
acd63f8
739be9e
505fde2
4b23b68
5adcc56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add support for shadow-banning users (ignoring any message send requests). |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,14 @@ | |
# limitations under the License. | ||
|
||
import logging | ||
import random | ||
|
||
from synapse.api.errors import ( | ||
AuthError, | ||
Codes, | ||
HttpResponseException, | ||
RequestSendFailed, | ||
ShadowBanError, | ||
StoreError, | ||
SynapseError, | ||
) | ||
|
@@ -144,6 +146,9 @@ async def set_displayname( | |
requester (Requester): The user attempting to make this change. | ||
new_displayname (str): The displayname to give this user. | ||
by_admin (bool): Whether this change was made by an administrator. | ||
|
||
Raises: | ||
ShadowBanError if the requester is shadow-banned. | ||
""" | ||
if not self.hs.is_mine(target_user): | ||
raise SynapseError(400, "User is not hosted on this homeserver") | ||
|
@@ -168,6 +173,13 @@ async def set_displayname( | |
if new_displayname == "": | ||
new_displayname = None | ||
|
||
# Since the requester can get overwritten below, check if the real | ||
# requester is shadow-banned. | ||
if requester.shadow_banned: | ||
# We randomly sleep a bit just to annoy the requester a bit. | ||
await self.clock.sleep(random.randint(1, 10)) | ||
raise ShadowBanError() | ||
|
||
Comment on lines
+176
to
+182
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is necessary. Shouldn't we let shadow-banned users change their displaynames if they want? It won't make it into any rooms thanks to the check in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm....I was under the impression it would still end up in the room, but I think you're right. I'll need to test this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the changes to |
||
# If the admin changes the display name of a user, the requesting user cannot send | ||
# the join event to update the displayname in the rooms. | ||
# This must be done by the target user himself. | ||
|
@@ -213,8 +225,17 @@ async def get_avatar_url(self, target_user): | |
async def set_avatar_url( | ||
self, target_user, requester, new_avatar_url, by_admin=False | ||
): | ||
"""target_user is the user whose avatar_url is to be changed; | ||
auth_user is the user attempting to make this change.""" | ||
"""Set a new avatar URL for a user. | ||
|
||
Args: | ||
target_user (UserID): the user whose displayname is to be changed. | ||
requester (Requester): The user attempting to make this change. | ||
new_displayname (str): The displayname to give this user. | ||
by_admin (bool): Whether this change was made by an administrator. | ||
|
||
Raises: | ||
ShadowBanError if the requester is shadow-banned. | ||
""" | ||
if not self.hs.is_mine(target_user): | ||
raise SynapseError(400, "User is not hosted on this homeserver") | ||
|
||
|
@@ -233,6 +254,13 @@ async def set_avatar_url( | |
400, "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN,) | ||
) | ||
|
||
# Since the requester can get overwritten below, check if the real | ||
# requester is shadow-banned. | ||
if requester.shadow_banned: | ||
# We randomly sleep a bit just to annoy the requester a bit. | ||
await self.clock.sleep(random.randint(1, 10)) | ||
raise ShadowBanError() | ||
|
||
# Same like set_displayname | ||
if by_admin: | ||
requester = create_requester(target_user) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
import itertools | ||
import logging | ||
import math | ||
import random | ||
import string | ||
from collections import OrderedDict | ||
from typing import Awaitable, Optional, Tuple | ||
|
@@ -129,6 +130,9 @@ async def upgrade_room( | |
|
||
Returns: | ||
the new room id | ||
|
||
Raises: | ||
ShadowBanError if the requester is shadow-banned. | ||
Comment on lines
+133
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again: what is the code path by which this happens? Is it just the final PL send? we should maybe ignore the exception there rather than allow it to propagate? or bail out sooner? |
||
""" | ||
await self.ratelimit(requester) | ||
|
||
|
@@ -247,6 +251,9 @@ async def _update_upgraded_room_pls( | |
old_room_id: the id of the room to be replaced | ||
new_room_id: the id of the replacement room | ||
old_room_state: the state map for the old room | ||
|
||
Raises: | ||
ShadowBanError if the requester is shadow-banned. | ||
""" | ||
old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, "")) | ||
|
||
|
@@ -614,6 +621,7 @@ async def create_room( | |
else: | ||
room_alias = None | ||
|
||
invite_3pid_list = config.get("invite_3pid", []) | ||
invite_list = config.get("invite", []) | ||
for i in invite_list: | ||
try: | ||
|
@@ -622,6 +630,15 @@ async def create_room( | |
except Exception: | ||
raise SynapseError(400, "Invalid user_id: %s" % (i,)) | ||
|
||
if (invite_list or invite_3pid_list) and requester.shadow_banned: | ||
# We randomly sleep a bit just to annoy the requester a bit. | ||
await self.clock.sleep(random.randint(1, 10)) | ||
|
||
# We actually allow this request to go through, but remove any | ||
# associated invites | ||
invite_list = [] | ||
invite_3pid_list = [] | ||
|
||
await self.event_creation_handler.assert_accepted_privacy_policy(requester) | ||
|
||
power_level_content_override = config.get("power_level_content_override") | ||
|
@@ -636,8 +653,6 @@ async def create_room( | |
% (user_id,), | ||
) | ||
|
||
invite_3pid_list = config.get("invite_3pid", []) | ||
|
||
visibility = config.get("visibility", None) | ||
is_public = visibility == "public" | ||
|
||
|
@@ -798,11 +813,13 @@ def create(etype, content, **kwargs): | |
async def send(etype, content, **kwargs) -> int: | ||
event = create(etype, content, **kwargs) | ||
logger.debug("Sending %s in new room", etype) | ||
# Ignore whether the user is shadow-banned to allow the room | ||
# creation to complete. | ||
( | ||
_, | ||
last_stream_id, | ||
) = await self.event_creation_handler.create_and_send_nonmember_event( | ||
creator, event, ratelimit=False | ||
creator, event, ratelimit=False, ignore_shadow_ban=True, | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
return last_stream_id | ||
|
||
|
@@ -816,6 +833,8 @@ async def send(etype, content, **kwargs) -> int: | |
await send(etype=EventTypes.Create, content=creation_content) | ||
|
||
logger.debug("Sending %s in new room", EventTypes.Member) | ||
# Shadow-banning won't interfere with the join, so this should complete | ||
# fine. | ||
await self.room_member_handler.update_membership( | ||
creator, | ||
creator.user, | ||
|
@@ -1108,7 +1127,7 @@ def __init__(self, hs): | |
async def shutdown_room( | ||
self, | ||
room_id: str, | ||
requester_user_id: str, | ||
requester: Requester, | ||
new_room_user_id: Optional[str] = None, | ||
new_room_name: Optional[str] = None, | ||
message: Optional[str] = None, | ||
|
@@ -1129,7 +1148,7 @@ async def shutdown_room( | |
|
||
Args: | ||
room_id: The ID of the room to shut down. | ||
requester_user_id: | ||
requester: | ||
User who requested the action and put the room on the | ||
blocking list. | ||
new_room_user_id: | ||
|
@@ -1171,8 +1190,25 @@ async def shutdown_room( | |
if not await self.store.get_room(room_id): | ||
raise NotFoundError("Unknown room id %s" % (room_id,)) | ||
|
||
# Check if the user is shadow-banned before any mutations occur. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. given this can only be called by an admin user, I think it might be a bit redundant to check the shadow-ban. |
||
if requester.shadow_banned: | ||
# We randomly sleep a bit just to annoy the requester a bit. | ||
await self.clock.sleep(random.randint(1, 10)) | ||
|
||
# Since this usually returns the response sent to the client, | ||
# assemble a fake response instead. | ||
return { | ||
"kicked_users": [], | ||
"failed_to_kick_users": [], | ||
"local_aliases": [], | ||
"new_room_id": stringutils.random_string(18) | ||
if new_room_user_id | ||
else None, | ||
} | ||
|
||
# This will work even if the room is already blocked, but that is | ||
# desirable in case the first attempt at blocking the room failed below. | ||
requester_user_id = requester.user.to_string() | ||
if block: | ||
await self.store.block_room(room_id, requester_user_id) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this coming from
_update_canonical_alias
? should maybe just ignore the exception if so, as we do with AuthError.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also: would be nice to stick the
Raises
docstring on_update_canonical_alias
, even though it's internal.