MSC2946: Treat federation responses the same way as local responses (#1724)

* Start treating fed rooms/events the same as local rooms/events

* Share more code
main
Kegsay 2021-01-20 17:03:35 +00:00 committed by GitHub
parent b70238f2d5
commit c08e38df2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 70 additions and 101 deletions

View File

@ -239,33 +239,23 @@ func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse {
// Mark this room as processed. // Mark this room as processed.
processed[roomID] = true processed[roomID] = true
// Is the caller currently joined to the room or is the room `world_readable` // Collect rooms/events to send back (either locally or fetched via federation)
// If no, skip this room. If yes, continue. var discoveredRooms []gomatrixserverlib.MSC2946Room
if !w.roomExists(roomID) || !w.authorised(roomID) { var discoveredEvents []gomatrixserverlib.MSC2946StrippedEvent
// attempt to query this room over federation, as either we've never heard of it before
// or we've left it and hence are not authorised (but info may be exposed regardless) // If we know about this room and the caller is authorised (joined/world_readable) then pull
fedRes, err := w.federatedRoomInfo(roomID) // events locally
if w.roomExists(roomID) && w.authorised(roomID) {
// Get all `m.space.child` and `m.space.parent` state events for the room. *In addition*, get
// all `m.space.child` and `m.space.parent` state events which *point to* (via `state_key` or `content.room_id`)
// this room. This requires servers to store reverse lookups.
events, err := w.references(roomID)
if err != nil { if err != nil {
util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Errorf("failed to query federated spaces") util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Error("failed to extract references for room")
continue continue
} }
if fedRes != nil { discoveredEvents = events
res = combineResponses(res, *fedRes)
}
continue
}
// Get all `m.space.child` and `m.space.parent` state events for the room. *In addition*, get
// all `m.space.child` and `m.space.parent` state events which *point to* (via `state_key` or `content.room_id`)
// this room. This requires servers to store reverse lookups.
refs, err := w.references(roomID)
if err != nil {
util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Error("failed to extract references for room")
continue
}
// If this room has not ever been in `rooms` (across multiple requests), extract the
// `PublicRoomsChunk` for this room.
if !w.alreadySent(roomID) && !w.roomIsExcluded(roomID) {
pubRoom := w.publicRoomsChunk(roomID) pubRoom := w.publicRoomsChunk(roomID)
roomType := "" roomType := ""
create := w.stateEvent(roomID, gomatrixserverlib.MRoomCreate, "") create := w.stateEvent(roomID, gomatrixserverlib.MRoomCreate, "")
@ -275,12 +265,31 @@ func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse {
} }
// Add the total number of events to `PublicRoomsChunk` under `num_refs`. Add `PublicRoomsChunk` to `rooms`. // Add the total number of events to `PublicRoomsChunk` under `num_refs`. Add `PublicRoomsChunk` to `rooms`.
res.Rooms = append(res.Rooms, gomatrixserverlib.MSC2946Room{ discoveredRooms = append(discoveredRooms, gomatrixserverlib.MSC2946Room{
PublicRoom: *pubRoom, PublicRoom: *pubRoom,
NumRefs: refs.len(), NumRefs: len(discoveredEvents),
RoomType: roomType, RoomType: roomType,
}) })
w.markSent(roomID) } else {
// attempt to query this room over federation, as either we've never heard of it before
// or we've left it and hence are not authorised (but info may be exposed regardless)
fedRes, err := w.federatedRoomInfo(roomID)
if err != nil {
util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Errorf("failed to query federated spaces")
continue
}
if fedRes != nil {
discoveredRooms = fedRes.Rooms
discoveredEvents = fedRes.Events
}
}
// If this room has not ever been in `rooms` (across multiple requests), send it now
for _, room := range discoveredRooms {
if !w.alreadySent(room.RoomID) && !w.roomIsExcluded(room.RoomID) {
res.Rooms = append(res.Rooms, room)
w.markSent(room.RoomID)
}
} }
uniqueRooms := make(set) uniqueRooms := make(set)
@ -288,45 +297,37 @@ func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse {
// If this is the root room from the original request, insert all these events into `events` if // If this is the root room from the original request, insert all these events into `events` if
// they haven't been added before (across multiple requests). // they haven't been added before (across multiple requests).
if w.rootRoomID == roomID { if w.rootRoomID == roomID {
for _, ev := range refs.events() { for _, ev := range discoveredEvents {
if !w.alreadySent(ev.EventID()) { if !w.alreadySent(eventKey(&ev)) {
strip := stripped(ev.Event) res.Events = append(res.Events, ev)
if strip == nil { uniqueRooms[ev.RoomID] = true
continue uniqueRooms[spaceTargetStripped(&ev)] = true
} w.markSent(eventKey(&ev))
res.Events = append(res.Events, *strip)
uniqueRooms[ev.RoomID()] = true
uniqueRooms[SpaceTarget(ev)] = true
w.markSent(ev.EventID())
} }
} }
} else { } else {
// Else add them to `events` honouring the `limit` and `max_rooms_per_space` values. If either // Else add them to `events` honouring the `limit` and `max_rooms_per_space` values. If either
// are exceeded, stop adding events. If the event has already been added, do not add it again. // are exceeded, stop adding events. If the event has already been added, do not add it again.
numAdded := 0 numAdded := 0
for _, ev := range refs.events() { for _, ev := range discoveredEvents {
if w.req.Limit > 0 && len(res.Events) >= w.req.Limit { if w.req.Limit > 0 && len(res.Events) >= w.req.Limit {
break break
} }
if w.req.MaxRoomsPerSpace > 0 && numAdded >= w.req.MaxRoomsPerSpace { if w.req.MaxRoomsPerSpace > 0 && numAdded >= w.req.MaxRoomsPerSpace {
break break
} }
if w.alreadySent(ev.EventID()) { if w.alreadySent(eventKey(&ev)) {
continue continue
} }
// Skip the room if it's part of exclude_rooms but ONLY IF the source matches, as we still // Skip the room if it's part of exclude_rooms but ONLY IF the source matches, as we still
// want to catch arrows which point to excluded rooms. // want to catch arrows which point to excluded rooms.
if w.roomIsExcluded(ev.RoomID()) { if w.roomIsExcluded(ev.RoomID) {
continue continue
} }
strip := stripped(ev.Event) res.Events = append(res.Events, ev)
if strip == nil { uniqueRooms[ev.RoomID] = true
continue uniqueRooms[spaceTargetStripped(&ev)] = true
} w.markSent(eventKey(&ev))
res.Events = append(res.Events, *strip)
uniqueRooms[ev.RoomID()] = true
uniqueRooms[SpaceTarget(ev)] = true
w.markSent(ev.EventID())
// we don't distinguish between child state events and parent state events for the purposes of // we don't distinguish between child state events and parent state events for the purposes of
// max_rooms_per_space, maybe we should? // max_rooms_per_space, maybe we should?
numAdded++ numAdded++
@ -521,51 +522,27 @@ func (w *walker) authorisedUser(roomID string) bool {
} }
// references returns all references pointing to or from this room. // references returns all references pointing to or from this room.
func (w *walker) references(roomID string) (eventLookup, error) { func (w *walker) references(roomID string) ([]gomatrixserverlib.MSC2946StrippedEvent, error) {
events, err := w.db.References(w.ctx, roomID) events, err := w.db.References(w.ctx, roomID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
el := make(eventLookup) el := make([]gomatrixserverlib.MSC2946StrippedEvent, 0, len(events))
for _, ev := range events { for _, ev := range events {
// only return events that have a `via` key as per MSC1772 // only return events that have a `via` key as per MSC1772
// else we'll incorrectly walk redacted events (as the link // else we'll incorrectly walk redacted events (as the link
// is in the state_key) // is in the state_key)
if gjson.GetBytes(ev.Content(), "via").Exists() { if gjson.GetBytes(ev.Content(), "via").Exists() {
el.set(ev) strip := stripped(ev.Event)
if strip == nil {
continue
}
el = append(el, *strip)
} }
} }
return el, nil return el, nil
} }
// state event lookup across multiple rooms keyed on event type
// NOT THREAD SAFE
type eventLookup map[string][]*gomatrixserverlib.HeaderedEvent
func (el eventLookup) set(ev *gomatrixserverlib.HeaderedEvent) {
evs := el[ev.Type()]
if evs == nil {
evs = make([]*gomatrixserverlib.HeaderedEvent, 0)
}
evs = append(evs, ev)
el[ev.Type()] = evs
}
func (el eventLookup) len() int {
sum := 0
for _, evs := range el {
sum += len(evs)
}
return sum
}
func (el eventLookup) events() (events []*gomatrixserverlib.HeaderedEvent) {
for _, evs := range el {
events = append(events, evs...)
}
return
}
type set map[string]bool type set map[string]bool
func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEvent { func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEvent {
@ -581,27 +558,19 @@ func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEve
} }
} }
func combineResponses(local, remote gomatrixserverlib.MSC2946SpacesResponse) gomatrixserverlib.MSC2946SpacesResponse { func eventKey(event *gomatrixserverlib.MSC2946StrippedEvent) string {
knownRooms := make(set) return event.RoomID + "|" + event.Type + "|" + event.StateKey
for _, room := range local.Rooms { }
knownRooms[room.RoomID] = true
} func spaceTargetStripped(event *gomatrixserverlib.MSC2946StrippedEvent) string {
knownEvents := make(set) if event.StateKey == "" {
for _, event := range local.Events { return "" // no-op
knownEvents[event.RoomID+event.Type+event.StateKey] = true }
} switch event.Type {
// mux in remote entries if and only if they aren't present already case ConstSpaceParentEventType:
for _, room := range remote.Rooms { return event.StateKey
if knownRooms[room.RoomID] { case ConstSpaceChildEventType:
continue return event.StateKey
} }
local.Rooms = append(local.Rooms, room) return ""
}
for _, event := range remote.Events {
if knownEvents[event.RoomID+event.Type+event.StateKey] {
continue
}
local.Events = append(local.Events, event)
}
return local
} }