From 162387ab58e98ff4afdc66413498d0f928c08281 Mon Sep 17 00:00:00 2001 From: Till <2353100+S7evinK@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:52:22 +0200 Subject: [PATCH] Support for room version v11 (#418) Co-authored-by: Devon Hudson --- eventV2.go | 1 + eventauth.go | 28 ++++++------- eventcontent.go | 24 +++++++++++ eventversion.go | 42 ++++++++++++++++++++ redactevent.go | 97 ++++++++++++++++++++++++++++++++++++++------- redactevent_test.go | 67 +++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 30 deletions(-) diff --git a/eventV2.go b/eventV2.go index 4be5e931..b0099e9b 100644 --- a/eventV2.go +++ b/eventV2.go @@ -196,6 +196,7 @@ var lenientByteLimitRoomVersions = map[RoomVersion]struct{}{ RoomVersionV8: {}, RoomVersionV9: {}, RoomVersionV10: {}, + RoomVersionV11: {}, RoomVersionPseudoIDs: {}, "org.matrix.msc3787": {}, "org.matrix.msc3667": {}, diff --git a/eventauth.go b/eventauth.go index 9318b4c6..ee1fd24e 100644 --- a/eventauth.go +++ b/eventauth.go @@ -370,7 +370,11 @@ func (a *allowerContext) update(provider AuthEventProvider) { } } if e, _ := provider.PowerLevels(); a.powerLevelsEvent == nil || a.powerLevelsEvent != e { - if p, err := NewPowerLevelContentFromAuthEvents(provider, a.create.Creator); err == nil { + creator := "" + if a.createEvent != nil { + creator = string(a.createEvent.SenderID()) + } + if p, err := NewPowerLevelContentFromAuthEvents(provider, creator); err == nil { a.powerLevelsEvent = e a.powerLevels = p } @@ -431,21 +435,15 @@ func (a *allowerContext) createEventAllowed(event PDU) error { if sender.Domain() != event.RoomID().Domain() { return errorf("create event room ID domain does not match sender: %q != %q", event.RoomID().Domain(), sender.String()) } - c := struct { - Creator *string `json:"creator"` - RoomVersion *RoomVersion `json:"room_version"` - }{} - if err := json.Unmarshal(event.Content(), &c); err != nil { - return errorf("create event has invalid content: %s", err.Error()) - } - if c.Creator == nil { - return errorf("create event has no creator field") + + verImpl, err := GetRoomVersion(event.Version()) + if err != nil { + return nil } - if c.RoomVersion != nil { - if !KnownRoomVersion(*c.RoomVersion) { - return errorf("create event has unrecognised room version %q", *c.RoomVersion) - } + if err = verImpl.CheckCreateEvent(event, KnownRoomVersion); err != nil { + return err } + return nil } @@ -1013,7 +1011,7 @@ func (m *membershipAllower) membershipAllowed(event PDU) error { // nolint: gocy // Special case the first join event in the room to allow the creator to join. // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L328 - if m.targetID == m.create.Creator && + if m.targetID == string(m.createEvent.SenderID()) && m.newMember.Membership == spec.Join && m.senderID == m.targetID && len(event.PrevEventIDs()) == 1 { diff --git a/eventcontent.go b/eventcontent.go index 83eb787a..39dc5687 100644 --- a/eventcontent.go +++ b/eventcontent.go @@ -575,3 +575,27 @@ type RelatesTo struct { EventID string `json:"event_id"` RelationType string `json:"rel_type"` } + +func noCheckCreateEvent(event PDU, knownRoomVersion knownRoomVersionFunc) error { + return nil +} + +func checkCreateEvent(event PDU, knownRoomVersion knownRoomVersionFunc) error { + c := struct { + Creator *string `json:"creator"` + RoomVersion *RoomVersion `json:"room_version"` + }{} + if err := json.Unmarshal(event.Content(), &c); err != nil { + return errorf("create event has invalid content: %s", err.Error()) + } + if c.Creator == nil { + return errorf("create event has no creator field") + } + if c.RoomVersion != nil { + if !knownRoomVersion(*c.RoomVersion) { + return errorf("create event has unrecognised room version %q", *c.RoomVersion) + } + } + + return nil +} diff --git a/eventversion.go b/eventversion.go index f42e28a2..e6b906f7 100644 --- a/eventversion.go +++ b/eventversion.go @@ -33,8 +33,11 @@ type IRoomVersion interface { CheckNotificationLevels(senderLevel int64, oldPowerLevels, newPowerLevels PowerLevelContent) error CheckCanonicalJSON(input []byte) error ParsePowerLevels(contentBytes []byte, c *PowerLevelContent) error + CheckCreateEvent(event PDU, knownRoomVersion knownRoomVersionFunc) error } +type knownRoomVersionFunc func(RoomVersion) bool + // StateResAlgorithm refers to a version of the state resolution algorithm. type StateResAlgorithm int @@ -58,6 +61,7 @@ const ( RoomVersionV8 RoomVersion = "8" RoomVersionV9 RoomVersion = "9" RoomVersionV10 RoomVersion = "10" + RoomVersionV11 RoomVersion = "11" RoomVersionPseudoIDs RoomVersion = "org.matrix.msc4014" ) @@ -96,6 +100,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: disallowKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV1, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV1, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV1, @@ -115,6 +120,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: disallowKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV1, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV1, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV1, @@ -134,6 +140,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: disallowKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -153,6 +160,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: disallowKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -172,6 +180,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: disallowKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -191,6 +200,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: disallowKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -210,6 +220,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: checkKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -229,6 +240,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: checkKnocking, checkRestrictedJoinAllowedFunc: allowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -248,6 +260,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: checkKnocking, checkRestrictedJoinAllowedFunc: allowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -267,6 +280,27 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parseIntegerPowerLevels, checkKnockingAllowedFunc: checkKnocking, checkRestrictedJoinAllowedFunc: allowRestrictedJoins, + checkCreateEvent: checkCreateEvent, + newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, + newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, + newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, + }, + RoomVersionV11: RoomVersionImpl{ + ver: RoomVersionV11, + stable: true, + stateResAlgorithm: StateResV2, + eventFormat: EventFormatV2, + eventIDFormat: EventIDFormatV3, + redactionAlgorithm: redactEventJSONV5, + signatureValidityCheckFunc: StrictValiditySignatureCheck, + canonicalJSONCheck: verifyEnforcedCanonicalJSON, + notificationLevelCheck: checkNotificationLevels, + restrictedJoinServernameFunc: extractAuthorisedViaServerName, + checkRestrictedJoin: checkRestrictedJoin, + parsePowerLevelsFunc: parseIntegerPowerLevels, + checkKnockingAllowedFunc: checkKnocking, + checkRestrictedJoinAllowedFunc: allowRestrictedJoins, + checkCreateEvent: noCheckCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -286,6 +320,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parseIntegerPowerLevels, checkKnockingAllowedFunc: checkKnocking, checkRestrictedJoinAllowedFunc: allowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -305,6 +340,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ parsePowerLevelsFunc: parseIntegerPowerLevels, checkKnockingAllowedFunc: checkKnocking, checkRestrictedJoinAllowedFunc: disallowRestrictedJoins, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -323,6 +359,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{ checkRestrictedJoin: checkRestrictedJoin, parsePowerLevelsFunc: parsePowerLevels, checkKnockingAllowedFunc: checkKnocking, + checkCreateEvent: checkCreateEvent, newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2, newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2, newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2, @@ -404,6 +441,7 @@ type RoomVersionImpl struct { restrictedJoinServernameFunc func(content []byte) (spec.ServerName, error) checkRestrictedJoinAllowedFunc func() error checkKnockingAllowedFunc func(m *membershipAllower) error + checkCreateEvent func(e PDU, knownRoomVersion knownRoomVersionFunc) error newEventFromUntrustedJSONFunc func(eventJSON []byte, roomVersion IRoomVersion) (result PDU, err error) newEventFromTrustedJSONFunc func(eventJSON []byte, redacted bool, roomVersion IRoomVersion) (result PDU, err error) newEventFromTrustedJSONWithEventIDFunc func(eventID string, eventJSON []byte, redacted bool, roomVersion IRoomVersion) (result PDU, err error) @@ -470,6 +508,10 @@ func (v RoomVersionImpl) ParsePowerLevels(contentBytes []byte, c *PowerLevelCont return v.parsePowerLevelsFunc(contentBytes, c) } +func (v RoomVersionImpl) CheckCreateEvent(event PDU, knownRoomVersion knownRoomVersionFunc) error { + return v.checkCreateEvent(event, knownRoomVersion) +} + func (v RoomVersionImpl) CheckRestrictedJoin( ctx context.Context, localServerName spec.ServerName, diff --git a/redactevent.go b/redactevent.go index 2f06b09c..c69ccb01 100644 --- a/redactevent.go +++ b/redactevent.go @@ -7,7 +7,7 @@ import ( ) // For satisfying "Upon receipt of a redaction event, the server must strip off any keys not in the following list:" -type unredactableEventFields struct { +type unredactableEventFieldsV1 struct { EventID spec.RawJSON `json:"event_id,omitempty"` Type string `json:"type"` RoomID spec.RawJSON `json:"room_id,omitempty"` @@ -25,6 +25,46 @@ type unredactableEventFields struct { Membership spec.RawJSON `json:"membership,omitempty"` } +func (u *unredactableEventFieldsV1) GetType() string { + return u.Type +} + +func (u *unredactableEventFieldsV1) GetContent() map[string]interface{} { + return u.Content +} + +func (u *unredactableEventFieldsV1) SetContent(content map[string]interface{}) { + u.Content = content +} + +// For satisfying "Upon receipt of a redaction event, the server must strip off any keys not in the following list:" +type unredactableEventFieldsV2 struct { + EventID spec.RawJSON `json:"event_id,omitempty"` + Type string `json:"type"` + RoomID spec.RawJSON `json:"room_id,omitempty"` + Sender spec.RawJSON `json:"sender,omitempty"` + StateKey spec.RawJSON `json:"state_key,omitempty"` + Content map[string]interface{} `json:"content"` + Hashes spec.RawJSON `json:"hashes,omitempty"` + Signatures spec.RawJSON `json:"signatures,omitempty"` + Depth spec.RawJSON `json:"depth,omitempty"` + PrevEvents spec.RawJSON `json:"prev_events,omitempty"` + AuthEvents spec.RawJSON `json:"auth_events,omitempty"` + OriginServerTS spec.RawJSON `json:"origin_server_ts,omitempty"` +} + +func (u *unredactableEventFieldsV2) GetType() string { + return u.Type +} + +func (u *unredactableEventFieldsV2) GetContent() map[string]interface{} { + return u.Content +} + +func (u *unredactableEventFieldsV2) SetContent(content map[string]interface{}) { + u.Content = content +} + // For satisfying "The content object must also be stripped of all keys, unless it is one of one of the following event types:" var ( unredactableContentFieldsV1 = map[string][]string{ @@ -56,52 +96,79 @@ var ( "m.room.power_levels": {"ban", "events", "events_default", "kick", "redact", "state_default", "users", "users_default"}, "m.room.history_visibility": {"history_visibility"}, } + unredactableContentFieldsV5 = map[string][]string{ + "m.room.member": {"membership", "join_authorised_via_users_server"}, + "m.room.create": {}, // NOTE: Keep all fields + "m.room.join_rules": {"join_rule", "allow"}, + "m.room.power_levels": {"ban", "events", "events_default", "kick", "redact", "state_default", "users", "users_default", "invite"}, + "m.room.history_visibility": {"history_visibility"}, + "m.room.redaction": {"redacts"}, + } ) +// RedactEvent strips the user controlled fields from an event, but leaves the +// fields necessary for authenticating the event. Implements https://spec.matrix.org/unstable/rooms/v9/#redactions +// which protects membership 'join_authorised_via_users_server' key +func redactEventJSONV5(eventJSON []byte) ([]byte, error) { + return redactEventJSON(eventJSON, &unredactableEventFieldsV2{}, unredactableContentFieldsV5) +} + // RedactEvent strips the user controlled fields from an event, but leaves the // fields necessary for authenticating the event. Implements https://spec.matrix.org/unstable/rooms/v9/#redactions // which protects membership 'join_authorised_via_users_server' key func redactEventJSONV4(eventJSON []byte) ([]byte, error) { - return redactEventJSON(eventJSON, unredactableContentFieldsV4) + return redactEventJSON(eventJSON, &unredactableEventFieldsV1{}, unredactableContentFieldsV4) } // RedactEvent strips the user controlled fields from an event, but leaves the // fields necessary for authenticating the event. Implements https://spec.matrix.org/unstable/rooms/v8/#redactions // which protects join rules 'allow' key func redactEventJSONV3(eventJSON []byte) ([]byte, error) { - return redactEventJSON(eventJSON, unredactableContentFieldsV3) + return redactEventJSON(eventJSON, &unredactableEventFieldsV1{}, unredactableContentFieldsV3) } // RedactEvent strips the user controlled fields from an event, but leaves the // fields necessary for authenticating the event. Implements https://spec.matrix.org/unstable/rooms/v6/#redactions // which has no special meaning for m.room.aliases func redactEventJSONV2(eventJSON []byte) ([]byte, error) { - return redactEventJSON(eventJSON, unredactableContentFieldsV2) + return redactEventJSON(eventJSON, &unredactableEventFieldsV1{}, unredactableContentFieldsV2) } // RedactEvent strips the user controlled fields from an event, but leaves the // fields necessary for authenticating the event. Implements https://spec.matrix.org/unstable/rooms/v1/#redactions func redactEventJSONV1(eventJSON []byte) ([]byte, error) { - return redactEventJSON(eventJSON, unredactableContentFieldsV1) + return redactEventJSON(eventJSON, &unredactableEventFieldsV1{}, unredactableContentFieldsV1) +} + +type unredactableEvent interface { + *unredactableEventFieldsV1 | *unredactableEventFieldsV2 + GetType() string + GetContent() map[string]interface{} + SetContent(map[string]interface{}) } -func redactEventJSON(eventJSON []byte, eventTypeToKeepContentFields map[string][]string) ([]byte, error) { - var event unredactableEventFields +func redactEventJSON[T unredactableEvent](eventJSON []byte, unredactableEvent T, eventTypeToKeepContentFields map[string][]string) ([]byte, error) { // Unmarshalling into a struct will discard any extra fields from the event. - if err := json.Unmarshal(eventJSON, &event); err != nil { + if err := json.Unmarshal(eventJSON, &unredactableEvent); err != nil { return nil, err } newContent := map[string]interface{}{} - keepContentFields := eventTypeToKeepContentFields[event.Type] - for _, contentKey := range keepContentFields { - val, ok := event.Content[contentKey] - if ok { - newContent[contentKey] = val + keepContentFields, ok := eventTypeToKeepContentFields[unredactableEvent.GetType()] + if ok && len(keepContentFields) == 0 { + // An unredactable content entry with no provided fields should keep all fields. + newContent = unredactableEvent.GetContent() + } else { + for _, contentKey := range keepContentFields { + val, ok := unredactableEvent.GetContent()[contentKey] + if ok { + newContent[contentKey] = val + } } } + // Replace the content with our new filtered content. // This will zero out any keys that weren't copied in the loop above. - event.Content = newContent + unredactableEvent.SetContent(newContent) // Return the redacted event encoded as JSON. - return json.Marshal(&event) + return json.Marshal(&unredactableEvent) } diff --git a/redactevent_test.go b/redactevent_test.go index b05336fb..708c2a88 100644 --- a/redactevent_test.go +++ b/redactevent_test.go @@ -43,3 +43,70 @@ func TestRedactionAlgorithmV4(t *testing.T) { t.Fatalf("room version 8 redaction produced unexpected result\nexpected: %s\ngot: %s", string(expectedv8), string(redactedv8withv9)) } } + +func TestRedactionAlgorithmV5(t *testing.T) { + // Specifically, the version 5 redaction algorithm used in room + // version 11 is ensuring that: + // - `m.room.create` keeps all `content` fields + // - `m.room.redaction` keeps `redacts` `content` field + // - `m.room.power_levels` keeps `invite` `content` field + // - top level `origin`, `membership`, and `prev_state` aren't protected from redaction + + input := []byte(`{"content":{"placeholder":"value"},"origin_server_ts":1633108629915,"sender":"@someone:somewhere.org","state_key":"@someone:somewhere.org","type":"m.room.create","unsigned":{"age":539338},"room_id":"!someroom:matrix.org","origin":"matrix.org","membership":"join","prev_state":""}`) + expectedv10 := CanonicalJSONAssumeValid([]byte(`{"sender":"@someone:somewhere.org","room_id":"!someroom:matrix.org","content":{},"type":"m.room.create","state_key":"@someone:somewhere.org","prev_state":"","origin":"matrix.org","origin_server_ts":1633108629915,"membership":"join"}`)) + expectedv11 := CanonicalJSONAssumeValid([]byte(`{"sender":"@someone:somewhere.org","room_id":"!someroom:matrix.org","content":{"placeholder":"value"},"type":"m.room.create","state_key":"@someone:somewhere.org","origin_server_ts":1633108629915}`)) + expectedv10withv11 := CanonicalJSONAssumeValid([]byte(`{"sender":"@someone:somewhere.org","room_id":"!someroom:matrix.org","content":{},"type":"m.room.create","state_key":"@someone:somewhere.org","origin_server_ts":1633108629915}`)) + + redactedv10, err := MustGetRoomVersion(RoomVersionV10).RedactEventJSON(input) + if err != nil { + t.Fatal(err) + } + + redactedv11, err := MustGetRoomVersion(RoomVersionV11).RedactEventJSON(input) + if err != nil { + t.Fatal(err) + } + redactedv10 = CanonicalJSONAssumeValid(redactedv10) + redactedv11 = CanonicalJSONAssumeValid(redactedv11) + + if !bytes.Equal(redactedv10, expectedv10) { + t.Fatalf("room version 10 redaction produced unexpected result\nexpected: %s\ngot: %s", string(expectedv10), string(redactedv10)) + } + + if !bytes.Equal(redactedv11, expectedv11) { + t.Fatalf("room version 11 redaction produced unexpected result\nexpected: %s\ngot: %s", string(expectedv11), string(redactedv11)) + } + + redactedv10withv11, err := MustGetRoomVersion(RoomVersionV11).RedactEventJSON(expectedv10) + if err != nil { + t.Fatal(err) + } + redactedv10withv11 = CanonicalJSONAssumeValid(redactedv10withv11) + if !bytes.Equal(redactedv10withv11, expectedv10withv11) { + t.Fatalf("room version 11 redaction produced unexpected result\nexpected: %s\ngot: %s", string(expectedv10withv11), string(redactedv10withv11)) + } + + powerLevelsInput := []byte(`{"content":{"invite":"","placeholder":"value"},"origin_server_ts":1633108629915,"sender":"@someone:somewhere.org","state_key":"@someone:somewhere.org","type":"m.room.power_levels","unsigned":{"age":539338},"room_id":"!someroom:matrix.org","origin":"matrix.org","membership":"join","prev_state":""}`) + expectedv11PLs := CanonicalJSONAssumeValid([]byte(`{"sender":"@someone:somewhere.org","room_id":"!someroom:matrix.org","content":{"invite":""},"type":"m.room.power_levels","state_key":"@someone:somewhere.org","origin_server_ts":1633108629915}`)) + + redactedv11PLs, err := MustGetRoomVersion(RoomVersionV11).RedactEventJSON(powerLevelsInput) + if err != nil { + t.Fatal(err) + } + redactedv11PLs = CanonicalJSONAssumeValid(redactedv11PLs) + if !bytes.Equal(redactedv11PLs, expectedv11PLs) { + t.Fatalf("room version 11 redaction produced unexpected result\nexpected: %s\ngot: %s", string(expectedv11PLs), string(redactedv11PLs)) + } + + readactionInput := []byte(`{"content":{"redacts":"","placeholder":"value"},"origin_server_ts":1633108629915,"sender":"@someone:somewhere.org","state_key":"@someone:somewhere.org","type":"m.room.redaction","unsigned":{"age":539338},"room_id":"!someroom:matrix.org","origin":"matrix.org","membership":"join","prev_state":""}`) + expectedv11Redaction := CanonicalJSONAssumeValid([]byte(`{"sender":"@someone:somewhere.org","room_id":"!someroom:matrix.org","content":{"redacts":""},"type":"m.room.redaction","state_key":"@someone:somewhere.org","origin_server_ts":1633108629915}`)) + + redactedv11Redaction, err := MustGetRoomVersion(RoomVersionV11).RedactEventJSON(readactionInput) + if err != nil { + t.Fatal(err) + } + redactedv11Redaction = CanonicalJSONAssumeValid(redactedv11Redaction) + if !bytes.Equal(redactedv11Redaction, expectedv11Redaction) { + t.Fatalf("room version 11 redaction produced unexpected result\nexpected: %s\ngot: %s", string(expectedv11Redaction), string(redactedv11Redaction)) + } +}