mirror of
https://git.anonymousland.org/anonymousland/synapse-product.git
synced 2024-12-12 12:14:18 -05:00
Implement MSC2176: Updated redaction rules (#8984)
An experimental room version ("org.matrix.msc2176") contains the new redaction rules for testing.
This commit is contained in:
parent
111b673fc1
commit
9dde9c9f01
1
changelog.d/8984.feature
Normal file
1
changelog.d/8984.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement [MSC2176](https://github.com/matrix-org/matrix-doc/pull/2176) in an experimental room version.
|
@ -51,11 +51,11 @@ class RoomDisposition:
|
|||||||
class RoomVersion:
|
class RoomVersion:
|
||||||
"""An object which describes the unique attributes of a room version."""
|
"""An object which describes the unique attributes of a room version."""
|
||||||
|
|
||||||
identifier = attr.ib() # str; the identifier for this version
|
identifier = attr.ib(type=str) # the identifier for this version
|
||||||
disposition = attr.ib() # str; one of the RoomDispositions
|
disposition = attr.ib(type=str) # one of the RoomDispositions
|
||||||
event_format = attr.ib() # int; one of the EventFormatVersions
|
event_format = attr.ib(type=int) # one of the EventFormatVersions
|
||||||
state_res = attr.ib() # int; one of the StateResolutionVersions
|
state_res = attr.ib(type=int) # one of the StateResolutionVersions
|
||||||
enforce_key_validity = attr.ib() # bool
|
enforce_key_validity = attr.ib(type=bool)
|
||||||
|
|
||||||
# bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules
|
# bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules
|
||||||
special_case_aliases_auth = attr.ib(type=bool)
|
special_case_aliases_auth = attr.ib(type=bool)
|
||||||
@ -64,9 +64,11 @@ class RoomVersion:
|
|||||||
# * Floats
|
# * Floats
|
||||||
# * NaN, Infinity, -Infinity
|
# * NaN, Infinity, -Infinity
|
||||||
strict_canonicaljson = attr.ib(type=bool)
|
strict_canonicaljson = attr.ib(type=bool)
|
||||||
# bool: MSC2209: Check 'notifications' key while verifying
|
# MSC2209: Check 'notifications' key while verifying
|
||||||
# m.room.power_levels auth rules.
|
# m.room.power_levels auth rules.
|
||||||
limit_notifications_power_levels = attr.ib(type=bool)
|
limit_notifications_power_levels = attr.ib(type=bool)
|
||||||
|
# MSC2174/MSC2176: Apply updated redaction rules algorithm.
|
||||||
|
msc2176_redaction_rules = attr.ib(type=bool)
|
||||||
|
|
||||||
|
|
||||||
class RoomVersions:
|
class RoomVersions:
|
||||||
@ -79,6 +81,7 @@ class RoomVersions:
|
|||||||
special_case_aliases_auth=True,
|
special_case_aliases_auth=True,
|
||||||
strict_canonicaljson=False,
|
strict_canonicaljson=False,
|
||||||
limit_notifications_power_levels=False,
|
limit_notifications_power_levels=False,
|
||||||
|
msc2176_redaction_rules=False,
|
||||||
)
|
)
|
||||||
V2 = RoomVersion(
|
V2 = RoomVersion(
|
||||||
"2",
|
"2",
|
||||||
@ -89,6 +92,7 @@ class RoomVersions:
|
|||||||
special_case_aliases_auth=True,
|
special_case_aliases_auth=True,
|
||||||
strict_canonicaljson=False,
|
strict_canonicaljson=False,
|
||||||
limit_notifications_power_levels=False,
|
limit_notifications_power_levels=False,
|
||||||
|
msc2176_redaction_rules=False,
|
||||||
)
|
)
|
||||||
V3 = RoomVersion(
|
V3 = RoomVersion(
|
||||||
"3",
|
"3",
|
||||||
@ -99,6 +103,7 @@ class RoomVersions:
|
|||||||
special_case_aliases_auth=True,
|
special_case_aliases_auth=True,
|
||||||
strict_canonicaljson=False,
|
strict_canonicaljson=False,
|
||||||
limit_notifications_power_levels=False,
|
limit_notifications_power_levels=False,
|
||||||
|
msc2176_redaction_rules=False,
|
||||||
)
|
)
|
||||||
V4 = RoomVersion(
|
V4 = RoomVersion(
|
||||||
"4",
|
"4",
|
||||||
@ -109,6 +114,7 @@ class RoomVersions:
|
|||||||
special_case_aliases_auth=True,
|
special_case_aliases_auth=True,
|
||||||
strict_canonicaljson=False,
|
strict_canonicaljson=False,
|
||||||
limit_notifications_power_levels=False,
|
limit_notifications_power_levels=False,
|
||||||
|
msc2176_redaction_rules=False,
|
||||||
)
|
)
|
||||||
V5 = RoomVersion(
|
V5 = RoomVersion(
|
||||||
"5",
|
"5",
|
||||||
@ -119,6 +125,7 @@ class RoomVersions:
|
|||||||
special_case_aliases_auth=True,
|
special_case_aliases_auth=True,
|
||||||
strict_canonicaljson=False,
|
strict_canonicaljson=False,
|
||||||
limit_notifications_power_levels=False,
|
limit_notifications_power_levels=False,
|
||||||
|
msc2176_redaction_rules=False,
|
||||||
)
|
)
|
||||||
V6 = RoomVersion(
|
V6 = RoomVersion(
|
||||||
"6",
|
"6",
|
||||||
@ -129,6 +136,18 @@ class RoomVersions:
|
|||||||
special_case_aliases_auth=False,
|
special_case_aliases_auth=False,
|
||||||
strict_canonicaljson=True,
|
strict_canonicaljson=True,
|
||||||
limit_notifications_power_levels=True,
|
limit_notifications_power_levels=True,
|
||||||
|
msc2176_redaction_rules=False,
|
||||||
|
)
|
||||||
|
MSC2176 = RoomVersion(
|
||||||
|
"org.matrix.msc2176",
|
||||||
|
RoomDisposition.UNSTABLE,
|
||||||
|
EventFormatVersions.V3,
|
||||||
|
StateResolutionVersions.V2,
|
||||||
|
enforce_key_validity=True,
|
||||||
|
special_case_aliases_auth=False,
|
||||||
|
strict_canonicaljson=True,
|
||||||
|
limit_notifications_power_levels=True,
|
||||||
|
msc2176_redaction_rules=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -141,5 +160,6 @@ KNOWN_ROOM_VERSIONS = {
|
|||||||
RoomVersions.V4,
|
RoomVersions.V4,
|
||||||
RoomVersions.V5,
|
RoomVersions.V5,
|
||||||
RoomVersions.V6,
|
RoomVersions.V6,
|
||||||
|
RoomVersions.MSC2176,
|
||||||
)
|
)
|
||||||
} # type: Dict[str, RoomVersion]
|
} # type: Dict[str, RoomVersion]
|
||||||
|
@ -79,13 +79,15 @@ def prune_event_dict(room_version: RoomVersion, event_dict: dict) -> dict:
|
|||||||
"state_key",
|
"state_key",
|
||||||
"depth",
|
"depth",
|
||||||
"prev_events",
|
"prev_events",
|
||||||
"prev_state",
|
|
||||||
"auth_events",
|
"auth_events",
|
||||||
"origin",
|
"origin",
|
||||||
"origin_server_ts",
|
"origin_server_ts",
|
||||||
"membership",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Room versions from before MSC2176 had additional allowed keys.
|
||||||
|
if not room_version.msc2176_redaction_rules:
|
||||||
|
allowed_keys.extend(["prev_state", "membership"])
|
||||||
|
|
||||||
event_type = event_dict["type"]
|
event_type = event_dict["type"]
|
||||||
|
|
||||||
new_content = {}
|
new_content = {}
|
||||||
@ -98,6 +100,10 @@ def prune_event_dict(room_version: RoomVersion, event_dict: dict) -> dict:
|
|||||||
if event_type == EventTypes.Member:
|
if event_type == EventTypes.Member:
|
||||||
add_fields("membership")
|
add_fields("membership")
|
||||||
elif event_type == EventTypes.Create:
|
elif event_type == EventTypes.Create:
|
||||||
|
# MSC2176 rules state that create events cannot be redacted.
|
||||||
|
if room_version.msc2176_redaction_rules:
|
||||||
|
return event_dict
|
||||||
|
|
||||||
add_fields("creator")
|
add_fields("creator")
|
||||||
elif event_type == EventTypes.JoinRules:
|
elif event_type == EventTypes.JoinRules:
|
||||||
add_fields("join_rule")
|
add_fields("join_rule")
|
||||||
@ -112,10 +118,16 @@ def prune_event_dict(room_version: RoomVersion, event_dict: dict) -> dict:
|
|||||||
"kick",
|
"kick",
|
||||||
"redact",
|
"redact",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if room_version.msc2176_redaction_rules:
|
||||||
|
add_fields("invite")
|
||||||
|
|
||||||
elif event_type == EventTypes.Aliases and room_version.special_case_aliases_auth:
|
elif event_type == EventTypes.Aliases and room_version.special_case_aliases_auth:
|
||||||
add_fields("aliases")
|
add_fields("aliases")
|
||||||
elif event_type == EventTypes.RoomHistoryVisibility:
|
elif event_type == EventTypes.RoomHistoryVisibility:
|
||||||
add_fields("history_visibility")
|
add_fields("history_visibility")
|
||||||
|
elif event_type == EventTypes.Redaction and room_version.msc2176_redaction_rules:
|
||||||
|
add_fields("redacts")
|
||||||
|
|
||||||
allowed_fields = {k: v for k, v in event_dict.items() if k in allowed_keys}
|
allowed_fields = {k: v for k, v in event_dict.items() if k in allowed_keys}
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ class RoomCreationHandler(BaseHandler):
|
|||||||
creation_content = {
|
creation_content = {
|
||||||
"room_version": new_room_version.identifier,
|
"room_version": new_room_version.identifier,
|
||||||
"predecessor": {"room_id": old_room_id, "event_id": tombstone_event_id},
|
"predecessor": {"room_id": old_room_id, "event_id": tombstone_event_id},
|
||||||
}
|
} # type: JsonDict
|
||||||
|
|
||||||
# Check if old room was non-federatable
|
# Check if old room was non-federatable
|
||||||
|
|
||||||
|
@ -34,11 +34,17 @@ def MockEvent(**kwargs):
|
|||||||
|
|
||||||
|
|
||||||
class PruneEventTestCase(unittest.TestCase):
|
class PruneEventTestCase(unittest.TestCase):
|
||||||
""" Asserts that a new event constructed with `evdict` will look like
|
|
||||||
`matchdict` when it is redacted. """
|
|
||||||
|
|
||||||
def run_test(self, evdict, matchdict, **kwargs):
|
def run_test(self, evdict, matchdict, **kwargs):
|
||||||
self.assertEquals(
|
"""
|
||||||
|
Asserts that a new event constructed with `evdict` will look like
|
||||||
|
`matchdict` when it is redacted.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
evdict: The dictionary to build the event from.
|
||||||
|
matchdict: The expected resulting dictionary.
|
||||||
|
kwargs: Additional keyword arguments used to create the event.
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
prune_event(make_event_from_dict(evdict, **kwargs)).get_dict(), matchdict
|
prune_event(make_event_from_dict(evdict, **kwargs)).get_dict(), matchdict
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,54 +61,80 @@ class PruneEventTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_basic_keys(self):
|
def test_basic_keys(self):
|
||||||
|
"""Ensure that the keys that should be untouched are kept."""
|
||||||
|
# Note that some of the values below don't really make sense, but the
|
||||||
|
# pruning of events doesn't worry about the values of any fields (with
|
||||||
|
# the exception of the content field).
|
||||||
self.run_test(
|
self.run_test(
|
||||||
{
|
{
|
||||||
|
"event_id": "$3:domain",
|
||||||
"type": "A",
|
"type": "A",
|
||||||
"room_id": "!1:domain",
|
"room_id": "!1:domain",
|
||||||
"sender": "@2:domain",
|
"sender": "@2:domain",
|
||||||
"event_id": "$3:domain",
|
"state_key": "B",
|
||||||
|
"content": {"other_key": "foo"},
|
||||||
|
"hashes": "hashes",
|
||||||
|
"signatures": {"domain": {"algo:1": "sigs"}},
|
||||||
|
"depth": 4,
|
||||||
|
"prev_events": "prev_events",
|
||||||
|
"prev_state": "prev_state",
|
||||||
|
"auth_events": "auth_events",
|
||||||
"origin": "domain",
|
"origin": "domain",
|
||||||
|
"origin_server_ts": 1234,
|
||||||
|
"membership": "join",
|
||||||
|
# Also include a key that should be removed.
|
||||||
|
"other_key": "foo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"event_id": "$3:domain",
|
||||||
"type": "A",
|
"type": "A",
|
||||||
"room_id": "!1:domain",
|
"room_id": "!1:domain",
|
||||||
"sender": "@2:domain",
|
"sender": "@2:domain",
|
||||||
"event_id": "$3:domain",
|
"state_key": "B",
|
||||||
|
"hashes": "hashes",
|
||||||
|
"depth": 4,
|
||||||
|
"prev_events": "prev_events",
|
||||||
|
"prev_state": "prev_state",
|
||||||
|
"auth_events": "auth_events",
|
||||||
"origin": "domain",
|
"origin": "domain",
|
||||||
|
"origin_server_ts": 1234,
|
||||||
|
"membership": "join",
|
||||||
"content": {},
|
"content": {},
|
||||||
"signatures": {},
|
"signatures": {"domain": {"algo:1": "sigs"}},
|
||||||
"unsigned": {},
|
"unsigned": {},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_unsigned_age_ts(self):
|
# As of MSC2176 we now redact the membership and prev_states keys.
|
||||||
self.run_test(
|
self.run_test(
|
||||||
{"type": "B", "event_id": "$test:domain", "unsigned": {"age_ts": 20}},
|
{"type": "A", "prev_state": "prev_state", "membership": "join"},
|
||||||
{
|
{"type": "A", "content": {}, "signatures": {}, "unsigned": {}},
|
||||||
"type": "B",
|
room_version=RoomVersions.MSC2176,
|
||||||
"event_id": "$test:domain",
|
|
||||||
"content": {},
|
|
||||||
"signatures": {},
|
|
||||||
"unsigned": {"age_ts": 20},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_unsigned(self):
|
||||||
|
"""Ensure that unsigned properties get stripped (except age_ts and replaces_state)."""
|
||||||
self.run_test(
|
self.run_test(
|
||||||
{
|
{
|
||||||
"type": "B",
|
"type": "B",
|
||||||
"event_id": "$test:domain",
|
"event_id": "$test:domain",
|
||||||
"unsigned": {"other_key": "here"},
|
"unsigned": {
|
||||||
|
"age_ts": 20,
|
||||||
|
"replaces_state": "$test2:domain",
|
||||||
|
"other_key": "foo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "B",
|
"type": "B",
|
||||||
"event_id": "$test:domain",
|
"event_id": "$test:domain",
|
||||||
"content": {},
|
"content": {},
|
||||||
"signatures": {},
|
"signatures": {},
|
||||||
"unsigned": {},
|
"unsigned": {"age_ts": 20, "replaces_state": "$test2:domain"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_content(self):
|
def test_content(self):
|
||||||
|
"""The content dictionary should be stripped in most cases."""
|
||||||
self.run_test(
|
self.run_test(
|
||||||
{"type": "C", "event_id": "$test:domain", "content": {"things": "here"}},
|
{"type": "C", "event_id": "$test:domain", "content": {"things": "here"}},
|
||||||
{
|
{
|
||||||
@ -114,11 +146,35 @@ class PruneEventTestCase(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Some events keep a single content key/value.
|
||||||
|
EVENT_KEEP_CONTENT_KEYS = [
|
||||||
|
("member", "membership", "join"),
|
||||||
|
("join_rules", "join_rule", "invite"),
|
||||||
|
("history_visibility", "history_visibility", "shared"),
|
||||||
|
]
|
||||||
|
for event_type, key, value in EVENT_KEEP_CONTENT_KEYS:
|
||||||
|
self.run_test(
|
||||||
|
{
|
||||||
|
"type": "m.room." + event_type,
|
||||||
|
"event_id": "$test:domain",
|
||||||
|
"content": {key: value, "other_key": "foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.room." + event_type,
|
||||||
|
"event_id": "$test:domain",
|
||||||
|
"content": {key: value},
|
||||||
|
"signatures": {},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
"""Create events are partially redacted until MSC2176."""
|
||||||
self.run_test(
|
self.run_test(
|
||||||
{
|
{
|
||||||
"type": "m.room.create",
|
"type": "m.room.create",
|
||||||
"event_id": "$test:domain",
|
"event_id": "$test:domain",
|
||||||
"content": {"creator": "@2:domain", "other_field": "here"},
|
"content": {"creator": "@2:domain", "other_key": "foo"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "m.room.create",
|
"type": "m.room.create",
|
||||||
@ -129,6 +185,68 @@ class PruneEventTestCase(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# After MSC2176, create events get nothing redacted.
|
||||||
|
self.run_test(
|
||||||
|
{"type": "m.room.create", "content": {"not_a_real_key": True}},
|
||||||
|
{
|
||||||
|
"type": "m.room.create",
|
||||||
|
"content": {"not_a_real_key": True},
|
||||||
|
"signatures": {},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
room_version=RoomVersions.MSC2176,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_power_levels(self):
|
||||||
|
"""Power level events keep a variety of content keys."""
|
||||||
|
self.run_test(
|
||||||
|
{
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"event_id": "$test:domain",
|
||||||
|
"content": {
|
||||||
|
"ban": 1,
|
||||||
|
"events": {"m.room.name": 100},
|
||||||
|
"events_default": 2,
|
||||||
|
"invite": 3,
|
||||||
|
"kick": 4,
|
||||||
|
"redact": 5,
|
||||||
|
"state_default": 6,
|
||||||
|
"users": {"@admin:domain": 100},
|
||||||
|
"users_default": 7,
|
||||||
|
"other_key": 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"event_id": "$test:domain",
|
||||||
|
"content": {
|
||||||
|
"ban": 1,
|
||||||
|
"events": {"m.room.name": 100},
|
||||||
|
"events_default": 2,
|
||||||
|
# Note that invite is not here.
|
||||||
|
"kick": 4,
|
||||||
|
"redact": 5,
|
||||||
|
"state_default": 6,
|
||||||
|
"users": {"@admin:domain": 100},
|
||||||
|
"users_default": 7,
|
||||||
|
},
|
||||||
|
"signatures": {},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# After MSC2176, power levels events keep the invite key.
|
||||||
|
self.run_test(
|
||||||
|
{"type": "m.room.power_levels", "content": {"invite": 75}},
|
||||||
|
{
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"content": {"invite": 75},
|
||||||
|
"signatures": {},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
room_version=RoomVersions.MSC2176,
|
||||||
|
)
|
||||||
|
|
||||||
def test_alias_event(self):
|
def test_alias_event(self):
|
||||||
"""Alias events have special behavior up through room version 6."""
|
"""Alias events have special behavior up through room version 6."""
|
||||||
self.run_test(
|
self.run_test(
|
||||||
@ -146,8 +264,7 @@ class PruneEventTestCase(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_msc2432_alias_event(self):
|
# After MSC2432, alias events have no special behavior.
|
||||||
"""After MSC2432, alias events have no special behavior."""
|
|
||||||
self.run_test(
|
self.run_test(
|
||||||
{"type": "m.room.aliases", "content": {"aliases": ["test"]}},
|
{"type": "m.room.aliases", "content": {"aliases": ["test"]}},
|
||||||
{
|
{
|
||||||
@ -159,6 +276,32 @@ class PruneEventTestCase(unittest.TestCase):
|
|||||||
room_version=RoomVersions.V6,
|
room_version=RoomVersions.V6,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_redacts(self):
|
||||||
|
"""Redaction events have no special behaviour until MSC2174/MSC2176."""
|
||||||
|
|
||||||
|
self.run_test(
|
||||||
|
{"type": "m.room.redaction", "content": {"redacts": "$test2:domain"}},
|
||||||
|
{
|
||||||
|
"type": "m.room.redaction",
|
||||||
|
"content": {},
|
||||||
|
"signatures": {},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
room_version=RoomVersions.V6,
|
||||||
|
)
|
||||||
|
|
||||||
|
# After MSC2174, redaction events keep the redacts content key.
|
||||||
|
self.run_test(
|
||||||
|
{"type": "m.room.redaction", "content": {"redacts": "$test2:domain"}},
|
||||||
|
{
|
||||||
|
"type": "m.room.redaction",
|
||||||
|
"content": {"redacts": "$test2:domain"},
|
||||||
|
"signatures": {},
|
||||||
|
"unsigned": {},
|
||||||
|
},
|
||||||
|
room_version=RoomVersions.MSC2176,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SerializeEventTestCase(unittest.TestCase):
|
class SerializeEventTestCase(unittest.TestCase):
|
||||||
def serialize(self, ev, fields):
|
def serialize(self, ev, fields):
|
||||||
|
Loading…
Reference in New Issue
Block a user