mirror of
https://github.com/1f349/dendrite.git
synced 2025-03-12 23:23:11 +00:00
* Start implementing the join room API * Hacks to get join room working * Make the TLS fingerprint configurable * Fix the client API proxy to handle '#' correctly * Return a 200 OK response * Write the join event along with current state to the room server * Comment on the error handling * Fix typos * Fix tab * Add TODO for moving authEventIDs to gomatrixserverlib * Comment on why we ignore the key ID argument for local keys * Avoid 'preceeded' * Neaten the control flow * Neaten the control flow even more * Return pointers * Rename err to lastErr for the loop
314 lines
11 KiB
Go
314 lines
11 KiB
Go
/* Copyright 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 (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// ResolveStateConflicts takes a list of state events with conflicting state keys
|
|
// and works out which event should be used for each state event.
|
|
func ResolveStateConflicts(conflicted []Event, authEvents []Event) []Event {
|
|
var r stateResolver
|
|
r.resolvedThirdPartyInvites = map[string]*Event{}
|
|
r.resolvedMembers = map[string]*Event{}
|
|
// Group the conflicted events by type and state key.
|
|
r.addConflicted(conflicted)
|
|
// Add the unconflicted auth events needed for auth checks.
|
|
for i := range authEvents {
|
|
r.addAuthEvent(&authEvents[i])
|
|
}
|
|
// Resolve the conflicted auth events.
|
|
r.resolveAndAddAuthBlocks([][]Event{r.creates})
|
|
r.resolveAndAddAuthBlocks([][]Event{r.powerLevels})
|
|
r.resolveAndAddAuthBlocks([][]Event{r.joinRules})
|
|
r.resolveAndAddAuthBlocks(r.thirdPartyInvites)
|
|
r.resolveAndAddAuthBlocks(r.members)
|
|
// Resolve any other conflicted state events.
|
|
for _, block := range r.others {
|
|
if event := r.resolveNormalBlock(block); event != nil {
|
|
r.result = append(r.result, *event)
|
|
}
|
|
}
|
|
return r.result
|
|
}
|
|
|
|
// A stateResolver tracks the internal state of the state resolution algorithm
|
|
// It has 3 sections:
|
|
//
|
|
// * Lists of lists of events to resolve grouped by event type and state key.
|
|
// * The resolved auth events grouped by type and state key.
|
|
// * A List of resolved events.
|
|
//
|
|
// It implements the AuthEvents interface and can be used for running auth checks.
|
|
type stateResolver struct {
|
|
// Lists of lists of events to resolve grouped by event type and state key:
|
|
// * creates, powerLevels, joinRules have empty state keys.
|
|
// * members and thirdPartyInvites are grouped by state key.
|
|
// * the others are grouped by the pair of type and state key.
|
|
creates []Event
|
|
powerLevels []Event
|
|
joinRules []Event
|
|
thirdPartyInvites [][]Event
|
|
members [][]Event
|
|
others [][]Event
|
|
// The resolved auth events grouped by type and state key.
|
|
resolvedCreate *Event
|
|
resolvedPowerLevels *Event
|
|
resolvedJoinRules *Event
|
|
resolvedThirdPartyInvites map[string]*Event
|
|
resolvedMembers map[string]*Event
|
|
// The list of resolved events.
|
|
// This will contain one entry for each conflicted event type and state key.
|
|
result []Event
|
|
}
|
|
|
|
func (r *stateResolver) Create() (*Event, error) {
|
|
return r.resolvedCreate, nil
|
|
}
|
|
|
|
func (r *stateResolver) PowerLevels() (*Event, error) {
|
|
return r.resolvedPowerLevels, nil
|
|
}
|
|
|
|
func (r *stateResolver) JoinRules() (*Event, error) {
|
|
return r.resolvedJoinRules, nil
|
|
}
|
|
|
|
func (r *stateResolver) ThirdPartyInvite(key string) (*Event, error) {
|
|
return r.resolvedThirdPartyInvites[key], nil
|
|
}
|
|
|
|
func (r *stateResolver) Member(key string) (*Event, error) {
|
|
return r.resolvedMembers[key], nil
|
|
}
|
|
|
|
func (r *stateResolver) addConflicted(events []Event) {
|
|
type conflictKey struct {
|
|
eventType string
|
|
stateKey string
|
|
}
|
|
offsets := map[conflictKey]int{}
|
|
// Split up the conflicted events into blocks with the same type and state key.
|
|
// Separate the auth events into specifically named lists because they have
|
|
// special rules for state resolution.
|
|
for _, event := range events {
|
|
key := conflictKey{event.Type(), *event.StateKey()}
|
|
// Work out which block to add the event to.
|
|
// By default we add the event to a block in the others list.
|
|
blockList := &r.others
|
|
switch key.eventType {
|
|
case MRoomCreate:
|
|
if key.stateKey == "" {
|
|
r.creates = append(r.creates, event)
|
|
continue
|
|
}
|
|
case MRoomPowerLevels:
|
|
if key.stateKey == "" {
|
|
r.powerLevels = append(r.powerLevels, event)
|
|
continue
|
|
}
|
|
case MRoomJoinRules:
|
|
if key.stateKey == "" {
|
|
r.joinRules = append(r.joinRules, event)
|
|
continue
|
|
}
|
|
case MRoomMember:
|
|
blockList = &r.members
|
|
case MRoomThirdPartyInvite:
|
|
blockList = &r.thirdPartyInvites
|
|
}
|
|
// We need to find an entry for the state key in a block list.
|
|
offset, ok := offsets[key]
|
|
if !ok {
|
|
// This is the first time we've seen that state key so we add a
|
|
// new block to the block list.
|
|
offset = len(*blockList)
|
|
*blockList = append(*blockList, nil)
|
|
offsets[key] = offset
|
|
}
|
|
// Get the address of the block in the block list.
|
|
block := &(*blockList)[offset]
|
|
// Add the event to the block.
|
|
*block = append(*block, event)
|
|
}
|
|
}
|
|
|
|
// Add an event to the resolved auth events.
|
|
func (r *stateResolver) addAuthEvent(event *Event) {
|
|
switch event.Type() {
|
|
case MRoomCreate:
|
|
if event.StateKeyEquals("") {
|
|
r.resolvedCreate = event
|
|
}
|
|
case MRoomPowerLevels:
|
|
if event.StateKeyEquals("") {
|
|
r.resolvedPowerLevels = event
|
|
}
|
|
case MRoomJoinRules:
|
|
if event.StateKeyEquals("") {
|
|
r.resolvedJoinRules = event
|
|
}
|
|
case MRoomMember:
|
|
r.resolvedMembers[*event.StateKey()] = event
|
|
case MRoomThirdPartyInvite:
|
|
r.resolvedThirdPartyInvites[*event.StateKey()] = event
|
|
default:
|
|
panic(fmt.Errorf("Unexpected auth event with type %q", event.Type()))
|
|
}
|
|
}
|
|
|
|
// Remove the auth event with the given type and state key.
|
|
func (r *stateResolver) removeAuthEvent(eventType, stateKey string) {
|
|
switch eventType {
|
|
case MRoomCreate:
|
|
if stateKey == "" {
|
|
r.resolvedCreate = nil
|
|
}
|
|
case MRoomPowerLevels:
|
|
if stateKey == "" {
|
|
r.resolvedPowerLevels = nil
|
|
}
|
|
case MRoomJoinRules:
|
|
if stateKey == "" {
|
|
r.resolvedJoinRules = nil
|
|
}
|
|
case MRoomMember:
|
|
r.resolvedMembers[stateKey] = nil
|
|
case MRoomThirdPartyInvite:
|
|
r.resolvedThirdPartyInvites[stateKey] = nil
|
|
default:
|
|
panic(fmt.Errorf("Unexpected auth event with type %q", eventType))
|
|
}
|
|
}
|
|
|
|
// resolveAndAddAuthBlocks resolves each block of conflicting auth state events in a list of blocks
|
|
// where all the blocks have the same event type.
|
|
// Once every block has been resolved the resulting events are added to the events used for auth checks.
|
|
// This is called once per auth event type and state key pair.
|
|
func (r *stateResolver) resolveAndAddAuthBlocks(blocks [][]Event) {
|
|
start := len(r.result)
|
|
for _, block := range blocks {
|
|
if len(block) == 0 {
|
|
continue
|
|
}
|
|
if event := r.resolveAuthBlock(block); event != nil {
|
|
r.result = append(r.result, *event)
|
|
}
|
|
}
|
|
// Only add the events to the auth events once all of the events with that type have been resolved.
|
|
// (SPEC: This is done to avoid the result of state resolution depending on the iteration order)
|
|
for i := start; i < len(r.result); i++ {
|
|
r.addAuthEvent(&r.result[i])
|
|
}
|
|
}
|
|
|
|
// resolveAuthBlock resolves a block of auth events with the same state key to a single event.
|
|
func (r *stateResolver) resolveAuthBlock(events []Event) *Event {
|
|
// Sort the events by depth and sha1 of event ID
|
|
block := sortConflictedEventsByDepthAndSHA1(events)
|
|
|
|
// Pick the "oldest" event, that is the one with the lowest depth, as the first candidate.
|
|
// If none of the newer events pass auth checks against this event then we pick the "oldest" event.
|
|
// (SPEC: This ensures that we always pick a state event for this type and state key.
|
|
// Note that if all the events fail auth checks we will still pick the "oldest" event.)
|
|
result := block[0].event
|
|
// Temporarily add the candidate event to the auth events.
|
|
r.addAuthEvent(result)
|
|
for i := 1; i < len(block); i++ {
|
|
event := block[i].event
|
|
// Check if the next event passes authentication checks against the current candidate.
|
|
// (SPEC: This ensures that "ban" events cannot be replaced by "join" events through a conflict)
|
|
if Allowed(*event, r) == nil {
|
|
// If the event passes authentication checks pick it as the current candidate.
|
|
// (SPEC: This prefers newer events so that we don't flip a valid state back to a previous version)
|
|
result = event
|
|
r.addAuthEvent(result)
|
|
} else {
|
|
// If the authentication check fails then we stop iterating the list and return the current candidate.
|
|
break
|
|
}
|
|
}
|
|
// Discard the event from the auth events.
|
|
// We'll add it back later when all events of the same type have been resolved.
|
|
// (SPEC: This is done to avoid the result of state resolution depending on the iteration order)
|
|
r.removeAuthEvent(result.Type(), *result.StateKey())
|
|
return result
|
|
}
|
|
|
|
// resolveNormalBlock resolves a block of normal state events with the same state key to a single event.
|
|
func (r *stateResolver) resolveNormalBlock(events []Event) *Event {
|
|
// Sort the events by depth and sha1 of event ID
|
|
block := sortConflictedEventsByDepthAndSHA1(events)
|
|
// Start at the "newest" event, that is the one with the highest depth, and go
|
|
// backward through the list until we find one that passes authentication checks.
|
|
// (SPEC: This prefers newer events so that we don't flip a valid state back to a previous version)
|
|
for i := len(block) - 1; i > 0; i-- {
|
|
event := block[i].event
|
|
if Allowed(*event, r) == nil {
|
|
return event
|
|
}
|
|
}
|
|
// If all the auth checks for newer events fail then we pick the oldest event.
|
|
// (SPEC: This ensures that we always pick a state event for this type and state key.
|
|
// Note that if all the events fail auth checks we will still pick the "oldest" event.)
|
|
return block[0].event
|
|
}
|
|
|
|
// sortConflictedEventsByDepthAndSHA1 sorts by ascending depth and descending sha1 of event ID.
|
|
func sortConflictedEventsByDepthAndSHA1(events []Event) []conflictedEvent {
|
|
block := make([]conflictedEvent, len(events))
|
|
for i := range events {
|
|
event := &events[i]
|
|
block[i] = conflictedEvent{
|
|
depth: event.Depth(),
|
|
eventIDSHA1: sha1.Sum([]byte(event.EventID())),
|
|
event: event,
|
|
}
|
|
}
|
|
sort.Sort(conflictedEventSorter(block))
|
|
return block
|
|
}
|
|
|
|
// A conflictedEvent is used to sort the events in a block by ascending depth and descending sha1 of event ID.
|
|
// (SPEC: We use the SHA1 of the event ID as an arbitrary tie breaker between events with the same depth)
|
|
type conflictedEvent struct {
|
|
depth int64
|
|
eventIDSHA1 [sha1.Size]byte
|
|
event *Event
|
|
}
|
|
|
|
// A conflictedEventSorter is used to sort the events using sort.Sort.
|
|
type conflictedEventSorter []conflictedEvent
|
|
|
|
func (s conflictedEventSorter) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s conflictedEventSorter) Less(i, j int) bool {
|
|
if s[i].depth == s[j].depth {
|
|
return bytes.Compare(s[i].eventIDSHA1[:], s[j].eventIDSHA1[:]) > 0
|
|
}
|
|
return s[i].depth < s[j].depth
|
|
}
|
|
|
|
func (s conflictedEventSorter) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|