package auth import ( "encoding/json" "fmt" "math" "strconv" "github.com/dariuszjaniak/warp/server/internal/matrix/event" "github.com/dariuszjaniak/warp/server/internal/matrix/id" "github.com/dariuszjaniak/warp/server/internal/matrix/roomversion" ) // InfinitePowerLevel is the level a room creator has from room version 12. // // The spec calls it "infinitely high". Since every level that can be // written down is capped at 2^53-1 by canonical JSON, the largest int64 // stands in for infinity without any comparison going wrong — and nothing // here does arithmetic on a level, only compares them. const InfinitePowerLevel int64 = math.MaxInt64 // The default levels, which apply key by key: a power levels event that // sets only "users" leaves every other level at the value below. // // state_default is 50 even in a room with no m.room.power_levels event at // all, which looks odd until you notice that the creator has 100 there, so // the only person who can send state is the one person who should be able // to. const ( DefaultBan int64 = 50 DefaultKick int64 = 50 DefaultRedact int64 = 50 DefaultInvite int64 = 0 DefaultEventsDefault int64 = 0 DefaultStateDefault int64 = 50 DefaultUsersDefault int64 = 0 DefaultNotification int64 = 50 // CreatorPowerLevel is what the room's creator has while the room has // no m.room.power_levels event, in room versions up to 11. CreatorPowerLevel int64 = 100 ) // PowerLevels is a room's resolved power level configuration: the event's // content with every default filled in, and the room's creators noted so // that their level can be answered. type PowerLevels struct { Ban int64 Kick int64 Redact int64 Invite int64 EventsDefault int64 StateDefault int64 UsersDefault int64 Events map[string]int64 Notifications map[string]int64 Users map[string]int64 // creators have InfinitePowerLevel from room version 12. Before that // the map is empty and the creator's 100 is written into Users // instead, because it is a real level there that a later power levels // event can change. creators map[string]bool } // ResolvePowerLevels works out the levels in force, given the room's // m.room.power_levels event (which may be nil) and its create event. // // The create event is needed even when there is a power levels event: from // room version 12 it is what says who the creators are, and their level // cannot be written down. func ResolvePowerLevels(plEvent, createEvent *event.Event, rules roomversion.Rules) (*PowerLevels, error) { pl := &PowerLevels{ Ban: DefaultBan, Kick: DefaultKick, Redact: DefaultRedact, Invite: DefaultInvite, EventsDefault: DefaultEventsDefault, StateDefault: DefaultStateDefault, UsersDefault: DefaultUsersDefault, Events: map[string]int64{}, Notifications: map[string]int64{}, Users: map[string]int64{}, creators: map[string]bool{}, } if createEvent != nil { creators, err := Creators(createEvent) if err != nil { return nil, err } if rules.CreatorsHaveInfinitePower { for _, c := range creators { pl.creators[c] = true } } else if plEvent == nil && len(creators) > 0 { // With no power levels event, the creator has 100 and everyone // else has nothing. pl.Users[creators[0]] = CreatorPowerLevel } } if plEvent == nil { return pl, nil } if err := pl.apply(plEvent.Content(), rules); err != nil { return nil, err } return pl, nil } // apply overlays the content of an m.room.power_levels event. func (p *PowerLevels) apply(content json.RawMessage, rules roomversion.Rules) error { var obj map[string]json.RawMessage if err := json.Unmarshal(content, &obj); err != nil { return reject("m.room.power_levels content is not an object: %v", err) } scalars := map[string]*int64{ "ban": &p.Ban, "kick": &p.Kick, "redact": &p.Redact, "invite": &p.Invite, "events_default": &p.EventsDefault, "state_default": &p.StateDefault, "users_default": &p.UsersDefault, } for name, target := range scalars { raw, ok := obj[name] if !ok { continue } level, err := parseLevel(raw, rules) if err != nil { return reject("m.room.power_levels %s: %v", name, err) } *target = level } for name, target := range map[string]*map[string]int64{ "events": &p.Events, "notifications": &p.Notifications, "users": &p.Users, } { raw, ok := obj[name] if !ok { continue } levels, err := parseLevelMap(raw, rules) if err != nil { return reject("m.room.power_levels %s: %v", name, err) } if name == "users" { for userID := range levels { if err := id.ValidateUserID(userID); err != nil { return reject("m.room.power_levels users: %v", err) } } } *target = levels } return nil } // UserLevel returns a user's power level. func (p *PowerLevels) UserLevel(userID string) int64 { if p.creators[userID] { return InfinitePowerLevel } if level, ok := p.Users[userID]; ok { return level } return p.UsersDefault } // IsCreator reports whether a user is one of the room's creators, in a room // version where that gives them infinite power. func (p *PowerLevels) IsCreator(userID string) bool { return p.creators[userID] } // SendLevel returns the level needed to send an event of a type. The events // map overrides everything; otherwise a state event needs state_default and // a message event events_default. func (p *PowerLevels) SendLevel(eventType string, isState bool) int64 { if level, ok := p.Events[eventType]; ok { return level } if isState { return p.StateDefault } return p.EventsDefault } // NotificationLevel returns the level needed to trigger a notification, // such as "room" for an @room mention. func (p *PowerLevels) NotificationLevel(key string) int64 { if level, ok := p.Notifications[key]; ok { return level } return DefaultNotification } // Creators returns the user IDs that count as creators of the room: the // sender of the create event, plus content.additional_creators in room // versions that allow it. func Creators(createEvent *event.Event) ([]string, error) { if createEvent.Type() != event.TypeCreate { return nil, fmt.Errorf("%s is not an m.room.create event", createEvent.EventID()) } creators := []string{createEvent.Sender()} if !createEvent.Rules().AdditionalCreators { return creators, nil } var content struct { AdditionalCreators []string `json:"additional_creators"` } if err := json.Unmarshal(createEvent.Content(), &content); err != nil { return nil, reject("m.room.create additional_creators is not an array of strings: %v", err) } for _, c := range content.AdditionalCreators { if err := id.ValidateUserID(c); err != nil { return nil, reject("m.room.create additional_creators: %v", err) } creators = append(creators, c) } return creators, nil } // parseLevel reads a single power level. // // Room versions up to 9 accepted a level written as a string, so "50" and // 50 both meant fifty — a laxness that let two servers that disagreed // about how to parse a string disagree about who could speak. Version 10 // requires an integer. func parseLevel(raw json.RawMessage, rules roomversion.Rules) (int64, error) { var level int64 if err := json.Unmarshal(raw, &level); err == nil { return level, nil } if rules.RequireIntegerPowerLevels { return 0, fmt.Errorf("%s is not an integer", raw) } var s string if err := json.Unmarshal(raw, &s); err != nil { return 0, fmt.Errorf("%s is neither an integer nor a string", raw) } level, err := strconv.ParseInt(s, 10, 64) if err != nil { return 0, fmt.Errorf("%s is a string that is not an integer", raw) } return level, nil } // parseLevelMap reads the events, notifications or users map. func parseLevelMap(raw json.RawMessage, rules roomversion.Rules) (map[string]int64, error) { var obj map[string]json.RawMessage if err := json.Unmarshal(raw, &obj); err != nil { return nil, fmt.Errorf("not an object: %v", err) } out := make(map[string]int64, len(obj)) for k, v := range obj { level, err := parseLevel(v, rules) if err != nil { return nil, fmt.Errorf("%q: %w", k, err) } out[k] = level } return out, nil }