CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Implements PLAN.md Phase 2: the library every later phase is built on, as pure code under server/internal/matrix with no HTTP and no storage. canonicaljson: the canonical JSON encoding, hand-written because encoding/json escapes <, > and & and accepts floats. Integers only, in [-(2^53)+1, 2^53-1]; lone surrogate escapes rejected rather than turned into U+FFFD. signing: the Signing JSON algorithm alongside the existing key handling, and one lenient base64 decoder shared by the packages above it. id: identifier grammars as they appear inside events, deliberately laxer than userapi's account rules because rooms carry historical user IDs. roomversion: the registry. Versions 3 to 12 are supported, each written out in full as a Rules value with the flags the other packages read. Versions 1 and 2 are known but refused: their event format and state resolution v1 wait for federation. createRoom will default to 11, as the spec asks for a while after 1.16, with 12 available. event: PDU parsing and validation, content and reference hashes, event IDs, the redaction algorithm for every supported version, signing and verification, and a Builder. auth: the authorisation rules in spec order, power levels with their defaults, v12 creators with infinite power, the auth events selection algorithm, rule 3's auth_events checks, and the separate test for whether a redaction applies. Comments use the room version 12 rule numbering, with the version 11 numbers given where they differ. stateres: state resolution v2, and version 12's amendment (an empty starting state and the conflicted state subgraph). Tested against the spec's own vectors byte for byte: the canonical JSON examples, both JSON signing vectors and both event signing vectors. State resolution ports scenarios from Synapse's suite. canonicaljson and event.Parse have fuzz targets. The capabilities endpoint now advertises the registry instead of a hard-coded "11". docs/event-core.md describes the packages and the known gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.2 KiB
Go
96 lines
3.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"github.com/dariuszjaniak/warp/server/internal/matrix/event"
|
|
)
|
|
|
|
// StateKey identifies one piece of room state: an event type and a state
|
|
// key together. Room state is a map from this to an event.
|
|
type StateKey struct {
|
|
Type string
|
|
StateKey string
|
|
}
|
|
|
|
// State is the room state an event is authorised against.
|
|
//
|
|
// It is only ever the handful of events the rules actually read — the
|
|
// create event, the power levels, the join rules, the members involved and
|
|
// any third-party invite — not the whole state of the room. Two callers
|
|
// build it differently: event intake builds it from the event's own
|
|
// auth_events, and state resolution builds it from the state it has
|
|
// resolved so far.
|
|
type State map[StateKey]*event.Event
|
|
|
|
// NewState indexes a set of state events.
|
|
//
|
|
// A non-state event in the input is ignored rather than refused: the usual
|
|
// source is an event's auth_events, and a server that cited something odd
|
|
// there should fail the auth_events checks, not crash the indexer.
|
|
func NewState(events []*event.Event) State {
|
|
st := make(State, len(events))
|
|
for _, e := range events {
|
|
if sk, ok := e.StateKey(); ok {
|
|
st[StateKey{Type: e.Type(), StateKey: sk}] = e
|
|
}
|
|
}
|
|
return st
|
|
}
|
|
|
|
// Get returns one state event, or nil.
|
|
func (s State) Get(eventType, stateKey string) *event.Event {
|
|
return s[StateKey{Type: eventType, StateKey: stateKey}]
|
|
}
|
|
|
|
// Create returns the m.room.create event, or nil if the state does not
|
|
// contain one — which for any event but the create event itself means the
|
|
// event cannot be authorised at all.
|
|
func (s State) Create() *event.Event { return s.Get(event.TypeCreate, "") }
|
|
|
|
// PowerLevels returns the m.room.power_levels event, or nil. A room
|
|
// without one is not broken: until the creator sends one, the defaults
|
|
// apply and the creator alone has power.
|
|
func (s State) PowerLevels() *event.Event { return s.Get(event.TypePowerLevels, "") }
|
|
|
|
// JoinRules returns the m.room.join_rules event, or nil.
|
|
func (s State) JoinRules() *event.Event { return s.Get(event.TypeJoinRules, "") }
|
|
|
|
// Member returns a user's m.room.member event, or nil.
|
|
func (s State) Member(userID string) *event.Event { return s.Get(event.TypeMember, userID) }
|
|
|
|
// ThirdPartyInvite returns the m.room.third_party_invite event for a token,
|
|
// or nil.
|
|
func (s State) ThirdPartyInvite(token string) *event.Event {
|
|
return s.Get(event.TypeThirdPartyInvite, token)
|
|
}
|
|
|
|
// Membership returns a user's membership in the room.
|
|
//
|
|
// A user with no m.room.member event has never been in the room, which the
|
|
// rules treat exactly like having left: every rule that cares asks whether
|
|
// the membership *is* something, never whether it is absent.
|
|
func (s State) Membership(userID string) string {
|
|
e := s.Member(userID)
|
|
if e == nil {
|
|
return event.MembershipLeave
|
|
}
|
|
membership, err := e.Membership()
|
|
if err != nil || membership == "" {
|
|
return event.MembershipLeave
|
|
}
|
|
return membership
|
|
}
|
|
|
|
// JoinRule returns the room's join rule, defaulting to "invite" as the
|
|
// spec does for a room with no m.room.join_rules event.
|
|
func (s State) JoinRule() string {
|
|
e := s.JoinRules()
|
|
if e == nil {
|
|
return JoinRuleInvite
|
|
}
|
|
rule, err := joinRuleOf(e)
|
|
if err != nil || rule == "" {
|
|
return JoinRuleInvite
|
|
}
|
|
return rule
|
|
}
|