diff --git a/eventauth.go b/eventauth.go index 1900d855..f37228f7 100644 --- a/eventauth.go +++ b/eventauth.go @@ -38,6 +38,9 @@ const ( Knock = "knock" // Restricted is the string constant "restricted" Restricted = "restricted" + // NOTSPEC: Restricted is the string constant "knock_restricted" (MSC3787) + // REVIEW: the MSC is merged though... so is this specced? Idk. + KnockRestricted = "knock_restricted" // NOTSPEC: Peek is the string constant "peek" (MSC2753, used as the label in the sync block) Peek = "peek" // Public is the string constant "public" @@ -983,7 +986,7 @@ func (m *membershipAllower) membershipAllowed(event *Event) error { // nolint: g func (m *membershipAllower) membershipAllowedSelfForRestrictedJoin() error { // Special case for restricted room joins, where we will check if the membership // event is signed by one of the allowed servers in the join rule content. - allowsRestricted, err := m.roomVersion.AllowRestrictedJoinsInEventAuth() + allowsRestricted, err := m.roomVersion.AllowRestrictedJoinsInEventAuth(m.joinRule.JoinRule) if err != nil { return err } @@ -1081,7 +1084,7 @@ func (m *membershipAllower) membershipAllowedSelf() error { // nolint: gocyclo switch m.newMember.Membership { case Knock: - if m.joinRule.JoinRule != Knock { + if m.joinRule.JoinRule != Knock && m.joinRule.JoinRule != KnockRestricted { return m.membershipFailed( "join rule %q does not allow knocking", m.joinRule.JoinRule, ) @@ -1090,11 +1093,16 @@ func (m *membershipAllower) membershipAllowedSelf() error { // nolint: gocyclo // rules are "knock" and they are not already joined to, invited to // or banned from the room. // Spec: https://spec.matrix.org/unstable/rooms/v7/ - if supported, err := m.roomVersion.AllowKnockingInEventAuth(); err != nil { + // MSC3787 extends this: the behaviour above is also permitted if the + // join rules are "knock_restricted" + // Spec: https://github.com/matrix-org/matrix-spec-proposals/pull/3787 + if supported, err := m.roomVersion.AllowKnockingInEventAuth(m.joinRule.JoinRule); err != nil { return fmt.Errorf("m.roomVersion.AllowKnockingInEventAuth: %w", err) } else if !supported { return m.membershipFailed( - "room version %q does not support knocking", m.roomVersion, + "room version %q does not support knocking on rooms with join rule %q", + m.roomVersion, + m.joinRule.JoinRule, ) } switch m.oldMember.Membership { @@ -1216,16 +1224,18 @@ func (m *membershipAllower) membershipAllowedOther() error { // nolint: gocyclo } // A user can invite in response to a knock. if m.oldMember.Membership == Knock && senderLevel >= m.powerLevels.Invite { - if m.joinRule.JoinRule != Knock { + if m.joinRule.JoinRule != Knock && m.joinRule.JoinRule != KnockRestricted { return m.membershipFailed( "join rule %q does not allow knocking", m.joinRule.JoinRule, ) } - if supported, err := m.roomVersion.AllowKnockingInEventAuth(); err != nil { + if supported, err := m.roomVersion.AllowKnockingInEventAuth(m.joinRule.JoinRule); err != nil { return fmt.Errorf("m.roomVersion.AllowKnockingInEventAuth: %w", err) } else if !supported { return m.membershipFailed( - "room version %q does not allow knocking", m.roomVersion, + "room version %q does not support knocking on rooms with join rule %q", + m.roomVersion, + m.joinRule.JoinRule, ) } return nil diff --git a/eventcrypto.go b/eventcrypto.go index b3ebb28d..5873f731 100644 --- a/eventcrypto.go +++ b/eventcrypto.go @@ -83,7 +83,7 @@ func (e *Event) VerifyEventSignatures(ctx context.Context, verifier JSONVerifier } // For restricted join rules, the authorising server should have signed. - if restricted, err := e.roomVersion.AllowRestrictedJoinsInEventAuth(); err != nil { + if restricted, err := e.roomVersion.MayAllowRestrictedJoinsInEventAuth(); err != nil { return fmt.Errorf("failed to check if restricted joins allowed: %w", err) } else if restricted && membership == Join { if v := gjson.GetBytes(e.Content(), "join_authorised_via_users_server"); v.Exists() { diff --git a/eventversion.go b/eventversion.go index ea7b85cd..81398642 100644 --- a/eventversion.go +++ b/eventversion.go @@ -17,6 +17,14 @@ type EventIDFormat int // RedactionAlgorithm refers to the redaction algorithm used in a room version. type RedactionAlgorithm int +// JoinRulesPermittingKnockInEventAuth specifies which kinds of join_rule allow +// a room to be knocked upon. +type JoinRulesPermittingKnockInEventAuth int + +// JoinRulesPermittingRestrictedJoinInEventAuth specifies which kinds of join_rule allow +// a room to be joined via a space. +type JoinRulesPermittingRestrictedJoinInEventAuth int + // Room version constants. These are strings because the version grammar // allows for future expansion. // https://matrix.org/docs/spec/#room-version-grammar @@ -59,6 +67,20 @@ const ( RedactionAlgorithmV4 // protects membership 'join_authorised_via_users_server' key ) +// Which join_rules permit knocking? +const ( + KnocksForbidden JoinRulesPermittingKnockInEventAuth = iota + 1 // no rooms can be knocked upon + KnockOnly // rooms with join_rule "knock" can be knocked upon + KnockOrKnockRestricted // rooms with join_rule "knock" or "knock_restricted" can be knocked upon +) + +// Which join_rules permit restricted joins? +const ( + NoRestrictedJoins JoinRulesPermittingRestrictedJoinInEventAuth = iota + 1 // no rooms can be joined via a space + RestrictedOnly // rooms with join_rule "restricted" can be joined via a space + RestrictedOrKnockRestricted // rooms with join_rule "restricted" or "knock_restricted" can be joined via a space +) + var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ RoomVersionV1: { Supported: true, @@ -70,8 +92,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: false, enforceCanonicalJSON: false, powerLevelsIncludeNotifications: false, - allowKnockingInEventAuth: false, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV2: { @@ -84,8 +106,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: false, enforceCanonicalJSON: false, powerLevelsIncludeNotifications: false, - allowKnockingInEventAuth: false, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV3: { @@ -98,8 +120,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: false, enforceCanonicalJSON: false, powerLevelsIncludeNotifications: false, - allowKnockingInEventAuth: false, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV4: { @@ -112,8 +134,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: false, enforceCanonicalJSON: false, powerLevelsIncludeNotifications: false, - allowKnockingInEventAuth: false, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV5: { @@ -126,8 +148,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: true, enforceCanonicalJSON: false, powerLevelsIncludeNotifications: false, - allowKnockingInEventAuth: false, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV6: { @@ -140,8 +162,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: true, enforceCanonicalJSON: true, powerLevelsIncludeNotifications: true, - allowKnockingInEventAuth: false, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV7: { @@ -154,8 +176,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: true, enforceCanonicalJSON: true, powerLevelsIncludeNotifications: true, - allowKnockingInEventAuth: true, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnockOnly, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: false, }, RoomVersionV8: { @@ -168,8 +190,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: true, enforceCanonicalJSON: true, powerLevelsIncludeNotifications: true, - allowKnockingInEventAuth: true, - allowRestrictedJoinsInEventAuth: true, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: RestrictedOnly, requireIntegerPowerLevels: false, }, RoomVersionV9: { @@ -182,8 +204,8 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: true, enforceCanonicalJSON: true, powerLevelsIncludeNotifications: true, - allowKnockingInEventAuth: true, - allowRestrictedJoinsInEventAuth: true, + allowKnockingInEventAuth: KnocksForbidden, + allowRestrictedJoinsInEventAuth: RestrictedOnly, requireIntegerPowerLevels: false, }, "org.matrix.msc3667": { // based on room version 7 @@ -196,10 +218,24 @@ var roomVersionMeta = map[RoomVersion]RoomVersionDescription{ enforceSignatureChecks: true, enforceCanonicalJSON: true, powerLevelsIncludeNotifications: true, - allowKnockingInEventAuth: true, - allowRestrictedJoinsInEventAuth: false, + allowKnockingInEventAuth: KnockOnly, + allowRestrictedJoinsInEventAuth: NoRestrictedJoins, requireIntegerPowerLevels: true, }, + "org.matrix.msc3787": { // roughly, the union of v7 and v9 + Supported: true, + Stable: false, + stateResAlgorithm: StateResV2, + eventFormat: EventFormatV2, + eventIDFormat: EventIDFormatV3, + redactionAlgorithm: RedactionAlgorithmV4, + enforceSignatureChecks: true, + enforceCanonicalJSON: true, + powerLevelsIncludeNotifications: true, + allowKnockingInEventAuth: KnockOrKnockRestricted, + allowRestrictedJoinsInEventAuth: RestrictedOrKnockRestricted, + requireIntegerPowerLevels: false, + }, } // RoomVersions returns information about room versions currently @@ -249,11 +285,11 @@ type RoomVersionDescription struct { eventFormat EventFormat eventIDFormat EventIDFormat redactionAlgorithm RedactionAlgorithm + allowKnockingInEventAuth JoinRulesPermittingKnockInEventAuth + allowRestrictedJoinsInEventAuth JoinRulesPermittingRestrictedJoinInEventAuth enforceSignatureChecks bool enforceCanonicalJSON bool powerLevelsIncludeNotifications bool - allowKnockingInEventAuth bool - allowRestrictedJoinsInEventAuth bool requireIntegerPowerLevels bool Supported bool Stable bool @@ -309,20 +345,50 @@ func (v RoomVersion) PowerLevelsIncludeNotifications() (bool, error) { return false, UnsupportedRoomVersionError{v} } -// AllowKnockingInEventAuth returns true if the given room version allows for -// the `knock` membership state or false otherwise. -func (v RoomVersion) AllowKnockingInEventAuth() (bool, error) { +// AllowKnockingInEventAuth returns true if the given room version and given +// join rule allows for the `knock` membership state or false otherwise. +func (v RoomVersion) AllowKnockingInEventAuth(joinRule string) (bool, error) { if r, ok := roomVersionMeta[v]; ok { - return r.allowKnockingInEventAuth, nil + switch r.allowKnockingInEventAuth { + case KnockOnly: + return joinRule == Knock, nil + case KnockOrKnockRestricted: + return (joinRule == Knock || joinRule == KnockRestricted), nil + case KnocksForbidden: + return false, nil + } } return false, UnsupportedRoomVersionError{v} } -// AllowRestrictedJoinsInEventAuth returns true if the given room version allows -// for memberships signed by servers in the restricted join rules. -func (v RoomVersion) AllowRestrictedJoinsInEventAuth() (bool, error) { +// AllowRestrictedJoinsInEventAuth returns true if the given room version and +// join rule allows for memberships signed by servers in the restricted join rules. +func (v RoomVersion) AllowRestrictedJoinsInEventAuth(joinRule string) (bool, error) { if r, ok := roomVersionMeta[v]; ok { - return r.allowRestrictedJoinsInEventAuth, nil + switch r.allowRestrictedJoinsInEventAuth { + case NoRestrictedJoins: + return false, nil + case RestrictedOnly: + return joinRule == Restricted, nil + case RestrictedOrKnockRestricted: + return (joinRule == Restricted || joinRule == KnockRestricted), nil + } + } + return false, UnsupportedRoomVersionError{v} +} + +// MayAllowRestrictedJoinsInEventAuth returns true if the given room version +// might allow for memberships signed by servers in the restricted join rules. +// (For an authoritative answer, the room's join rules must be known. If they +// are, use AllowRestrictedJoinsInEventAuth.) +func (v RoomVersion) MayAllowRestrictedJoinsInEventAuth() (bool, error) { + if r, ok := roomVersionMeta[v]; ok { + switch r.allowRestrictedJoinsInEventAuth { + case NoRestrictedJoins: + return false, nil + case RestrictedOnly, RestrictedOrKnockRestricted: + return true, nil + } } return false, UnsupportedRoomVersionError{v} }