Skip to content

Commit

Permalink
Stop shadow-banned users from sending non-member events. (matrix-org#…
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep authored Aug 24, 2020
1 parent 420484a commit cbd8d83
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 42 deletions.
1 change: 1 addition & 0 deletions changelog.d/8142.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).
6 changes: 6 additions & 0 deletions synapse/handlers/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
CodeMessageException,
Codes,
NotFoundError,
ShadowBanError,
StoreError,
SynapseError,
)
Expand Down Expand Up @@ -199,6 +200,8 @@ async def delete_association(

try:
await self._update_canonical_alias(requester, user_id, room_id, room_alias)
except ShadowBanError as e:
logger.info("Failed to update alias events due to shadow-ban: %s", e)
except AuthError as e:
logger.info("Failed to update alias events: %s", e)

Expand Down Expand Up @@ -292,6 +295,9 @@ async def _update_canonical_alias(
"""
Send an updated canonical alias event if the removed alias was set as
the canonical alias or listed in the alt_aliases field.
Raises:
ShadowBanError if the requester has been shadow-banned.
"""
alias_event = await self.state.get_current_state(
room_id, EventTypes.CanonicalAlias, ""
Expand Down
10 changes: 10 additions & 0 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import random
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple

from canonicaljson import encode_canonical_json
Expand All @@ -34,6 +35,7 @@
Codes,
ConsentNotGivenError,
NotFoundError,
ShadowBanError,
SynapseError,
)
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
Expand Down Expand Up @@ -716,12 +718,20 @@ async def create_and_send_nonmember_event(
event_dict: dict,
ratelimit: bool = True,
txn_id: Optional[str] = None,
ignore_shadow_ban: bool = False,
) -> Tuple[EventBase, int]:
"""
Creates an event, then sends it.
See self.create_event and self.send_nonmember_event.
Raises:
ShadowBanError if the requester has been shadow-banned.
"""
if not ignore_shadow_ban and requester.shadow_banned:
# We randomly sleep a bit just to annoy the requester.
await self.clock.sleep(random.randint(1, 10))
raise ShadowBanError()

# We limit the number of concurrent event sends in a room so that we
# don't fork the DAG too much. If we don't limit then we can end up in
Expand Down
19 changes: 18 additions & 1 deletion synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ async def upgrade_room(
Returns:
the new room id
Raises:
ShadowBanError if the requester is shadow-banned.
"""
await self.ratelimit(requester)

Expand Down Expand Up @@ -171,6 +174,15 @@ async def upgrade_room(
async def _upgrade_room(
self, requester: Requester, old_room_id: str, new_version: RoomVersion
):
"""
Args:
requester: the user requesting the upgrade
old_room_id: the id of the room to be replaced
new_versions: the version to upgrade the room to
Raises:
ShadowBanError if the requester is shadow-banned.
"""
user_id = requester.user.to_string()

# start by allocating a new room id
Expand Down Expand Up @@ -257,6 +269,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, ""))

Expand Down Expand Up @@ -829,11 +844,13 @@ def create(etype: str, content: JsonDict, **kwargs) -> JsonDict:
async def send(etype: str, content: JsonDict, **kwargs) -> int:
event = create(etype, content, **kwargs)
logger.debug("Sending %s in new room", etype)
# Allow these events to be sent even if 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,
)
return last_stream_id

Expand Down
74 changes: 44 additions & 30 deletions synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ async def on_PUT(self, request, room_id, event_type, state_key, txn_id=None):
if state_key is not None:
event_dict["state_key"] = state_key

if event_type == EventTypes.Member:
try:
try:
if event_type == EventTypes.Member:
membership = content.get("membership", None)
event_id, _ = await self.room_member_handler.update_membership(
requester,
Expand All @@ -211,16 +211,16 @@ async def on_PUT(self, request, room_id, event_type, state_key, txn_id=None):
action=membership,
content=content,
)
except ShadowBanError:
event_id = "$" + random_string(43)
else:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict, txn_id=txn_id
)
event_id = event.event_id
else:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict, txn_id=txn_id
)
event_id = event.event_id
except ShadowBanError:
event_id = "$" + random_string(43)

set_tag("event_id", event_id)
ret = {"event_id": event_id}
Expand Down Expand Up @@ -253,12 +253,19 @@ async def on_POST(self, request, room_id, event_type, txn_id=None):
if b"ts" in request.args and requester.app_service:
event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)

event, _ = await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict, txn_id=txn_id
)
try:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict, txn_id=txn_id
)
event_id = event.event_id
except ShadowBanError:
event_id = "$" + random_string(43)

set_tag("event_id", event.event_id)
return 200, {"event_id": event.event_id}
set_tag("event_id", event_id)
return 200, {"event_id": event_id}

def on_GET(self, request, room_id, event_type, txn_id):
return 200, "Not implemented"
Expand Down Expand Up @@ -799,20 +806,27 @@ async def on_POST(self, request, room_id, event_id, txn_id=None):
requester = await self.auth.get_user_by_req(request)
content = parse_json_object_from_request(request)

event, _ = await self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Redaction,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
"redacts": event_id,
},
txn_id=txn_id,
)
try:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Redaction,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
"redacts": event_id,
},
txn_id=txn_id,
)
event_id = event.event_id
except ShadowBanError:
event_id = "$" + random_string(43)

set_tag("event_id", event.event_id)
return 200, {"event_id": event.event_id}
set_tag("event_id", event_id)
return 200, {"event_id": event_id}

def on_PUT(self, request, room_id, event_id, txn_id):
set_tag("txn_id", txn_id)
Expand Down
18 changes: 13 additions & 5 deletions synapse/rest/client/v2_alpha/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import logging

from synapse.api.constants import EventTypes, RelationTypes
from synapse.api.errors import SynapseError
from synapse.api.errors import ShadowBanError, SynapseError
from synapse.http.servlet import (
RestServlet,
parse_integer,
Expand All @@ -35,6 +35,7 @@
PaginationChunk,
RelationPaginationToken,
)
from synapse.util.stringutils import random_string

from ._base import client_patterns

Expand Down Expand Up @@ -111,11 +112,18 @@ async def on_PUT_or_POST(
"sender": requester.user.to_string(),
}

event, _ = await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict=event_dict, txn_id=txn_id
)
try:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester, event_dict=event_dict, txn_id=txn_id
)
event_id = event.event_id
except ShadowBanError:
event_id = "$" + random_string(43)

return 200, {"event_id": event.event_id}
return 200, {"event_id": event_id}


class RelationPaginationServlet(RestServlet):
Expand Down
14 changes: 9 additions & 5 deletions synapse/rest/client/v2_alpha/room_upgrade_rest_servlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@

import logging

from synapse.api.errors import Codes, SynapseError
from synapse.api.errors import Codes, ShadowBanError, SynapseError
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
parse_json_object_from_request,
)
from synapse.util import stringutils

from ._base import client_patterns

Expand Down Expand Up @@ -62,7 +63,6 @@ async def on_POST(self, request, room_id):

content = parse_json_object_from_request(request)
assert_params_in_dict(content, ("new_version",))
new_version = content["new_version"]

new_version = KNOWN_ROOM_VERSIONS.get(content["new_version"])
if new_version is None:
Expand All @@ -72,9 +72,13 @@ async def on_POST(self, request, room_id):
Codes.UNSUPPORTED_ROOM_VERSION,
)

new_room_id = await self._room_creation_handler.upgrade_room(
requester, room_id, new_version
)
try:
new_room_id = await self._room_creation_handler.upgrade_room(
requester, room_id, new_version
)
except ShadowBanError:
# Generate a random room ID.
new_room_id = stringutils.random_string(18)

ret = {"replacement_room": new_room_id}

Expand Down
55 changes: 54 additions & 1 deletion tests/rest/client/v1/test_rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from synapse.api.constants import EventContentFields, EventTypes, Membership
from synapse.handlers.pagination import PurgeStatus
from synapse.rest.client.v1 import directory, login, profile, room
from synapse.rest.client.v2_alpha import account
from synapse.rest.client.v2_alpha import account, room_upgrade_rest_servlet
from synapse.types import JsonDict, RoomAlias
from synapse.util.stringutils import random_string

Expand Down Expand Up @@ -1984,6 +1984,7 @@ class ShadowBannedTestCase(unittest.HomeserverTestCase):
directory.register_servlets,
login.register_servlets,
room.register_servlets,
room_upgrade_rest_servlet.register_servlets,
]

def prepare(self, reactor, clock, homeserver):
Expand Down Expand Up @@ -2076,3 +2077,55 @@ def test_create_room(self):
# Both users should be in the room.
users = self.get_success(self.store.get_users_in_room(room_id))
self.assertCountEqual(users, ["@banned:test", "@otheruser:test"])

def test_message(self):
"""Messages from shadow-banned users don't actually get sent."""

room_id = self.helper.create_room_as(
self.other_user_id, tok=self.other_access_token
)

# The user should be in the room.
self.helper.join(room_id, self.banned_user_id, tok=self.banned_access_token)

# Sending a message should complete successfully.
result = self.helper.send_event(
room_id=room_id,
type=EventTypes.Message,
content={"msgtype": "m.text", "body": "with right label"},
tok=self.banned_access_token,
)
self.assertIn("event_id", result)
event_id = result["event_id"]

latest_events = self.get_success(
self.store.get_latest_event_ids_in_room(room_id)
)
self.assertNotIn(event_id, latest_events)

def test_upgrade(self):
"""A room upgrade should fail, but look like it succeeded."""

# The create works fine.
room_id = self.helper.create_room_as(
self.banned_user_id, tok=self.banned_access_token
)

request, channel = self.make_request(
"POST",
"/_matrix/client/r0/rooms/%s/upgrade" % (room_id,),
{"new_version": "6"},
access_token=self.banned_access_token,
)
self.render(request)
self.assertEquals(200, channel.code, channel.result)
# A new room_id should be returned.
self.assertIn("replacement_room", channel.json_body)

new_room_id = channel.json_body["replacement_room"]

# It doesn't really matter what API we use here, we just want to assert
# that the room doesn't exist.
summary = self.get_success(self.store.get_room_summary(new_room_id))
# The summary should be empty since the room doesn't exist.
self.assertEqual(summary, {})

0 comments on commit cbd8d83

Please sign in to comment.