/* Copyright 2016-2017 Vector Creations Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gomatrixserverlib import ( "encoding/json" "strconv" "strings" ) // createContent is the JSON content of a m.room.create event along with // the top level keys needed for auth. // See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-create for descriptions of the fields. type createContent struct { // We need the domain of the create event when checking federatability. senderDomain string // We need the roomID to check that events are in the same room as the create event. roomID string // We need the eventID to check the first join event in the room. eventID string // The "m.federate" flag tells us whether the room can be federated to other servers. Federate *bool `json:"m.federate"` // The creator of the room tells us what the default power levels are. Creator string `json:"creator"` } // newCreateContentFromAuthEvents loads the create event content from the create event in the // auth events. func newCreateContentFromAuthEvents(authEvents AuthEvents) (c createContent, err error) { var createEvent *Event if createEvent, err = authEvents.Create(); err != nil { return } if createEvent == nil { err = errorf("missing create event") return } if err = json.Unmarshal(createEvent.Content(), &c); err != nil { err = errorf("unparsable create event content: %s", err.Error()) return } c.roomID = createEvent.RoomID() c.eventID = createEvent.EventID() if c.senderDomain, err = domainFromID(createEvent.Sender()); err != nil { return } return } // domainAllowed checks whether the domain is allowed in the room by the // "m.federate" flag. func (c *createContent) domainAllowed(domain string) error { if domain == c.senderDomain { // If the domain matches the domain of the create event then the event // is always allowed regardless of the value of the "m.federate" flag. return nil } if c.Federate == nil || *c.Federate { // The m.federate field defaults to true. // If the domains are different then event is only allowed if the // "m.federate" flag is absent or true. return nil } return errorf("room is unfederatable") } // userIDAllowed checks whether the domain part of the user ID is allowed in // the room by the "m.federate" flag. func (c *createContent) userIDAllowed(id string) error { domain, err := domainFromID(id) if err != nil { return err } return c.domainAllowed(domain) } // domainFromID returns everything after the first ":" character to extract // the domain part of a matrix ID. func domainFromID(id string) (string, error) { // IDs have the format: SIGIL LOCALPART ":" DOMAIN // Split on the first ":" character since the domain can contain ":" // characters. parts := strings.SplitN(id, ":", 2) if len(parts) != 2 { // The ID must have a ":" character. return "", errorf("invalid ID: %q", id) } // Return everything after the first ":" character. return parts[1], nil } // memberContent is the JSON content of a m.room.member event needed for auth checks. // See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member for descriptions of the fields. type memberContent struct { // We use the membership key in order to check if the user is in the room. Membership string `json:"membership"` // We use the third_party_invite key to special case thirdparty invites. ThirdPartyInvite json.RawMessage `json:"third_party_invite"` } // newMemberContentFromAuthEvents loads the member content from the member event for the user ID in the auth events. // Returns an error if there was an error loading the member event or parsing the event content. func newMemberContentFromAuthEvents(authEvents AuthEvents, userID string) (c memberContent, err error) { var memberEvent *Event if memberEvent, err = authEvents.Member(userID); err != nil { return } if memberEvent == nil { // If there isn't a member event then the membership for the user // defaults to leave. c.Membership = leave return } return newMemberContentFromEvent(*memberEvent) } // newMemberContentFromEvent parse the member content from an event. // Returns an error if the content couldn't be parsed. func newMemberContentFromEvent(event Event) (c memberContent, err error) { if err = json.Unmarshal(event.Content(), &c); err != nil { err = errorf("unparsable member event content: %s", err.Error()) return } return } // joinRuleContent is the JSON content of a m.room.join_rules event needed for auth checks. // See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-join-rules for descriptions of the fields. type joinRuleContent struct { // We use the join_rule key to check whether join m.room.member events are allowed. JoinRule string `json:"join_rule"` } // newJoinRuleContentFromAuthEvents loads the join rule content from the join rules event in the auth event. // Returns an error if there was an error loading the join rule event or parsing the content. func newJoinRuleContentFromAuthEvents(authEvents AuthEvents) (c joinRuleContent, err error) { var joinRulesEvent *Event if joinRulesEvent, err = authEvents.JoinRules(); err != nil { return } if joinRulesEvent == nil { // Default to "invite" // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L368 c.JoinRule = invite return } if err = json.Unmarshal(joinRulesEvent.Content(), &c); err != nil { err = errorf("unparsable join_rules event content: %s", err.Error()) return } return } // powerLevelContent is the JSON content of a m.room.power_levels event needed for auth checks. // We can't unmarshal the content directly from JSON because we need to set // defaults and convert string values to int values. // See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels for descriptions of the fields. type powerLevelContent struct { banLevel int64 inviteLevel int64 kickLevel int64 redactLevel int64 userLevels map[string]int64 userDefaultLevel int64 eventLevels map[string]int64 eventDefaultLevel int64 stateDefaultLevel int64 } // userLevel returns the power level a user has in the room. func (c *powerLevelContent) userLevel(userID string) int64 { level, ok := c.userLevels[userID] if ok { return level } return c.userDefaultLevel } // eventLevel returns the power level needed to send an event in the room. func (c *powerLevelContent) eventLevel(eventType string, isState bool) int64 { if eventType == "m.room.third_party_invite" { // Special case third_party_invite events to have the same level as // m.room.member invite events. // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L182 return c.inviteLevel } level, ok := c.eventLevels[eventType] if ok { return level } if isState { return c.stateDefaultLevel } return c.eventDefaultLevel } // newPowerLevelContentFromAuthEvents loads the power level content from the // power level event in the auth events or returns the default values if there // is no power level event. func newPowerLevelContentFromAuthEvents(authEvents AuthEvents, creatorUserID string) (c powerLevelContent, err error) { powerLevelsEvent, err := authEvents.PowerLevels() if err != nil { return } if powerLevelsEvent != nil { return newPowerLevelContentFromEvent(*powerLevelsEvent) } // If there are no power levels then fall back to defaults. c.defaults() // If there is no power level event then the creator gets level 100 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L569 c.userLevels = map[string]int64{creatorUserID: 100} return } // defaults sets the power levels to their default values. func (c *powerLevelContent) defaults() { // Default invite level is 0. // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L426 c.inviteLevel = 0 // Default ban, kick and redacts levels are 50 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L376 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L456 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L1041 c.banLevel = 50 c.kickLevel = 50 c.redactLevel = 50 // Default user level is 0 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L558 c.userDefaultLevel = 0 // Default event level is 0, Default state level is 50 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L987 // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L991 c.eventDefaultLevel = 0 c.stateDefaultLevel = 50 } // newPowerLevelContentFromEvent loads the power level content from an event. func newPowerLevelContentFromEvent(event Event) (c powerLevelContent, err error) { // Set the levels to their default values. c.defaults() // We can't extract the JSON directly to the powerLevelContent because we // need to convert string values to int values. var content struct { InviteLevel levelJSONValue `json:"invite"` BanLevel levelJSONValue `json:"ban"` KickLevel levelJSONValue `json:"kick"` RedactLevel levelJSONValue `json:"redact"` UserLevels map[string]levelJSONValue `json:"users"` UsersDefaultLevel levelJSONValue `json:"users_default"` EventLevels map[string]levelJSONValue `json:"events"` StateDefaultLevel levelJSONValue `json:"state_default"` EventDefaultLevel levelJSONValue `json:"event_default"` } if err = json.Unmarshal(event.Content(), &content); err != nil { err = errorf("unparsable power_levels event content: %s", err.Error()) return } // Update the levels with the values that are present in the event content. content.InviteLevel.assignIfExists(&c.inviteLevel) content.BanLevel.assignIfExists(&c.banLevel) content.KickLevel.assignIfExists(&c.kickLevel) content.RedactLevel.assignIfExists(&c.redactLevel) content.UsersDefaultLevel.assignIfExists(&c.userDefaultLevel) content.StateDefaultLevel.assignIfExists(&c.stateDefaultLevel) content.EventDefaultLevel.assignIfExists(&c.eventDefaultLevel) for k, v := range content.UserLevels { if c.userLevels == nil { c.userLevels = make(map[string]int64) } c.userLevels[k] = v.value } for k, v := range content.EventLevels { if c.eventLevels == nil { c.eventLevels = make(map[string]int64) } c.eventLevels[k] = v.value } return } // A levelJSONValue is used for unmarshalling power levels from JSON. // It is intended to replicate the effects of x = int(content["key"]) in python. type levelJSONValue struct { // Was a value loaded from the JSON? exists bool // The integer value of the power level. value int64 } func (v *levelJSONValue) UnmarshalJSON(data []byte) error { var stringValue string var int64Value int64 var floatValue float64 var err error // First try to unmarshal as an int64. if err = json.Unmarshal(data, &int64Value); err != nil { // If unmarshalling as an int64 fails try as a string. if err = json.Unmarshal(data, &stringValue); err != nil { // If unmarshalling as a string fails try as a float. if err = json.Unmarshal(data, &floatValue); err != nil { return err } int64Value = int64(floatValue) } else { // If we managed to get a string, try parsing the string as an int. int64Value, err = strconv.ParseInt(stringValue, 10, 64) if err != nil { return err } } } v.exists = true v.value = int64Value return nil } // assign the power level if a value was present in the JSON. func (v *levelJSONValue) assignIfExists(to *int64) { if v.exists { *to = v.value } } // Check if the user ID is a valid user ID. func isValidUserID(userID string) bool { // TODO: Do we want to add anymore checks beyond checking the sigil and that it has a domain part? return userID[0] == '@' && strings.IndexByte(userID, ':') != -1 }