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

Commit

Permalink
Stop shadow-banned users from sending invites.
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep committed Aug 14, 2020
1 parent ac77cdb commit 4b0e761
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 31 deletions.
1 change: 1 addition & 0 deletions changelog.d/8095.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for shadow-banning users (ignoring any message send requests).
8 changes: 8 additions & 0 deletions synapse/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,11 @@ def to_synapse_error(self):
errmsg = j.pop("error", self.msg)

return ProxiedRequestError(self.code, errmsg, errcode, j)


class ShadowBanError(Exception):
"""
Raised when a shadow-banned user attempts to perform an action.
This should be caught and a proper "fake" success response send to the user.
"""
16 changes: 14 additions & 2 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import itertools
import logging
import math
import random
import string
from collections import OrderedDict
from typing import TYPE_CHECKING, Any, Awaitable, Dict, List, Optional, Tuple
Expand Down Expand Up @@ -626,6 +627,7 @@ async def create_room(
if mapping:
raise SynapseError(400, "Room alias already taken", Codes.ROOM_IN_USE)

invite_3pid_list = config.get("invite_3pid", [])
invite_list = config.get("invite", [])
for i in invite_list:
try:
Expand All @@ -634,6 +636,14 @@ 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))

# Allow the request to go through, but remove any associated invites.
invite_3pid_list = []
invite_list = []

await self.event_creation_handler.assert_accepted_privacy_policy(requester)

power_level_content_override = config.get("power_level_content_override")
Expand All @@ -648,8 +658,6 @@ async def create_room(
% (user_id,),
)

invite_3pid_list = config.get("invite_3pid", [])

visibility = config.get("visibility", None)
is_public = visibility == "public"

Expand Down Expand Up @@ -744,6 +752,8 @@ async def create_room(
if is_direct:
content["is_direct"] = is_direct

# Note that update_membership with an action of "invite" can raise a
# ShadowBanError, but this was handled above by emptying invite_list.
_, last_stream_id = await self.room_member_handler.update_membership(
requester,
UserID.from_string(invitee),
Expand All @@ -758,6 +768,8 @@ async def create_room(
id_access_token = invite_3pid.get("id_access_token") # optional
address = invite_3pid["address"]
medium = invite_3pid["medium"]
# Note that do_3pid_invite can raise a ShadowBanError, but this was
# handled above by emptying invite_3pid_list.
last_stream_id = await self.hs.get_room_member_handler().do_3pid_invite(
room_id,
requester.user,
Expand Down
62 changes: 60 additions & 2 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@

import abc
import logging
import random
from http import HTTPStatus
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Tuple, Union

from unpaddedbase64 import encode_base64

from synapse import types
from synapse.api.constants import MAX_DEPTH, EventTypes, Membership
from synapse.api.errors import AuthError, Codes, LimitExceededError, SynapseError
from synapse.api.errors import (
AuthError,
Codes,
LimitExceededError,
ShadowBanError,
SynapseError,
)
from synapse.api.ratelimiting import Ratelimiter
from synapse.api.room_versions import EventFormatVersions
from synapse.crypto.event_signing import compute_event_reference_hash
Expand Down Expand Up @@ -285,6 +292,31 @@ async def update_membership(
content: Optional[dict] = None,
require_consent: bool = True,
) -> Tuple[str, int]:
"""Update a user's membership in a room.
Params:
requester: The user who is performing the update.
target: The user whose membership is being updated.
room_id: The room ID whose membership is being updated.
action: The membership change, see synapse.api.constants.Membership.
txn_id: The transaction ID, if given.
remote_room_hosts: Remote servers to send the update to.
third_party_signed: Information from a 3PID invite.
ratelimit: Whether to rate limit the request.
content: The content of the created event.
require_consent: Whether consent is required.
Returns:
A tuple of the new event ID and stream ID.
Raises:
ShadowBanError if a shadow-banned requester attempts to send an invite.
"""
if action == Membership.INVITE and 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()

key = (room_id,)

with (await self.member_linearizer.queue(key)):
Expand Down Expand Up @@ -773,13 +805,37 @@ async def do_3pid_invite(
txn_id: Optional[str],
id_access_token: Optional[str] = None,
) -> int:
"""Invite a 3PID to a room.
Args:
room_id: The room to invite the 3PID to.
inviter: The user sending the invite.
medium: The 3PID's medium.
address: The 3PID's address.
id_server: The identity server to use.
requester: The user making the request.
txn_id: The transaction ID this is part of, or None if this is not
part of a transaction.
id_access_token: The optional identity server access token.
Returns:
The new stream ID.
Raises:
ShadowBanError if the requester has been shadow-banned.
"""
if self.config.block_non_admin_invites:
is_requester_admin = await self.auth.is_server_admin(requester.user)
if not is_requester_admin:
raise SynapseError(
403, "Invites have been disabled on this server", Codes.FORBIDDEN
)

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()

# We need to rate limit *before* we send out any 3PID invites, so we
# can't just rely on the standard ratelimiting of events.
await self.base_handler.ratelimit(requester)
Expand All @@ -804,6 +860,8 @@ async def do_3pid_invite(
)

if invitee:
# Note that update_membership with an action of "invite" can raise
# a ShadowBanError, but this was done above already.
_, stream_id = await self.update_membership(
requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id
)
Expand Down Expand Up @@ -1042,7 +1100,7 @@ async def _remote_join(
return event_id, stream_id

# The room is too large. Leave.
requester = types.create_requester(user, None, False, None)
requester = types.create_requester(user, None, False, False, None)
await self.update_membership(
requester=requester, target=user, room_id=room_id, action="leave"
)
Expand Down
3 changes: 3 additions & 0 deletions synapse/rest/admin/rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ async def on_POST(self, request, room_identifier):
join_rules_event = room_state.get((EventTypes.JoinRules, ""))
if join_rules_event:
if not (join_rules_event.content.get("join_rule") == JoinRules.PUBLIC):
# update_membership with an action of "invite" can raise a
# ShadowBanError. This is not handled since it is assumed that
# an admin isn't going to call this API with shadow-banned user.
await self.room_member_handler.update_membership(
requester=requester,
target=fake_requester.user,
Expand Down
67 changes: 40 additions & 27 deletions synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
Codes,
HttpResponseException,
InvalidClientCredentialsError,
ShadowBanError,
SynapseError,
)
from synapse.api.filtering import Filter
Expand All @@ -46,6 +47,7 @@
from synapse.storage.state import StateFilter
from synapse.streams.config import PaginationConfig
from synapse.types import RoomAlias, RoomID, StreamToken, ThirdPartyInstanceID, UserID
from synapse.util.stringutils import random_string

MYPY = False
if MYPY:
Expand Down Expand Up @@ -201,14 +203,17 @@ async def on_PUT(self, request, room_id, event_type, state_key, txn_id=None):
event_dict["state_key"] = state_key

if event_type == EventTypes.Member:
membership = content.get("membership", None)
event_id, _ = await self.room_member_handler.update_membership(
requester,
target=UserID.from_string(state_key),
room_id=room_id,
action=membership,
content=content,
)
try:
membership = content.get("membership", None)
event_id, _ = await self.room_member_handler.update_membership(
requester,
target=UserID.from_string(state_key),
room_id=room_id,
action=membership,
content=content,
)
except ShadowBanError:
event_id = "$" + random_string(43)
else:
(
event,
Expand Down Expand Up @@ -716,16 +721,20 @@ async def on_POST(self, request, room_id, membership_action, txn_id=None):
content = {}

if membership_action == "invite" and self._has_3pid_invite_keys(content):
await self.room_member_handler.do_3pid_invite(
room_id,
requester.user,
content["medium"],
content["address"],
content["id_server"],
requester,
txn_id,
content.get("id_access_token"),
)
try:
await self.room_member_handler.do_3pid_invite(
room_id,
requester.user,
content["medium"],
content["address"],
content["id_server"],
requester,
txn_id,
content.get("id_access_token"),
)
except ShadowBanError:
# Pretend the request succeed.
pass
return 200, {}

target = requester.user
Expand All @@ -737,15 +746,19 @@ async def on_POST(self, request, room_id, membership_action, txn_id=None):
if "reason" in content:
event_content = {"reason": content["reason"]}

await self.room_member_handler.update_membership(
requester=requester,
target=target,
room_id=room_id,
action=membership_action,
txn_id=txn_id,
third_party_signed=content.get("third_party_signed", None),
content=event_content,
)
try:
await self.room_member_handler.update_membership(
requester=requester,
target=target,
room_id=room_id,
action=membership_action,
txn_id=txn_id,
third_party_signed=content.get("third_party_signed", None),
content=event_content,
)
except ShadowBanError:
# Pretend the request succeed.
pass

return_value = {}

Expand Down

0 comments on commit 4b0e761

Please sign in to comment.