Skip to content

Commit

Permalink
Support for room version v11 (#418)
Browse files Browse the repository at this point in the history
Co-authored-by: Devon Hudson <[email protected]>
  • Loading branch information
S7evinK and devonh authored Sep 26, 2023
1 parent 0466775 commit 162387a
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 30 deletions.
1 change: 1 addition & 0 deletions eventV2.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ var lenientByteLimitRoomVersions = map[RoomVersion]struct{}{
RoomVersionV8: {},
RoomVersionV9: {},
RoomVersionV10: {},
RoomVersionV11: {},
RoomVersionPseudoIDs: {},
"org.matrix.msc3787": {},
"org.matrix.msc3667": {},
Expand Down
28 changes: 13 additions & 15 deletions eventauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 24 additions & 0 deletions eventcontent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
42 changes: 42 additions & 0 deletions eventversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -58,6 +61,7 @@ const (
RoomVersionV8 RoomVersion = "8"
RoomVersionV9 RoomVersion = "9"
RoomVersionV10 RoomVersion = "10"
RoomVersionV11 RoomVersion = "11"
RoomVersionPseudoIDs RoomVersion = "org.matrix.msc4014"
)

Expand Down Expand Up @@ -96,6 +100,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: disallowKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV1,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV1,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV1,
Expand All @@ -115,6 +120,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: disallowKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV1,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV1,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV1,
Expand All @@ -134,6 +140,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: disallowKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -153,6 +160,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: disallowKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -172,6 +180,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: disallowKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -191,6 +200,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: disallowKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -210,6 +220,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: checkKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -229,6 +240,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: checkKnocking,
checkRestrictedJoinAllowedFunc: allowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -248,6 +260,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: checkKnocking,
checkRestrictedJoinAllowedFunc: allowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -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,
Expand All @@ -286,6 +320,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parseIntegerPowerLevels,
checkKnockingAllowedFunc: checkKnocking,
checkRestrictedJoinAllowedFunc: allowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -305,6 +340,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
parsePowerLevelsFunc: parseIntegerPowerLevels,
checkKnockingAllowedFunc: checkKnocking,
checkRestrictedJoinAllowedFunc: disallowRestrictedJoins,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand All @@ -323,6 +359,7 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
checkRestrictedJoin: checkRestrictedJoin,
parsePowerLevelsFunc: parsePowerLevels,
checkKnockingAllowedFunc: checkKnocking,
checkCreateEvent: checkCreateEvent,
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV2,
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV2,
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV2,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
97 changes: 82 additions & 15 deletions redactevent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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{
Expand Down Expand Up @@ -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)
}
Loading

0 comments on commit 162387a

Please sign in to comment.