mirror of
https://github.com/element-hq/synapse.git
synced 2025-07-04 00:00:27 -04:00
Compare commits
16 Commits
179815a90f
...
007c0ea42e
Author | SHA1 | Date | |
---|---|---|---|
|
007c0ea42e | ||
|
af6ca26af0 | ||
|
17ce194081 | ||
|
8d7eddd6f9 | ||
|
65d7b41b1c | ||
|
4323879d23 | ||
|
80e0979653 | ||
|
5a53b9337f | ||
|
656b648420 | ||
|
5ad9e66fdd | ||
|
e4a1014209 | ||
|
b6aa6c0ccf | ||
|
e8d328f3bb | ||
|
8c606cd397 | ||
|
ce555e7cf7 | ||
|
0766119fc9 |
1
changelog.d/18540.feature
Normal file
1
changelog.d/18540.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add support for [MSC4293](https://github.com/matrix-org/matrix-spec-proposals/pull/4293) - Redact on Kick/Ban.
|
@ -575,3 +575,6 @@ class ExperimentalConfig(Config):
|
|||||||
|
|
||||||
# MSC4155: Invite filtering
|
# MSC4155: Invite filtering
|
||||||
self.msc4155_enabled: bool = experimental.get("msc4155_enabled", False)
|
self.msc4155_enabled: bool = experimental.get("msc4155_enabled", False)
|
||||||
|
|
||||||
|
# MSC4293: Redact on Kick/Ban
|
||||||
|
self.msc4293_enabled: bool = experimental.get("msc4293_enabled", False)
|
||||||
|
@ -1088,6 +1088,7 @@ class RoomMembershipRestServlet(TransactionRestServlet):
|
|||||||
super().__init__(hs)
|
super().__init__(hs)
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
self.config = hs.config
|
||||||
|
|
||||||
def register(self, http_server: HttpServer) -> None:
|
def register(self, http_server: HttpServer) -> None:
|
||||||
# /rooms/$roomid/[join|invite|leave|ban|unban|kick]
|
# /rooms/$roomid/[join|invite|leave|ban|unban|kick]
|
||||||
@ -1111,12 +1112,12 @@ class RoomMembershipRestServlet(TransactionRestServlet):
|
|||||||
}:
|
}:
|
||||||
raise AuthError(403, "Guest access not allowed")
|
raise AuthError(403, "Guest access not allowed")
|
||||||
|
|
||||||
content = parse_json_object_from_request(request, allow_empty_body=True)
|
request_body = parse_json_object_from_request(request, allow_empty_body=True)
|
||||||
|
|
||||||
if membership_action == "invite" and all(
|
if membership_action == "invite" and all(
|
||||||
key in content for key in ("medium", "address")
|
key in request_body for key in ("medium", "address")
|
||||||
):
|
):
|
||||||
if not all(key in content for key in ("id_server", "id_access_token")):
|
if not all(key in request_body for key in ("id_server", "id_access_token")):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
"`id_server` and `id_access_token` are required when doing 3pid invite",
|
"`id_server` and `id_access_token` are required when doing 3pid invite",
|
||||||
@ -1127,12 +1128,12 @@ class RoomMembershipRestServlet(TransactionRestServlet):
|
|||||||
await self.room_member_handler.do_3pid_invite(
|
await self.room_member_handler.do_3pid_invite(
|
||||||
room_id,
|
room_id,
|
||||||
requester.user,
|
requester.user,
|
||||||
content["medium"],
|
request_body["medium"],
|
||||||
content["address"],
|
request_body["address"],
|
||||||
content["id_server"],
|
request_body["id_server"],
|
||||||
requester,
|
requester,
|
||||||
txn_id,
|
txn_id,
|
||||||
content["id_access_token"],
|
request_body["id_access_token"],
|
||||||
)
|
)
|
||||||
except ShadowBanError:
|
except ShadowBanError:
|
||||||
# Pretend the request succeeded.
|
# Pretend the request succeeded.
|
||||||
@ -1141,12 +1142,19 @@ class RoomMembershipRestServlet(TransactionRestServlet):
|
|||||||
|
|
||||||
target = requester.user
|
target = requester.user
|
||||||
if membership_action in ["invite", "ban", "unban", "kick"]:
|
if membership_action in ["invite", "ban", "unban", "kick"]:
|
||||||
assert_params_in_dict(content, ["user_id"])
|
assert_params_in_dict(request_body, ["user_id"])
|
||||||
target = UserID.from_string(content["user_id"])
|
target = UserID.from_string(request_body["user_id"])
|
||||||
|
|
||||||
event_content = None
|
event_content = None
|
||||||
if "reason" in content:
|
if "reason" in request_body:
|
||||||
event_content = {"reason": content["reason"]}
|
event_content = {"reason": request_body["reason"]}
|
||||||
|
if self.config.experimental.msc4293_enabled:
|
||||||
|
if "org.matrix.msc4293.redact_events" in request_body:
|
||||||
|
if event_content is None:
|
||||||
|
event_content = {}
|
||||||
|
event_content["org.matrix.msc4293.redact_events"] = request_body[
|
||||||
|
"org.matrix.msc4293.redact_events"
|
||||||
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.room_member_handler.update_membership(
|
await self.room_member_handler.update_membership(
|
||||||
@ -1155,7 +1163,7 @@ class RoomMembershipRestServlet(TransactionRestServlet):
|
|||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
action=membership_action,
|
action=membership_action,
|
||||||
txn_id=txn_id,
|
txn_id=txn_id,
|
||||||
third_party_signed=content.get("third_party_signed", None),
|
third_party_signed=request_body.get("third_party_signed", None),
|
||||||
content=event_content,
|
content=event_content,
|
||||||
)
|
)
|
||||||
except ShadowBanError:
|
except ShadowBanError:
|
||||||
|
@ -108,6 +108,9 @@ SLIDING_SYNC_RELEVANT_STATE_SET = (
|
|||||||
(EventTypes.Tombstone, ""),
|
(EventTypes.Tombstone, ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# An arbitrarily large number
|
||||||
|
MAX_EVENTS = 1000000
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True, auto_attribs=True)
|
@attr.s(slots=True, auto_attribs=True)
|
||||||
class DeltaState:
|
class DeltaState:
|
||||||
@ -376,6 +379,109 @@ class PersistEventsStore:
|
|||||||
|
|
||||||
event_counter.labels(event.type, origin_type, origin_entity).inc()
|
event_counter.labels(event.type, origin_type, origin_entity).inc()
|
||||||
|
|
||||||
|
if (
|
||||||
|
not self.hs.config.experimental.msc4293_enabled
|
||||||
|
or event.type != EventTypes.Member
|
||||||
|
or event.state_key is None
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# check if this is an unban/join that will undo a ban/kick redaction for
|
||||||
|
# user in room
|
||||||
|
if event.membership in [Membership.LEAVE, Membership.JOIN]:
|
||||||
|
if event.membership == Membership.LEAVE:
|
||||||
|
# self-leave, ignore
|
||||||
|
if event.sender == event.state_key:
|
||||||
|
continue
|
||||||
|
# check to see if there is an existing ban/leave causing redactions for
|
||||||
|
# this user/room combination
|
||||||
|
res = await self.db_pool.simple_select_list(
|
||||||
|
"room_ban_redactions",
|
||||||
|
{"room_id": event.room_id, "user_id": event.state_key},
|
||||||
|
["room_id", "user_id"],
|
||||||
|
)
|
||||||
|
if res:
|
||||||
|
# if so, update the entry with the stream ordering when the redactions should
|
||||||
|
# stop
|
||||||
|
await self.db_pool.simple_update(
|
||||||
|
"room_ban_redactions",
|
||||||
|
{"room_id": event.room_id, "user_id": event.state_key},
|
||||||
|
{
|
||||||
|
"redact_end_ordering": event.internal_metadata.stream_ordering
|
||||||
|
},
|
||||||
|
desc="room_ban_redactions update redact_end_ordering",
|
||||||
|
)
|
||||||
|
|
||||||
|
# check for msc4293 redact_events flag and apply if found
|
||||||
|
if event.membership not in [Membership.LEAVE, Membership.BAN]:
|
||||||
|
continue
|
||||||
|
redact = event.content.get("org.matrix.msc4293.redact_events", False)
|
||||||
|
if not redact or not isinstance(redact, bool):
|
||||||
|
continue
|
||||||
|
# self-bans currently are not authorized so we don't check for that
|
||||||
|
# case
|
||||||
|
if (
|
||||||
|
event.membership == Membership.LEAVE
|
||||||
|
and event.sender == event.state_key
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
# check that sender can redact
|
||||||
|
state_filter = StateFilter.from_types([(EventTypes.PowerLevels, "")])
|
||||||
|
state = await self.store.get_partial_filtered_current_state_ids(
|
||||||
|
event.room_id, state_filter
|
||||||
|
)
|
||||||
|
pl_id = state[(EventTypes.PowerLevels, "")]
|
||||||
|
pl_event = await self.store.get_event(pl_id)
|
||||||
|
if pl_event:
|
||||||
|
sender_level = pl_event.content.get("users", {}).get(event.sender)
|
||||||
|
if sender_level is None:
|
||||||
|
sender_level = pl_event.content.get("users_default", 0)
|
||||||
|
|
||||||
|
redact_level = pl_event.content.get("redact")
|
||||||
|
if not redact_level:
|
||||||
|
redact_level = pl_event.content.get("events_default", 0)
|
||||||
|
|
||||||
|
room_redaction_level = pl_event.content.get("events", {}).get(
|
||||||
|
"m.room.redaction"
|
||||||
|
)
|
||||||
|
if room_redaction_level:
|
||||||
|
if sender_level < room_redaction_level:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sender_level >= redact_level:
|
||||||
|
await self.db_pool.simple_upsert(
|
||||||
|
"room_ban_redactions",
|
||||||
|
{"room_id": event.room_id, "user_id": event.state_key},
|
||||||
|
{
|
||||||
|
"redacting_event_id": event.event_id,
|
||||||
|
"redact_end_ordering": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"room_id": event.room_id,
|
||||||
|
"user_id": event.state_key,
|
||||||
|
"redacting_event_id": event.event_id,
|
||||||
|
"redact_end_ordering": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# normally the cache entry for a redacted event would be invalidated
|
||||||
|
# by an arriving redaction event, but since we are not creating redaction
|
||||||
|
# events we invalidate manually
|
||||||
|
ids_to_redact = (
|
||||||
|
await self.store.get_events_sent_by_user_in_room(
|
||||||
|
event.state_key, event.room_id, limit=MAX_EVENTS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not ids_to_redact:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for id in ids_to_redact:
|
||||||
|
await self.db_pool.runInteraction(
|
||||||
|
"invalidate cache",
|
||||||
|
self.store.invalidate_get_event_cache_after_txn,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
|
||||||
if new_forward_extremities:
|
if new_forward_extremities:
|
||||||
self.store.get_latest_event_ids_in_room.prefill(
|
self.store.get_latest_event_ids_in_room.prefill(
|
||||||
(room_id,), frozenset(new_forward_extremities)
|
(room_id,), frozenset(new_forward_extremities)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# [This file includes modifications made by New Vector Limited]
|
# [This file includes modifications made by New Vector Limited]
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import weakref
|
import weakref
|
||||||
@ -1571,6 +1571,42 @@ class EventsWorkerStore(SQLBaseStore):
|
|||||||
if d:
|
if d:
|
||||||
d.redactions.append(redacter)
|
d.redactions.append(redacter)
|
||||||
|
|
||||||
|
# check for MSC4932 redactions
|
||||||
|
to_check = []
|
||||||
|
events: List[_EventRow] = []
|
||||||
|
for e in evs:
|
||||||
|
event = event_dict.get(e)
|
||||||
|
if not event:
|
||||||
|
continue
|
||||||
|
events.append(event)
|
||||||
|
event_json = json.loads(event.json)
|
||||||
|
room_id = event_json.get("room_id")
|
||||||
|
user_id = event_json.get("sender")
|
||||||
|
to_check.append((room_id, user_id))
|
||||||
|
|
||||||
|
# likely that some of these events may be for the same room/user combo, in
|
||||||
|
# which case we don't need to do redundant queries
|
||||||
|
to_check_set = set(to_check)
|
||||||
|
for room_and_user in to_check_set:
|
||||||
|
room_redactions_sql = "SELECT redacting_event_id, redact_end_ordering FROM room_ban_redactions WHERE room_id = ? and user_id = ?"
|
||||||
|
txn.execute(room_redactions_sql, room_and_user)
|
||||||
|
|
||||||
|
res = txn.fetchone()
|
||||||
|
# we have a redaction for a room, user_id combo - apply it to matching events
|
||||||
|
if not res:
|
||||||
|
continue
|
||||||
|
for e_row in events:
|
||||||
|
e_json = json.loads(e_row.json)
|
||||||
|
room_id = e_json.get("room_id")
|
||||||
|
user_id = e_json.get("sender")
|
||||||
|
if room_and_user != (room_id, user_id):
|
||||||
|
continue
|
||||||
|
redacting_event_id, redact_end_ordering = res
|
||||||
|
if redact_end_ordering:
|
||||||
|
if e_row.stream_ordering < redact_end_ordering:
|
||||||
|
e_row.redactions.append(redacting_event_id)
|
||||||
|
else:
|
||||||
|
e_row.redactions.append(redacting_event_id)
|
||||||
return event_dict
|
return event_dict
|
||||||
|
|
||||||
def _maybe_redact_event_row(
|
def _maybe_redact_event_row(
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
--
|
||||||
|
-- This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
--
|
||||||
|
-- Copyright (C) 2025 New Vector, Ltd
|
||||||
|
--
|
||||||
|
-- This program is free software: you can redistribute it and/or modify
|
||||||
|
-- it under the terms of the GNU Affero General Public License as
|
||||||
|
-- published by the Free Software Foundation, either version 3 of the
|
||||||
|
-- License, or (at your option) any later version.
|
||||||
|
--
|
||||||
|
-- See the GNU Affero General Public License for more details:
|
||||||
|
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
|
||||||
|
CREATE TABLE room_ban_redactions(
|
||||||
|
room_id text NOT NULL,
|
||||||
|
user_id text NOT NULL,
|
||||||
|
redacting_event_id text NOT NULL,
|
||||||
|
redact_end_ordering bigint DEFAULT NULL, -- stream ordering after which redactions are not applied
|
||||||
|
CONSTRAINT room_ban_redaction_uniqueness UNIQUE (room_id, user_id)
|
||||||
|
);
|
||||||
|
|
@ -43,8 +43,9 @@ from synapse.api.constants import (
|
|||||||
RoomTypes,
|
RoomTypes,
|
||||||
)
|
)
|
||||||
from synapse.api.errors import Codes, HttpResponseException
|
from synapse.api.errors import Codes, HttpResponseException
|
||||||
|
from synapse.api.room_versions import RoomVersions
|
||||||
from synapse.appservice import ApplicationService
|
from synapse.appservice import ApplicationService
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase, make_event_from_dict
|
||||||
from synapse.events.snapshot import EventContext
|
from synapse.events.snapshot import EventContext
|
||||||
from synapse.rest import admin
|
from synapse.rest import admin
|
||||||
from synapse.rest.client import (
|
from synapse.rest.client import (
|
||||||
@ -4401,3 +4402,985 @@ class RoomParticipantTestCase(unittest.HomeserverTestCase):
|
|||||||
self.store.get_room_participation(self.user2, self.room1)
|
self.store.get_room_participation(self.user2, self.room1)
|
||||||
)
|
)
|
||||||
self.assertFalse(participant)
|
self.assertFalse(participant)
|
||||||
|
|
||||||
|
|
||||||
|
class MSC4293RedactOnBanKickTestCase(unittest.FederatingHomeserverTestCase):
|
||||||
|
servlets = [
|
||||||
|
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||||
|
room.register_servlets,
|
||||||
|
login.register_servlets,
|
||||||
|
admin.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||||
|
super().prepare(reactor, clock, hs)
|
||||||
|
self.creator = self.register_user("creator", "test")
|
||||||
|
self.creator_tok = self.login("creator", "test")
|
||||||
|
|
||||||
|
self.bad_user_id = self.register_user("bad", "test")
|
||||||
|
self.bad_tok = self.login("bad", "test")
|
||||||
|
|
||||||
|
self.room_id = self.helper.create_room_as(self.creator, tok=self.creator_tok)
|
||||||
|
|
||||||
|
self.store = hs.get_datastores().main
|
||||||
|
self._storage_controllers = hs.get_storage_controllers()
|
||||||
|
|
||||||
|
self.federation_event_handler = self.hs.get_federation_event_handler()
|
||||||
|
|
||||||
|
self.hs.config.experimental.msc4293_enabled = True
|
||||||
|
|
||||||
|
def _check_redactions(
|
||||||
|
self,
|
||||||
|
original_events: List[EventBase],
|
||||||
|
pulled_events: List[JsonDict],
|
||||||
|
expect_redaction: bool,
|
||||||
|
reason: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Checks a set of original events against a second set of the same events, pulled
|
||||||
|
from the /messages api. If expect_redaction is true, we expect that the second
|
||||||
|
set of events will be redacted, and the test will fail if that is not the case.
|
||||||
|
Otherwise, verifies that the events have not been redacted and fails if not.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
original_events: A list of the original events sent
|
||||||
|
pulled_events: A list of the same events as the orignal events, fetched
|
||||||
|
over the /messages api
|
||||||
|
expect_redaction: Whether or not the pulled_events should be redacted
|
||||||
|
reason: If the events are expected to be redacted, the expected reason
|
||||||
|
for the redaction
|
||||||
|
|
||||||
|
"""
|
||||||
|
if expect_redaction:
|
||||||
|
redacted_count = 0
|
||||||
|
for pulled_event in pulled_events:
|
||||||
|
for old_event in original_events:
|
||||||
|
if pulled_event["event_id"] != old_event.event_id:
|
||||||
|
continue
|
||||||
|
# we have a matching event, check that it is redacted
|
||||||
|
event_content = pulled_event["content"]
|
||||||
|
if event_content:
|
||||||
|
self.fail(f"Expected event {pulled_event} to be redacted")
|
||||||
|
redacting_event = pulled_event.get("redacted_because")
|
||||||
|
if not redacting_event:
|
||||||
|
self.fail(
|
||||||
|
f"Expected event {pulled_event} to have a redacting event."
|
||||||
|
)
|
||||||
|
# check that the redacting event records the expected reason, and the
|
||||||
|
# redact_events flag
|
||||||
|
content = redacting_event["content"]
|
||||||
|
self.assertEqual(content["reason"], reason)
|
||||||
|
self.assertEqual(content["org.matrix.msc4293.redact_events"], True)
|
||||||
|
redacted_count += 1
|
||||||
|
# all provided events should be redacted
|
||||||
|
self.assertEqual(len(original_events), redacted_count)
|
||||||
|
else:
|
||||||
|
unredacted_events = 0
|
||||||
|
for pulled_event in pulled_events:
|
||||||
|
for old_event in original_events:
|
||||||
|
if pulled_event["event_id"] != old_event.event_id:
|
||||||
|
continue
|
||||||
|
# we have a matching event, make sure it is not redacted
|
||||||
|
redacted_because = pulled_event.get("redacted_because")
|
||||||
|
if redacted_because:
|
||||||
|
self.fail("Event should not have been redacted")
|
||||||
|
self.assertEqual(old_event.content, pulled_event["content"])
|
||||||
|
unredacted_events += 1
|
||||||
|
# all provided events should not have been redacted
|
||||||
|
self.assertEqual(unredacted_events, len(original_events))
|
||||||
|
|
||||||
|
def test_banning_local_member_with_flag_redacts_their_events(self) -> None:
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
originals = []
|
||||||
|
for i in range(5):
|
||||||
|
event = {"body": f"bothersome noise {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
originals.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events for comparison
|
||||||
|
original_events = [self.get_success(self.store.get_event(x)) for x in originals]
|
||||||
|
|
||||||
|
# creator bans user with redaction flag set
|
||||||
|
content = {
|
||||||
|
"reason": "flooding",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.creator,
|
||||||
|
self.bad_user_id,
|
||||||
|
"ban",
|
||||||
|
content,
|
||||||
|
self.creator_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
original_events,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
expect_redaction=True,
|
||||||
|
reason="flooding",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_banning_remote_member_with_flag_redacts_their_events(self) -> None:
|
||||||
|
bad_user = "@remote_bad_user:" + self.OTHER_SERVER_NAME
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{self.room_id}/{bad_user}?ver=10",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
RoomVersions.V10,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{self.room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
|
||||||
|
# the room should show that the bad user is a member
|
||||||
|
r = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(r[("m.room.member", bad_user)].membership, "join")
|
||||||
|
|
||||||
|
auth_ids = [
|
||||||
|
r[("m.room.create", "")].event_id,
|
||||||
|
r[("m.room.power_levels", "")].event_id,
|
||||||
|
r[("m.room.member", "@remote_bad_user:other.example.com")].event_id,
|
||||||
|
]
|
||||||
|
original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"remote bummer{i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
original_messages.append(remote_message)
|
||||||
|
|
||||||
|
# creator bans bad user with redaction flag set
|
||||||
|
content = {
|
||||||
|
"reason": "bummer messages",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
res = self.helper.change_membership(
|
||||||
|
self.room_id, self.creator, bad_user, "ban", content, self.creator_tok
|
||||||
|
)
|
||||||
|
ban_event_id = res["event_id"]
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
original_messages,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
expect_redaction=True,
|
||||||
|
reason="bummer messages",
|
||||||
|
)
|
||||||
|
|
||||||
|
# any future messages that are soft-failed are also redacted - send messages referencing
|
||||||
|
# dag before ban, they should be soft-failed but also redacted
|
||||||
|
new_original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"soft-fail remote bummer{i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
new_original_messages.append(remote_message)
|
||||||
|
|
||||||
|
# pull them from the db to check because they should be soft-failed and thus not available over
|
||||||
|
# cs-api
|
||||||
|
for message in new_original_messages:
|
||||||
|
original = self.get_success(self.store.get_event(message.event_id))
|
||||||
|
if not original:
|
||||||
|
self.fail("Expected to find remote message in DB")
|
||||||
|
redacted_because = original.unsigned.get("redacted_because")
|
||||||
|
if not redacted_because:
|
||||||
|
self.fail("Did not find redacted_because field")
|
||||||
|
self.assertEqual(redacted_because.event_id, ban_event_id)
|
||||||
|
|
||||||
|
def test_unbanning_remote_user_stops_redaction_action(self) -> None:
|
||||||
|
bad_user = "@remote_bad_user:" + self.OTHER_SERVER_NAME
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{self.room_id}/{bad_user}?ver=10",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
RoomVersions.V10,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{self.room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
|
||||||
|
# the room should show that the bad user is a member
|
||||||
|
r = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(r[("m.room.member", bad_user)].membership, "join")
|
||||||
|
|
||||||
|
auth_ids = [
|
||||||
|
r[("m.room.create", "")].event_id,
|
||||||
|
r[("m.room.power_levels", "")].event_id,
|
||||||
|
r[("m.room.member", "@remote_bad_user:other.example.com")].event_id,
|
||||||
|
]
|
||||||
|
original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"annoying messages {i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
original_messages.append(remote_message)
|
||||||
|
|
||||||
|
# creator bans bad user with redaction flag set
|
||||||
|
content = {
|
||||||
|
"reason": "this dude sucks",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id, self.creator, bad_user, "ban", content, self.creator_tok
|
||||||
|
)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
original_messages,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
True,
|
||||||
|
reason="this dude sucks",
|
||||||
|
)
|
||||||
|
|
||||||
|
# unban user
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id, self.creator, bad_user, "unban", {}, self.creator_tok
|
||||||
|
)
|
||||||
|
|
||||||
|
# user should be able to join again
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{self.room_id}/{bad_user}?ver=10",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
RoomVersions.V10,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{self.room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
|
||||||
|
# the room should show that the bad user is a member again
|
||||||
|
new_state = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(new_state[("m.room.member", bad_user)].membership, "join")
|
||||||
|
|
||||||
|
new_state = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
auth_ids = [
|
||||||
|
new_state[("m.room.create", "")].event_id,
|
||||||
|
new_state[("m.room.power_levels", "")].event_id,
|
||||||
|
new_state[("m.room.member", "@remote_bad_user:other.example.com")].event_id,
|
||||||
|
]
|
||||||
|
|
||||||
|
# messages after unban and join proceed unredacted
|
||||||
|
new_original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"no longer a bummer {i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
new_original_messages.append(remote_message)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(new_original_messages, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
def test_redaction_flag_ignored_for_user_if_banner_lacks_redaction_power(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
# change power levels so creator can ban but not redact
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room_id,
|
||||||
|
"m.room.power_levels",
|
||||||
|
{"events_default": 0, "redact": 100, "users": {self.creator: 75}},
|
||||||
|
tok=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
original_ids = []
|
||||||
|
for i in range(15):
|
||||||
|
event = {"body": f"being a menace {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
original_ids.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events before ban
|
||||||
|
originals = [self.get_success(self.store.get_event(x)) for x in original_ids]
|
||||||
|
|
||||||
|
# creator bans bad user with redaction flag
|
||||||
|
content = {
|
||||||
|
"reason": "flooding",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.creator,
|
||||||
|
self.bad_user_id,
|
||||||
|
"ban",
|
||||||
|
content,
|
||||||
|
self.creator_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
# messages are not redacted
|
||||||
|
self._check_redactions(originals, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
def test_kicking_local_member_with_flag_redacts_their_events(self) -> None:
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
originals = []
|
||||||
|
for i in range(5):
|
||||||
|
event = {"body": f"bothersome noise {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
originals.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events for comparison
|
||||||
|
original_events = [self.get_success(self.store.get_event(x)) for x in originals]
|
||||||
|
|
||||||
|
# creator kicks user with redaction flag set
|
||||||
|
content = {
|
||||||
|
"reason": "flooding",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.creator,
|
||||||
|
self.bad_user_id,
|
||||||
|
"kick",
|
||||||
|
content,
|
||||||
|
self.creator_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
original_events,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
expect_redaction=True,
|
||||||
|
reason="flooding",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_kicking_remote_member_with_flag_redacts_their_events(self) -> None:
|
||||||
|
bad_user = "@remote_bad_user:" + self.OTHER_SERVER_NAME
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{self.room_id}/{bad_user}?ver=10",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
RoomVersions.V10,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{self.room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
|
||||||
|
# the room should show that the bad user is a member
|
||||||
|
r = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(r[("m.room.member", bad_user)].membership, "join")
|
||||||
|
|
||||||
|
auth_ids = [
|
||||||
|
r[("m.room.create", "")].event_id,
|
||||||
|
r[("m.room.power_levels", "")].event_id,
|
||||||
|
r[("m.room.member", "@remote_bad_user:other.example.com")].event_id,
|
||||||
|
]
|
||||||
|
original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"remote bummer{i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
original_messages.append(remote_message)
|
||||||
|
|
||||||
|
# creator kicks bad user with redaction flag set
|
||||||
|
content = {
|
||||||
|
"reason": "bummer messages",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
res = self.helper.change_membership(
|
||||||
|
self.room_id, self.creator, bad_user, "kick", content, self.creator_tok
|
||||||
|
)
|
||||||
|
ban_event_id = res["event_id"]
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
original_messages,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
expect_redaction=True,
|
||||||
|
reason="bummer messages",
|
||||||
|
)
|
||||||
|
|
||||||
|
# any future messages that are soft-failed are also redacted - send messages referencing
|
||||||
|
# dag before ban, they should be soft-failed but also redacted
|
||||||
|
new_original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"soft-fail remote bummer{i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
new_original_messages.append(remote_message)
|
||||||
|
|
||||||
|
# pull them from the db to check because they should be soft-failed and thus not available over
|
||||||
|
# cs-api
|
||||||
|
for message in new_original_messages:
|
||||||
|
original = self.get_success(self.store.get_event(message.event_id))
|
||||||
|
if not original:
|
||||||
|
self.fail("Expected to find remote message in DB")
|
||||||
|
self.assertEqual(original.unsigned["redacted_by"], ban_event_id)
|
||||||
|
|
||||||
|
def test_rejoining_kicked_remote_user_stops_redaction_action(self) -> None:
|
||||||
|
bad_user = "@remote_bad_user:" + self.OTHER_SERVER_NAME
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{self.room_id}/{bad_user}?ver=10",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
RoomVersions.V10,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{self.room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
|
||||||
|
# the room should show that the bad user is a member
|
||||||
|
r = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(r[("m.room.member", bad_user)].membership, "join")
|
||||||
|
|
||||||
|
auth_ids = [
|
||||||
|
r[("m.room.create", "")].event_id,
|
||||||
|
r[("m.room.power_levels", "")].event_id,
|
||||||
|
r[("m.room.member", "@remote_bad_user:other.example.com")].event_id,
|
||||||
|
]
|
||||||
|
original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"annoying messages {i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
original_messages.append(remote_message)
|
||||||
|
|
||||||
|
# creator kicks bad user with redaction flag set
|
||||||
|
content = {
|
||||||
|
"reason": "this dude sucks",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id, self.creator, bad_user, "kick", content, self.creator_tok
|
||||||
|
)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
original_messages,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
True,
|
||||||
|
reason="this dude sucks",
|
||||||
|
)
|
||||||
|
|
||||||
|
# user re-joins after kick
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{self.room_id}/{bad_user}?ver=10",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
RoomVersions.V10,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{self.room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
|
||||||
|
# the room should show that the bad user is a member again
|
||||||
|
new_state = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(new_state[("m.room.member", bad_user)].membership, "join")
|
||||||
|
|
||||||
|
new_state = self.get_success(
|
||||||
|
self._storage_controllers.state.get_current_state(self.room_id)
|
||||||
|
)
|
||||||
|
auth_ids = [
|
||||||
|
new_state[("m.room.create", "")].event_id,
|
||||||
|
new_state[("m.room.power_levels", "")].event_id,
|
||||||
|
new_state[("m.room.member", "@remote_bad_user:other.example.com")].event_id,
|
||||||
|
]
|
||||||
|
|
||||||
|
# messages after kick and re-join proceed unredacted
|
||||||
|
new_original_messages = []
|
||||||
|
for i in range(5):
|
||||||
|
remote_message = make_event_from_dict(
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
{
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": bad_user,
|
||||||
|
"depth": 1000,
|
||||||
|
"origin_server_ts": 1,
|
||||||
|
"type": "m.room.message",
|
||||||
|
"content": {"body": f"no longer a bummer {i}"},
|
||||||
|
"auth_events": auth_ids,
|
||||||
|
"prev_events": auth_ids,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
room_version=RoomVersions.V10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.get_success(
|
||||||
|
self.federation_event_handler.on_receive_pdu(
|
||||||
|
self.OTHER_SERVER_NAME, remote_message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
new_original_messages.append(remote_message)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(new_original_messages, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
def test_redaction_flag_ignored_for_user_if_kicker_lacks_redaction_power(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
# change power levels so creator can kick but not redact
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room_id,
|
||||||
|
"m.room.power_levels",
|
||||||
|
{"events_default": 0, "redact": 100, "users": {self.creator: 75}},
|
||||||
|
tok=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
original_ids = []
|
||||||
|
for i in range(15):
|
||||||
|
event = {"body": f"being a menace {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
original_ids.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events before ban
|
||||||
|
originals = [self.get_success(self.store.get_event(x)) for x in original_ids]
|
||||||
|
|
||||||
|
# creator kicks bad user with redaction flag
|
||||||
|
content = {
|
||||||
|
"reason": "flooding",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.creator,
|
||||||
|
self.bad_user_id,
|
||||||
|
"kick",
|
||||||
|
content,
|
||||||
|
self.creator_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
# messages are not redacted
|
||||||
|
self._check_redactions(originals, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
def test_MSC4293_flag_ignored_in_other_membership_events(self) -> None:
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
original_ids = []
|
||||||
|
for i in range(15):
|
||||||
|
event = {"body": f"being a menace {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
original_ids.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events before ban
|
||||||
|
originals = [self.get_success(self.store.get_event(x)) for x in original_ids]
|
||||||
|
|
||||||
|
# bad user leaves on their own with flag
|
||||||
|
content = {
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.bad_user_id,
|
||||||
|
self.bad_user_id,
|
||||||
|
"leave",
|
||||||
|
content,
|
||||||
|
self.bad_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# their messages are not redacted
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(originals, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
# bad user is invited with flag in invite event
|
||||||
|
content = {
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.creator,
|
||||||
|
self.bad_user_id,
|
||||||
|
"invite",
|
||||||
|
content,
|
||||||
|
self.creator_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# their messages are still not redacted
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(originals, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
# bad user joins with flag in invite event
|
||||||
|
content = {
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
}
|
||||||
|
self.helper.change_membership(
|
||||||
|
self.room_id,
|
||||||
|
self.bad_user_id,
|
||||||
|
self.bad_user_id,
|
||||||
|
"join",
|
||||||
|
content,
|
||||||
|
self.bad_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
# and still their messages are not redacted
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(originals, channel.json_body["chunk"], False)
|
||||||
|
|
||||||
|
def test_MSC4293_redaction_applied_via_kick_api(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that MSC4239 field passed through and applied when using /kick
|
||||||
|
"""
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
original_ids = []
|
||||||
|
for i in range(15):
|
||||||
|
event = {"body": f"being a menace {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
original_ids.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events before kick
|
||||||
|
originals = [self.get_success(self.store.get_event(x)) for x in original_ids]
|
||||||
|
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
f"/_matrix/client/v3/rooms/{self.room_id}/kick",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
content={
|
||||||
|
"reason": "being annoying",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
"user_id": self.bad_user_id,
|
||||||
|
},
|
||||||
|
shorthand=False,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
originals,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
expect_redaction=True,
|
||||||
|
reason="being annoying",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_MSC4293_redaction_applied_via_ban_api(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that MSC4239 field passed through and applied when using /ban
|
||||||
|
"""
|
||||||
|
self.helper.join(self.room_id, self.bad_user_id, tok=self.bad_tok)
|
||||||
|
|
||||||
|
# bad user send some messages
|
||||||
|
original_ids = []
|
||||||
|
for i in range(15):
|
||||||
|
event = {"body": f"being a menace {i}", "msgtype": "m.text"}
|
||||||
|
res = self.helper.send_event(
|
||||||
|
self.room_id, "m.room.message", event, tok=self.bad_tok, expect_code=200
|
||||||
|
)
|
||||||
|
original_ids.append(res["event_id"])
|
||||||
|
|
||||||
|
# grab original events before ban
|
||||||
|
originals = [self.get_success(self.store.get_event(x)) for x in original_ids]
|
||||||
|
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
f"/_matrix/client/v3/rooms/{self.room_id}/ban",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
content={
|
||||||
|
"reason": "being disruptive",
|
||||||
|
"org.matrix.msc4293.redact_events": True,
|
||||||
|
"user_id": self.bad_user_id,
|
||||||
|
},
|
||||||
|
shorthand=False,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
|
||||||
|
filter = json.dumps({"types": [EventTypes.Message]})
|
||||||
|
channel = self.make_request(
|
||||||
|
"GET",
|
||||||
|
f"rooms/{self.room_id}/messages?filter={filter}&limit=50",
|
||||||
|
access_token=self.creator_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200)
|
||||||
|
self._check_redactions(
|
||||||
|
originals,
|
||||||
|
channel.json_body["chunk"],
|
||||||
|
expect_redaction=True,
|
||||||
|
reason="being disruptive",
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user