mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-09 23:23:10 -06:00
Refactor room hierarchy walker
This commit is contained in:
parent
b98e50f544
commit
97f2befd1b
|
|
@ -30,24 +30,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RoomHierarchyPaginationCache struct {
|
type RoomHierarchyPaginationCache struct {
|
||||||
cache map[string]roomserverAPI.CachedRoomHierarchyWalker
|
cache map[string]roomserverAPI.RoomHierarchyWalker
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoomHierarchyPaginationCache() RoomHierarchyPaginationCache {
|
func NewRoomHierarchyPaginationCache() RoomHierarchyPaginationCache {
|
||||||
return RoomHierarchyPaginationCache{
|
return RoomHierarchyPaginationCache{
|
||||||
cache: map[string]roomserverAPI.CachedRoomHierarchyWalker{},
|
cache: map[string]roomserverAPI.RoomHierarchyWalker{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RoomHierarchyPaginationCache) Get(token string) roomserverAPI.CachedRoomHierarchyWalker {
|
func (c *RoomHierarchyPaginationCache) Get(token string) *roomserverAPI.RoomHierarchyWalker {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
line := c.cache[token]
|
line, ok := c.cache[token]
|
||||||
return line
|
if ok {
|
||||||
|
return &line
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RoomHierarchyPaginationCache) AddLine(line roomserverAPI.CachedRoomHierarchyWalker) string {
|
func (c *RoomHierarchyPaginationCache) AddLine(line roomserverAPI.RoomHierarchyWalker) string {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
token := uuid.NewString()
|
token := uuid.NewString()
|
||||||
|
|
@ -114,21 +118,21 @@ func QueryRoomHierarchy(req *http.Request, device *userapi.Device, roomIDStr str
|
||||||
|
|
||||||
var walker roomserverAPI.RoomHierarchyWalker
|
var walker roomserverAPI.RoomHierarchyWalker
|
||||||
if from == "" { // No pagination token provided, so start new hierarchy walker
|
if from == "" { // No pagination token provided, so start new hierarchy walker
|
||||||
walker = rsAPI.QueryRoomHierarchy(req.Context(), types.NewDeviceNotServerName(*device), roomID, suggestedOnly, maxDepth)
|
walker = roomserverAPI.NewRoomHierarchyWalker(types.NewDeviceNotServerName(*device), roomID, suggestedOnly, maxDepth)
|
||||||
} else { // Attempt to resume cached walker
|
} else { // Attempt to resume cached walker
|
||||||
cachedWalker := paginationCache.Get(from)
|
cachedWalker := paginationCache.Get(from)
|
||||||
|
|
||||||
if cachedWalker == nil || !cachedWalker.ValidateParams(suggestedOnly, maxDepth) {
|
if cachedWalker == nil || cachedWalker.SuggestedOnly != suggestedOnly || cachedWalker.MaxDepth != maxDepth {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.InvalidParam("pagination not found for provided token ('from') with given 'max_depth', 'suggested_only' and room ID"),
|
JSON: spec.InvalidParam("pagination not found for provided token ('from') with given 'max_depth', 'suggested_only' and room ID"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
walker = cachedWalker.GetWalker()
|
walker = *cachedWalker
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveredRooms, err := walker.NextPage(limit)
|
discoveredRooms, nextWalker, err := rsAPI.QueryNextRoomHierarchyPage(req.Context(), walker, limit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
|
|
@ -147,9 +151,9 @@ func QueryRoomHierarchy(req *http.Request, device *userapi.Device, roomIDStr str
|
||||||
}
|
}
|
||||||
|
|
||||||
nextBatch := ""
|
nextBatch := ""
|
||||||
if !walker.Done() {
|
// nextWalker will be nil if there's no more rooms left to walk
|
||||||
cacheLine := walker.GetCached()
|
if nextWalker != nil {
|
||||||
nextBatch = paginationCache.AddLine(cacheLine)
|
nextBatch = paginationCache.AddLine(*nextWalker)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,8 @@ func QueryRoomHierarchy(httpReq *http.Request, request *fclient.FederationReques
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
walker := rsAPI.QueryRoomHierarchy(httpReq.Context(), types.NewServerNameNotDevice(request.Origin()), roomID, suggestedOnly, 1)
|
walker := roomserverAPI.NewRoomHierarchyWalker(types.NewServerNameNotDevice(request.Origin()), roomID, suggestedOnly, 1)
|
||||||
|
discoveredRooms, _, err := rsAPI.QueryNextRoomHierarchyPage(httpReq.Context(), walker, -1)
|
||||||
discoveredRooms, err := walker.NextPage(-1)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ type QueryEventsAPI interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryRoomHierarchyAPI interface {
|
type QueryRoomHierarchyAPI interface {
|
||||||
QueryRoomHierarchy(ctx context.Context, caller types.DeviceOrServerName, roomID spec.RoomID, suggestedOnly bool, maxDepth int) RoomHierarchyWalker
|
QueryNextRoomHierarchyPage(ctx context.Context, walker RoomHierarchyWalker, limit int) ([]fclient.MSC2946Room, *RoomHierarchyWalker, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// API functions required by the syncapi
|
// API functions required by the syncapi
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
|
|
@ -512,24 +511,63 @@ type QueryRoomHierarchyRequest struct {
|
||||||
From int `json:"json"`
|
From int `json:"json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// An iterator-like interface for walking a room/space hierarchy, returning each rooms information.
|
// A struct storing the intermediate state of a room hierarchy query for pagination purposes.
|
||||||
//
|
//
|
||||||
// Used for implementing space summaries / room hierarchies
|
// Used for implementing space summaries / room hierarchies
|
||||||
type RoomHierarchyWalker interface {
|
//
|
||||||
// Walk the room hierarchy to retrieve room information until either
|
// Use NewRoomHierarchyWalker on the roomserver API to construct this.
|
||||||
// no room left, or provided limit reached. If limit provided is -1, then this is
|
type RoomHierarchyWalker struct {
|
||||||
// treated as no limit.
|
RootRoomID spec.RoomID
|
||||||
NextPage(limit int) ([]fclient.MSC2946Room, error)
|
Caller types.DeviceOrServerName
|
||||||
// Returns true if there are no more rooms left to walk
|
SuggestedOnly bool
|
||||||
Done() bool
|
MaxDepth int
|
||||||
// Returns a stripped down version of the hiearchy walker suitable for pagination caching
|
Processed RoomSet
|
||||||
GetCached() CachedRoomHierarchyWalker
|
Unvisited []RoomHierarchyWalkerQueuedRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stripped down version of RoomHierarchyWalker suitable for caching (for pagination)
|
type RoomHierarchyWalkerQueuedRoom struct {
|
||||||
type CachedRoomHierarchyWalker interface {
|
RoomID spec.RoomID
|
||||||
// Converts this cached walker back into an actual walker, to resume walking from.
|
ParentRoomID *spec.RoomID
|
||||||
GetWalker() RoomHierarchyWalker
|
Depth int
|
||||||
// Validates that the given parameters match those stored in the cache
|
Vias []string // vias to query this room by
|
||||||
ValidateParams(suggestedOnly bool, maxDepth int) bool
|
}
|
||||||
|
|
||||||
|
func NewRoomHierarchyWalker(caller types.DeviceOrServerName, roomID spec.RoomID, suggestedOnly bool, maxDepth int) RoomHierarchyWalker {
|
||||||
|
walker := RoomHierarchyWalker{
|
||||||
|
RootRoomID: roomID,
|
||||||
|
Caller: caller,
|
||||||
|
SuggestedOnly: suggestedOnly,
|
||||||
|
MaxDepth: maxDepth,
|
||||||
|
Unvisited: []RoomHierarchyWalkerQueuedRoom{{
|
||||||
|
RoomID: roomID,
|
||||||
|
ParentRoomID: nil,
|
||||||
|
Depth: 0,
|
||||||
|
}},
|
||||||
|
Processed: NewRoomSet(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return walker
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoomSet map[spec.RoomID]struct{}
|
||||||
|
|
||||||
|
func NewRoomSet() RoomSet {
|
||||||
|
return RoomSet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s RoomSet) Contains(val spec.RoomID) bool {
|
||||||
|
_, ok := s[val]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s RoomSet) Add(val spec.RoomID) {
|
||||||
|
s[val] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s RoomSet) Copy() RoomSet {
|
||||||
|
copied := make(RoomSet, len(s))
|
||||||
|
for k, _ := range s {
|
||||||
|
copied.Add(k)
|
||||||
|
}
|
||||||
|
return copied
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fs "github.com/matrix-org/dendrite/federationapi/api"
|
fs "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
|
||||||
roomserver "github.com/matrix-org/dendrite/roomserver/api"
|
roomserver "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
|
@ -33,59 +32,6 @@ import (
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Query the hierarchy of a room (A.K.A 'space summary')
|
|
||||||
//
|
|
||||||
// This function returns a iterator-like struct over the hierarchy of a room.
|
|
||||||
func (r *Queryer) QueryRoomHierarchy(ctx context.Context, caller types.DeviceOrServerName, roomID spec.RoomID, suggestedOnly bool, maxDepth int) roomserver.RoomHierarchyWalker {
|
|
||||||
walker := RoomHierarchyWalker{
|
|
||||||
rootRoomID: roomID.String(),
|
|
||||||
caller: caller,
|
|
||||||
thisServer: r.Cfg.Global.ServerName,
|
|
||||||
rsAPI: r,
|
|
||||||
fsAPI: r.FSAPI,
|
|
||||||
ctx: ctx,
|
|
||||||
roomHierarchyCache: r.Cache,
|
|
||||||
suggestedOnly: suggestedOnly,
|
|
||||||
maxDepth: maxDepth,
|
|
||||||
unvisited: []roomVisit{{
|
|
||||||
roomID: roomID.String(),
|
|
||||||
parentRoomID: "",
|
|
||||||
depth: 0,
|
|
||||||
}},
|
|
||||||
processed: stringSet{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &walker
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringSet map[string]struct{}
|
|
||||||
|
|
||||||
func (s stringSet) contains(val string) bool {
|
|
||||||
_, ok := s[val]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stringSet) add(val string) {
|
|
||||||
s[val] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoomHierarchyWalker struct {
|
|
||||||
rootRoomID string // TODO change to spec.RoomID
|
|
||||||
caller types.DeviceOrServerName
|
|
||||||
thisServer spec.ServerName
|
|
||||||
rsAPI *Queryer
|
|
||||||
fsAPI fs.RoomserverFederationAPI
|
|
||||||
ctx context.Context
|
|
||||||
roomHierarchyCache caching.RoomHierarchyCache
|
|
||||||
suggestedOnly bool
|
|
||||||
maxDepth int
|
|
||||||
|
|
||||||
processed stringSet
|
|
||||||
unvisited []roomVisit
|
|
||||||
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ConstCreateEventContentKey = "type"
|
ConstCreateEventContentKey = "type"
|
||||||
ConstCreateEventContentValueSpace = "m.space"
|
ConstCreateEventContentValueSpace = "m.space"
|
||||||
|
|
@ -96,34 +42,39 @@ const (
|
||||||
// Walk the room hierarchy to retrieve room information until either
|
// Walk the room hierarchy to retrieve room information until either
|
||||||
// no room left, or provided limit reached. If limit provided is -1, then this is
|
// no room left, or provided limit reached. If limit provided is -1, then this is
|
||||||
// treated as no limit.
|
// treated as no limit.
|
||||||
func (w *RoomHierarchyWalker) NextPage(limit int) ([]fclient.MSC2946Room, error) {
|
func (querier *Queryer) QueryNextRoomHierarchyPage(ctx context.Context, walker roomserver.RoomHierarchyWalker, limit int) ([]fclient.MSC2946Room, *roomserver.RoomHierarchyWalker, error) {
|
||||||
if authorised, _ := w.authorised(w.rootRoomID, ""); !authorised {
|
if authorised, _ := authorised(ctx, querier, walker.Caller, walker.RootRoomID, nil); !authorised {
|
||||||
return nil, roomserver.ErrRoomUnknownOrNotAllowed{Err: fmt.Errorf("room is unknown/forbidden")}
|
return nil, nil, roomserver.ErrRoomUnknownOrNotAllowed{Err: fmt.Errorf("room is unknown/forbidden")}
|
||||||
}
|
}
|
||||||
|
|
||||||
var discoveredRooms []fclient.MSC2946Room
|
var discoveredRooms []fclient.MSC2946Room
|
||||||
|
|
||||||
|
// Copy unvisited and processed to avoid modifying walker
|
||||||
|
unvisited := []roomserver.RoomHierarchyWalkerQueuedRoom{}
|
||||||
|
copy(unvisited, walker.Unvisited)
|
||||||
|
processed := walker.Processed.Copy()
|
||||||
|
|
||||||
// Depth first -> stack data structure
|
// Depth first -> stack data structure
|
||||||
for len(w.unvisited) > 0 {
|
for len(unvisited) > 0 {
|
||||||
if len(discoveredRooms) >= limit {
|
if len(discoveredRooms) >= limit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// pop the stack
|
// pop the stack
|
||||||
rv := w.unvisited[len(w.unvisited)-1]
|
queuedRoom := unvisited[len(unvisited)-1]
|
||||||
w.unvisited = w.unvisited[:len(w.unvisited)-1]
|
unvisited = unvisited[:len(unvisited)-1]
|
||||||
// If this room has already been processed, skip.
|
// If this room has already been processed, skip.
|
||||||
// If this room exceeds the specified depth, skip.
|
// If this room exceeds the specified depth, skip.
|
||||||
if w.processed.contains(rv.roomID) || rv.roomID == "" || (w.maxDepth > 0 && rv.depth > w.maxDepth) {
|
if processed.Contains(queuedRoom.RoomID) || (walker.MaxDepth > 0 && queuedRoom.Depth > walker.MaxDepth) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark this room as processed.
|
// Mark this room as processed.
|
||||||
w.processed.add(rv.roomID)
|
processed.Add(queuedRoom.RoomID)
|
||||||
|
|
||||||
// if this room is not a space room, skip.
|
// if this room is not a space room, skip.
|
||||||
var roomType string
|
var roomType string
|
||||||
create := w.stateEvent(rv.roomID, spec.MRoomCreate, "")
|
create := stateEvent(ctx, querier, queuedRoom.RoomID, spec.MRoomCreate, "")
|
||||||
if create != nil {
|
if create != nil {
|
||||||
// escape the `.`s so gjson doesn't think it's nested
|
// escape the `.`s so gjson doesn't think it's nested
|
||||||
roomType = gjson.GetBytes(create.Content(), strings.ReplaceAll(ConstCreateEventContentKey, ".", `\.`)).Str
|
roomType = gjson.GetBytes(create.Content(), strings.ReplaceAll(ConstCreateEventContentKey, ".", `\.`)).Str
|
||||||
|
|
@ -134,11 +85,11 @@ func (w *RoomHierarchyWalker) NextPage(limit int) ([]fclient.MSC2946Room, error)
|
||||||
|
|
||||||
// If we know about this room and the caller is authorised (joined/world_readable) then pull
|
// If we know about this room and the caller is authorised (joined/world_readable) then pull
|
||||||
// events locally
|
// events locally
|
||||||
roomExists := w.roomExists(rv.roomID)
|
roomExists := roomExists(ctx, querier, queuedRoom.RoomID)
|
||||||
if !roomExists {
|
if !roomExists {
|
||||||
// attempt to query this room over federation, as either we've never heard of it before
|
// 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)
|
// or we've left it and hence are not authorised (but info may be exposed regardless)
|
||||||
fedRes := w.federatedRoomInfo(rv.roomID, rv.vias)
|
fedRes := federatedRoomInfo(ctx, querier, walker.Caller, walker.SuggestedOnly, queuedRoom.RoomID, queuedRoom.Vias)
|
||||||
if fedRes != nil {
|
if fedRes != nil {
|
||||||
discoveredChildEvents = fedRes.Room.ChildrenState
|
discoveredChildEvents = fedRes.Room.ChildrenState
|
||||||
discoveredRooms = append(discoveredRooms, fedRes.Room)
|
discoveredRooms = append(discoveredRooms, fedRes.Room)
|
||||||
|
|
@ -150,16 +101,16 @@ func (w *RoomHierarchyWalker) NextPage(limit int) ([]fclient.MSC2946Room, error)
|
||||||
// as these children may be rooms we do know about.
|
// as these children may be rooms we do know about.
|
||||||
roomType = ConstCreateEventContentValueSpace
|
roomType = ConstCreateEventContentValueSpace
|
||||||
}
|
}
|
||||||
} else if authorised, isJoinedOrInvited := w.authorised(rv.roomID, rv.parentRoomID); authorised {
|
} else if authorised, isJoinedOrInvited := authorised(ctx, querier, walker.Caller, queuedRoom.RoomID, queuedRoom.ParentRoomID); authorised {
|
||||||
// Get all `m.space.child` state events for this room
|
// Get all `m.space.child` state events for this room
|
||||||
events, err := w.childReferences(rv.roomID)
|
events, err := childReferences(querier, walker.SuggestedOnly, queuedRoom.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(w.ctx).WithError(err).WithField("room_id", rv.roomID).Error("failed to extract references for room")
|
util.GetLogger(ctx).WithError(err).WithField("room_id", queuedRoom.RoomID).Error("failed to extract references for room")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
discoveredChildEvents = events
|
discoveredChildEvents = events
|
||||||
|
|
||||||
pubRoom := w.publicRoomsChunk(rv.roomID)
|
pubRoom := publicRoomsChunk(ctx, querier, queuedRoom.RoomID)
|
||||||
|
|
||||||
discoveredRooms = append(discoveredRooms, fclient.MSC2946Room{
|
discoveredRooms = append(discoveredRooms, fclient.MSC2946Room{
|
||||||
PublicRoom: *pubRoom,
|
PublicRoom: *pubRoom,
|
||||||
|
|
@ -192,51 +143,213 @@ func (w *RoomHierarchyWalker) NextPage(limit int) ([]fclient.MSC2946Room, error)
|
||||||
}{}
|
}{}
|
||||||
ev := discoveredChildEvents[i]
|
ev := discoveredChildEvents[i]
|
||||||
_ = json.Unmarshal(ev.Content, &spaceContent)
|
_ = json.Unmarshal(ev.Content, &spaceContent)
|
||||||
w.unvisited = append(w.unvisited, roomVisit{
|
|
||||||
roomID: ev.StateKey,
|
childRoomID, err := spec.NewRoomID(ev.StateKey)
|
||||||
parentRoomID: rv.roomID,
|
|
||||||
depth: rv.depth + 1,
|
if err != nil {
|
||||||
vias: spaceContent.Via,
|
util.GetLogger(ctx).WithError(err).WithField("invalid_room_id", ev.StateKey).WithField("parent_room_id", queuedRoom.RoomID).Warn("Invalid room ID in m.space.child state event")
|
||||||
})
|
} else {
|
||||||
|
unvisited = append(unvisited, roomserver.RoomHierarchyWalkerQueuedRoom{
|
||||||
|
RoomID: *childRoomID,
|
||||||
|
ParentRoomID: &queuedRoom.RoomID,
|
||||||
|
Depth: queuedRoom.Depth + 1,
|
||||||
|
Vias: spaceContent.Via,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(w.unvisited) == 0 {
|
if len(unvisited) == 0 {
|
||||||
w.done = true
|
// If no more rooms to walk, then don't return a walker for future pages
|
||||||
|
return discoveredRooms, nil, nil
|
||||||
|
} else {
|
||||||
|
// If there are more rooms to walk, then return a new walker to resume walking from (for querying more pages)
|
||||||
|
newWalker := roomserver.RoomHierarchyWalker{
|
||||||
|
RootRoomID: walker.RootRoomID,
|
||||||
|
Caller: walker.Caller,
|
||||||
|
SuggestedOnly: walker.SuggestedOnly,
|
||||||
|
MaxDepth: walker.MaxDepth,
|
||||||
|
Unvisited: unvisited,
|
||||||
|
Processed: processed,
|
||||||
|
}
|
||||||
|
|
||||||
|
return discoveredRooms, &newWalker, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return discoveredRooms, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *RoomHierarchyWalker) Done() bool {
|
// authorised returns true iff the user is joined this room or the room is world_readable
|
||||||
return w.done
|
func authorised(ctx context.Context, querier *Queryer, caller types.DeviceOrServerName, roomID spec.RoomID, parentRoomID *spec.RoomID) (authed, isJoinedOrInvited bool) {
|
||||||
}
|
if clientCaller := caller.Device(); clientCaller != nil {
|
||||||
|
return authorisedUser(ctx, querier, clientCaller, roomID, parentRoomID)
|
||||||
func (w *RoomHierarchyWalker) GetCached() roomserver.CachedRoomHierarchyWalker {
|
} else {
|
||||||
return CachedRoomHierarchyWalker{
|
return authorisedServer(ctx, querier, roomID, *caller.ServerName()), false
|
||||||
rootRoomID: w.rootRoomID,
|
|
||||||
caller: w.caller,
|
|
||||||
thisServer: w.thisServer,
|
|
||||||
rsAPI: w.rsAPI,
|
|
||||||
fsAPI: w.fsAPI,
|
|
||||||
ctx: w.ctx,
|
|
||||||
cache: w.roomHierarchyCache,
|
|
||||||
suggestedOnly: w.suggestedOnly,
|
|
||||||
maxDepth: w.maxDepth,
|
|
||||||
processed: w.processed,
|
|
||||||
unvisited: w.unvisited,
|
|
||||||
done: w.done,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *RoomHierarchyWalker) stateEvent(roomID, evType, stateKey string) *types.HeaderedEvent {
|
// authorisedServer returns true iff the server is joined this room or the room is world_readable, public, or knockable
|
||||||
|
func authorisedServer(ctx context.Context, querier *Queryer, roomID spec.RoomID, callerServerName spec.ServerName) bool {
|
||||||
|
// Check history visibility / join rules first
|
||||||
|
hisVisTuple := gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: spec.MRoomHistoryVisibility,
|
||||||
|
StateKey: "",
|
||||||
|
}
|
||||||
|
joinRuleTuple := gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: spec.MRoomJoinRules,
|
||||||
|
StateKey: "",
|
||||||
|
}
|
||||||
|
var queryRoomRes roomserver.QueryCurrentStateResponse
|
||||||
|
err := querier.QueryCurrentState(ctx, &roomserver.QueryCurrentStateRequest{
|
||||||
|
RoomID: roomID.String(),
|
||||||
|
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||||
|
hisVisTuple, joinRuleTuple,
|
||||||
|
},
|
||||||
|
}, &queryRoomRes)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("failed to QueryCurrentState")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hisVisEv := queryRoomRes.StateEvents[hisVisTuple]
|
||||||
|
if hisVisEv != nil {
|
||||||
|
hisVis, _ := hisVisEv.HistoryVisibility()
|
||||||
|
if hisVis == "world_readable" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this room is a restricted room and if so, we need to check if the server is joined to an allowed room ID
|
||||||
|
// in addition to the actual room ID (but always do the actual one first as it's quicker in the common case)
|
||||||
|
allowJoinedToRoomIDs := []spec.RoomID{roomID}
|
||||||
|
joinRuleEv := queryRoomRes.StateEvents[joinRuleTuple]
|
||||||
|
|
||||||
|
if joinRuleEv != nil {
|
||||||
|
rule, ruleErr := joinRuleEv.JoinRule()
|
||||||
|
if ruleErr != nil {
|
||||||
|
util.GetLogger(ctx).WithError(ruleErr).WithField("parent_room_id", roomID).Warn("failed to get join rule")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule == spec.Public || rule == spec.Knock {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule == spec.Restricted {
|
||||||
|
allowJoinedToRoomIDs = append(allowJoinedToRoomIDs, restrictedJoinRuleAllowedRooms(ctx, joinRuleEv, "m.room_membership")...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if server is joined to any allowed room
|
||||||
|
for _, allowedRoomID := range allowJoinedToRoomIDs {
|
||||||
|
var queryRes fs.QueryJoinedHostServerNamesInRoomResponse
|
||||||
|
err = querier.FSAPI.QueryJoinedHostServerNamesInRoom(ctx, &fs.QueryJoinedHostServerNamesInRoomRequest{
|
||||||
|
RoomID: allowedRoomID.String(),
|
||||||
|
}, &queryRes)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("failed to QueryJoinedHostServerNamesInRoom")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, srv := range queryRes.ServerNames {
|
||||||
|
if srv == callerServerName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// authorisedUser returns true iff the user is invited/joined this room or the room is world_readable
|
||||||
|
// or if the room has a public or knock join rule.
|
||||||
|
// Failing that, if the room has a restricted join rule and belongs to the space parent listed, it will return true.
|
||||||
|
func authorisedUser(ctx context.Context, querier *Queryer, clientCaller *userapi.Device, roomID spec.RoomID, parentRoomID *spec.RoomID) (authed bool, isJoinedOrInvited bool) {
|
||||||
|
hisVisTuple := gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: spec.MRoomHistoryVisibility,
|
||||||
|
StateKey: "",
|
||||||
|
}
|
||||||
|
joinRuleTuple := gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: spec.MRoomJoinRules,
|
||||||
|
StateKey: "",
|
||||||
|
}
|
||||||
|
roomMemberTuple := gomatrixserverlib.StateKeyTuple{
|
||||||
|
EventType: spec.MRoomMember,
|
||||||
|
StateKey: clientCaller.UserID,
|
||||||
|
}
|
||||||
|
var queryRes roomserver.QueryCurrentStateResponse
|
||||||
|
err := querier.QueryCurrentState(ctx, &roomserver.QueryCurrentStateRequest{
|
||||||
|
RoomID: roomID.String(),
|
||||||
|
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||||
|
hisVisTuple, joinRuleTuple, roomMemberTuple,
|
||||||
|
},
|
||||||
|
}, &queryRes)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("failed to QueryCurrentState")
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
memberEv := queryRes.StateEvents[roomMemberTuple]
|
||||||
|
if memberEv != nil {
|
||||||
|
membership, _ := memberEv.Membership()
|
||||||
|
if membership == spec.Join || membership == spec.Invite {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hisVisEv := queryRes.StateEvents[hisVisTuple]
|
||||||
|
if hisVisEv != nil {
|
||||||
|
hisVis, _ := hisVisEv.HistoryVisibility()
|
||||||
|
if hisVis == "world_readable" {
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
joinRuleEv := queryRes.StateEvents[joinRuleTuple]
|
||||||
|
if parentRoomID != nil && joinRuleEv != nil {
|
||||||
|
var allowed bool
|
||||||
|
rule, ruleErr := joinRuleEv.JoinRule()
|
||||||
|
if ruleErr != nil {
|
||||||
|
util.GetLogger(ctx).WithError(ruleErr).WithField("parent_room_id", parentRoomID).Warn("failed to get join rule")
|
||||||
|
} else if rule == spec.Public || rule == spec.Knock {
|
||||||
|
allowed = true
|
||||||
|
} else if rule == spec.Restricted {
|
||||||
|
allowedRoomIDs := restrictedJoinRuleAllowedRooms(ctx, joinRuleEv, "m.room_membership")
|
||||||
|
// check parent is in the allowed set
|
||||||
|
for _, a := range allowedRoomIDs {
|
||||||
|
if *parentRoomID == a {
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allowed {
|
||||||
|
// ensure caller is joined to the parent room
|
||||||
|
var queryRes2 roomserver.QueryCurrentStateResponse
|
||||||
|
err = querier.QueryCurrentState(ctx, &roomserver.QueryCurrentStateRequest{
|
||||||
|
RoomID: parentRoomID.String(),
|
||||||
|
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||||
|
roomMemberTuple,
|
||||||
|
},
|
||||||
|
}, &queryRes2)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).WithField("parent_room_id", parentRoomID).Warn("failed to check user is joined to parent room")
|
||||||
|
} else {
|
||||||
|
memberEv = queryRes2.StateEvents[roomMemberTuple]
|
||||||
|
if memberEv != nil {
|
||||||
|
membership, _ := memberEv.Membership()
|
||||||
|
if membership == spec.Join {
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateEvent(ctx context.Context, querier *Queryer, roomID spec.RoomID, evType, stateKey string) *types.HeaderedEvent {
|
||||||
var queryRes roomserver.QueryCurrentStateResponse
|
var queryRes roomserver.QueryCurrentStateResponse
|
||||||
tuple := gomatrixserverlib.StateKeyTuple{
|
tuple := gomatrixserverlib.StateKeyTuple{
|
||||||
EventType: evType,
|
EventType: evType,
|
||||||
StateKey: stateKey,
|
StateKey: stateKey,
|
||||||
}
|
}
|
||||||
err := w.rsAPI.QueryCurrentState(w.ctx, &roomserver.QueryCurrentStateRequest{
|
err := querier.QueryCurrentState(ctx, &roomserver.QueryCurrentStateRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID.String(),
|
||||||
StateTuples: []gomatrixserverlib.StateKeyTuple{tuple},
|
StateTuples: []gomatrixserverlib.StateKeyTuple{tuple},
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -245,14 +358,14 @@ func (w *RoomHierarchyWalker) stateEvent(roomID, evType, stateKey string) *types
|
||||||
return queryRes.StateEvents[tuple]
|
return queryRes.StateEvents[tuple]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *RoomHierarchyWalker) roomExists(roomID string) bool {
|
func roomExists(ctx context.Context, querier *Queryer, roomID spec.RoomID) bool {
|
||||||
var queryRes roomserver.QueryServerJoinedToRoomResponse
|
var queryRes roomserver.QueryServerJoinedToRoomResponse
|
||||||
err := w.rsAPI.QueryServerJoinedToRoom(w.ctx, &roomserver.QueryServerJoinedToRoomRequest{
|
err := querier.QueryServerJoinedToRoom(ctx, &roomserver.QueryServerJoinedToRoomRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID.String(),
|
||||||
ServerName: w.thisServer,
|
ServerName: querier.Cfg.Global.ServerName,
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryServerJoinedToRoom")
|
util.GetLogger(ctx).WithError(err).Error("failed to QueryServerJoinedToRoom")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if the room exists but we aren't in the room then we might have stale data so we want to fetch
|
// if the room exists but we aren't in the room then we might have stale data so we want to fetch
|
||||||
|
|
@ -262,26 +375,26 @@ func (w *RoomHierarchyWalker) roomExists(roomID string) bool {
|
||||||
|
|
||||||
// federatedRoomInfo returns more of the spaces graph from another server. Returns nil if this was
|
// federatedRoomInfo returns more of the spaces graph from another server. Returns nil if this was
|
||||||
// unsuccessful.
|
// unsuccessful.
|
||||||
func (w *RoomHierarchyWalker) federatedRoomInfo(roomID string, vias []string) *fclient.MSC2946SpacesResponse {
|
func federatedRoomInfo(ctx context.Context, querier *Queryer, caller types.DeviceOrServerName, suggestedOnly bool, roomID spec.RoomID, vias []string) *fclient.MSC2946SpacesResponse {
|
||||||
// only do federated requests for client requests
|
// only do federated requests for client requests
|
||||||
if w.caller.Device() == nil {
|
if caller.Device() == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
resp, ok := w.roomHierarchyCache.GetRoomHierarchy(roomID)
|
resp, ok := querier.Cache.GetRoomHierarchy(roomID.String())
|
||||||
if ok {
|
if ok {
|
||||||
util.GetLogger(w.ctx).Debugf("Returning cached response for %s", roomID)
|
util.GetLogger(ctx).Debugf("Returning cached response for %s", roomID)
|
||||||
return &resp
|
return &resp
|
||||||
}
|
}
|
||||||
util.GetLogger(w.ctx).Debugf("Querying %s via %+v", roomID, vias)
|
util.GetLogger(ctx).Debugf("Querying %s via %+v", roomID, vias)
|
||||||
ctx := context.Background()
|
innerCtx := context.Background()
|
||||||
// query more of the spaces graph using these servers
|
// query more of the spaces graph using these servers
|
||||||
for _, serverName := range vias {
|
for _, serverName := range vias {
|
||||||
if serverName == string(w.thisServer) {
|
if serverName == string(querier.Cfg.Global.ServerName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res, err := w.fsAPI.RoomHierarchies(ctx, w.thisServer, spec.ServerName(serverName), roomID, w.suggestedOnly)
|
res, err := querier.FSAPI.RoomHierarchies(innerCtx, querier.Cfg.Global.ServerName, spec.ServerName(serverName), roomID.String(), suggestedOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(w.ctx).WithError(err).Warnf("failed to call RoomHierarchies on server %s", serverName)
|
util.GetLogger(ctx).WithError(err).Warnf("failed to call RoomHierarchies on server %s", serverName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// ensure nil slices are empty as we send this to the client sometimes
|
// ensure nil slices are empty as we send this to the client sometimes
|
||||||
|
|
@ -295,7 +408,7 @@ func (w *RoomHierarchyWalker) federatedRoomInfo(roomID string, vias []string) *f
|
||||||
}
|
}
|
||||||
res.Children[i] = child
|
res.Children[i] = child
|
||||||
}
|
}
|
||||||
w.roomHierarchyCache.StoreRoomHierarchy(roomID, res)
|
querier.Cache.StoreRoomHierarchy(roomID.String(), res)
|
||||||
|
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
|
|
@ -303,14 +416,14 @@ func (w *RoomHierarchyWalker) federatedRoomInfo(roomID string, vias []string) *f
|
||||||
}
|
}
|
||||||
|
|
||||||
// references returns all child references pointing to or from this room.
|
// references returns all child references pointing to or from this room.
|
||||||
func (w *RoomHierarchyWalker) childReferences(roomID string) ([]fclient.MSC2946StrippedEvent, error) {
|
func childReferences(querier *Queryer, suggestedOnly bool, roomID spec.RoomID) ([]fclient.MSC2946StrippedEvent, error) {
|
||||||
createTuple := gomatrixserverlib.StateKeyTuple{
|
createTuple := gomatrixserverlib.StateKeyTuple{
|
||||||
EventType: spec.MRoomCreate,
|
EventType: spec.MRoomCreate,
|
||||||
StateKey: "",
|
StateKey: "",
|
||||||
}
|
}
|
||||||
var res roomserver.QueryCurrentStateResponse
|
var res roomserver.QueryCurrentStateResponse
|
||||||
err := w.rsAPI.QueryCurrentState(context.Background(), &roomserver.QueryCurrentStateRequest{
|
err := querier.QueryCurrentState(context.Background(), &roomserver.QueryCurrentStateRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID.String(),
|
||||||
AllowWildcards: true,
|
AllowWildcards: true,
|
||||||
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
||||||
createTuple, {
|
createTuple, {
|
||||||
|
|
@ -346,7 +459,7 @@ func (w *RoomHierarchyWalker) childReferences(roomID string) ([]fclient.MSC2946S
|
||||||
}
|
}
|
||||||
// if suggested only and this child isn't suggested, skip it.
|
// if suggested only and this child isn't suggested, skip it.
|
||||||
// if suggested only = false we include everything so don't need to check the content.
|
// if suggested only = false we include everything so don't need to check the content.
|
||||||
if w.suggestedOnly && !content.Get("suggested").Bool() {
|
if suggestedOnly && !content.Get("suggested").Bool() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
el = append(el, *strip)
|
el = append(el, *strip)
|
||||||
|
|
@ -360,174 +473,10 @@ func (w *RoomHierarchyWalker) childReferences(roomID string) ([]fclient.MSC2946S
|
||||||
return el, nil
|
return el, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// authorised returns true iff the user is joined this room or the room is world_readable
|
func publicRoomsChunk(ctx context.Context, querier *Queryer, roomID spec.RoomID) *fclient.PublicRoom {
|
||||||
func (w *RoomHierarchyWalker) authorised(roomID, parentRoomID string) (authed, isJoinedOrInvited bool) {
|
pubRooms, err := roomserver.PopulatePublicRooms(ctx, []string{roomID.String()}, querier)
|
||||||
if clientCaller := w.caller.Device(); clientCaller != nil {
|
|
||||||
return w.authorisedUser(roomID, clientCaller, parentRoomID)
|
|
||||||
} else {
|
|
||||||
return w.authorisedServer(roomID, *w.caller.ServerName()), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorisedServer returns true iff the server is joined this room or the room is world_readable, public, or knockable
|
|
||||||
func (w *RoomHierarchyWalker) authorisedServer(roomID string, callerServerName spec.ServerName) bool {
|
|
||||||
// Check history visibility / join rules first
|
|
||||||
hisVisTuple := gomatrixserverlib.StateKeyTuple{
|
|
||||||
EventType: spec.MRoomHistoryVisibility,
|
|
||||||
StateKey: "",
|
|
||||||
}
|
|
||||||
joinRuleTuple := gomatrixserverlib.StateKeyTuple{
|
|
||||||
EventType: spec.MRoomJoinRules,
|
|
||||||
StateKey: "",
|
|
||||||
}
|
|
||||||
var queryRoomRes roomserver.QueryCurrentStateResponse
|
|
||||||
err := w.rsAPI.QueryCurrentState(w.ctx, &roomserver.QueryCurrentStateRequest{
|
|
||||||
RoomID: roomID,
|
|
||||||
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
|
||||||
hisVisTuple, joinRuleTuple,
|
|
||||||
},
|
|
||||||
}, &queryRoomRes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryCurrentState")
|
util.GetLogger(ctx).WithError(err).Error("failed to PopulatePublicRooms")
|
||||||
return false
|
|
||||||
}
|
|
||||||
hisVisEv := queryRoomRes.StateEvents[hisVisTuple]
|
|
||||||
if hisVisEv != nil {
|
|
||||||
hisVis, _ := hisVisEv.HistoryVisibility()
|
|
||||||
if hisVis == "world_readable" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if this room is a restricted room and if so, we need to check if the server is joined to an allowed room ID
|
|
||||||
// in addition to the actual room ID (but always do the actual one first as it's quicker in the common case)
|
|
||||||
allowJoinedToRoomIDs := []string{roomID}
|
|
||||||
joinRuleEv := queryRoomRes.StateEvents[joinRuleTuple]
|
|
||||||
|
|
||||||
if joinRuleEv != nil {
|
|
||||||
rule, ruleErr := joinRuleEv.JoinRule()
|
|
||||||
if ruleErr != nil {
|
|
||||||
util.GetLogger(w.ctx).WithError(ruleErr).WithField("parent_room_id", roomID).Warn("failed to get join rule")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule == spec.Public || rule == spec.Knock {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule == spec.Restricted {
|
|
||||||
allowJoinedToRoomIDs = append(allowJoinedToRoomIDs, w.restrictedJoinRuleAllowedRooms(joinRuleEv, "m.room_membership")...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if server is joined to any allowed room
|
|
||||||
for _, allowedRoomID := range allowJoinedToRoomIDs {
|
|
||||||
var queryRes fs.QueryJoinedHostServerNamesInRoomResponse
|
|
||||||
err = w.fsAPI.QueryJoinedHostServerNamesInRoom(w.ctx, &fs.QueryJoinedHostServerNamesInRoomRequest{
|
|
||||||
RoomID: allowedRoomID,
|
|
||||||
}, &queryRes)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryJoinedHostServerNamesInRoom")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, srv := range queryRes.ServerNames {
|
|
||||||
if srv == callerServerName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// authorisedUser returns true iff the user is invited/joined this room or the room is world_readable
|
|
||||||
// or if the room has a public or knock join rule.
|
|
||||||
// Failing that, if the room has a restricted join rule and belongs to the space parent listed, it will return true.
|
|
||||||
func (w *RoomHierarchyWalker) authorisedUser(roomID string, clientCaller *userapi.Device, parentRoomID string) (authed bool, isJoinedOrInvited bool) {
|
|
||||||
hisVisTuple := gomatrixserverlib.StateKeyTuple{
|
|
||||||
EventType: spec.MRoomHistoryVisibility,
|
|
||||||
StateKey: "",
|
|
||||||
}
|
|
||||||
joinRuleTuple := gomatrixserverlib.StateKeyTuple{
|
|
||||||
EventType: spec.MRoomJoinRules,
|
|
||||||
StateKey: "",
|
|
||||||
}
|
|
||||||
roomMemberTuple := gomatrixserverlib.StateKeyTuple{
|
|
||||||
EventType: spec.MRoomMember,
|
|
||||||
StateKey: clientCaller.UserID,
|
|
||||||
}
|
|
||||||
var queryRes roomserver.QueryCurrentStateResponse
|
|
||||||
err := w.rsAPI.QueryCurrentState(w.ctx, &roomserver.QueryCurrentStateRequest{
|
|
||||||
RoomID: roomID,
|
|
||||||
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
|
||||||
hisVisTuple, joinRuleTuple, roomMemberTuple,
|
|
||||||
},
|
|
||||||
}, &queryRes)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(w.ctx).WithError(err).Error("failed to QueryCurrentState")
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
memberEv := queryRes.StateEvents[roomMemberTuple]
|
|
||||||
if memberEv != nil {
|
|
||||||
membership, _ := memberEv.Membership()
|
|
||||||
if membership == spec.Join || membership == spec.Invite {
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hisVisEv := queryRes.StateEvents[hisVisTuple]
|
|
||||||
if hisVisEv != nil {
|
|
||||||
hisVis, _ := hisVisEv.HistoryVisibility()
|
|
||||||
if hisVis == "world_readable" {
|
|
||||||
return true, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
joinRuleEv := queryRes.StateEvents[joinRuleTuple]
|
|
||||||
if parentRoomID != "" && joinRuleEv != nil {
|
|
||||||
var allowed bool
|
|
||||||
rule, ruleErr := joinRuleEv.JoinRule()
|
|
||||||
if ruleErr != nil {
|
|
||||||
util.GetLogger(w.ctx).WithError(ruleErr).WithField("parent_room_id", parentRoomID).Warn("failed to get join rule")
|
|
||||||
} else if rule == spec.Public || rule == spec.Knock {
|
|
||||||
allowed = true
|
|
||||||
} else if rule == spec.Restricted {
|
|
||||||
allowedRoomIDs := w.restrictedJoinRuleAllowedRooms(joinRuleEv, "m.room_membership")
|
|
||||||
// check parent is in the allowed set
|
|
||||||
for _, a := range allowedRoomIDs {
|
|
||||||
if parentRoomID == a {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if allowed {
|
|
||||||
// ensure caller is joined to the parent room
|
|
||||||
var queryRes2 roomserver.QueryCurrentStateResponse
|
|
||||||
err = w.rsAPI.QueryCurrentState(w.ctx, &roomserver.QueryCurrentStateRequest{
|
|
||||||
RoomID: parentRoomID,
|
|
||||||
StateTuples: []gomatrixserverlib.StateKeyTuple{
|
|
||||||
roomMemberTuple,
|
|
||||||
},
|
|
||||||
}, &queryRes2)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(w.ctx).WithError(err).WithField("parent_room_id", parentRoomID).Warn("failed to check user is joined to parent room")
|
|
||||||
} else {
|
|
||||||
memberEv = queryRes2.StateEvents[roomMemberTuple]
|
|
||||||
if memberEv != nil {
|
|
||||||
membership, _ := memberEv.Membership()
|
|
||||||
if membership == spec.Join {
|
|
||||||
return true, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *RoomHierarchyWalker) publicRoomsChunk(roomID string) *fclient.PublicRoom {
|
|
||||||
pubRooms, err := roomserver.PopulatePublicRooms(w.ctx, []string{roomID}, w.rsAPI)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(w.ctx).WithError(err).Error("failed to PopulatePublicRooms")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(pubRooms) == 0 {
|
if len(pubRooms) == 0 {
|
||||||
|
|
@ -536,13 +485,6 @@ func (w *RoomHierarchyWalker) publicRoomsChunk(roomID string) *fclient.PublicRoo
|
||||||
return &pubRooms[0]
|
return &pubRooms[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
type roomVisit struct {
|
|
||||||
roomID string
|
|
||||||
parentRoomID string
|
|
||||||
depth int
|
|
||||||
vias []string // vias to query this room by
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripped(ev gomatrixserverlib.PDU) *fclient.MSC2946StrippedEvent {
|
func stripped(ev gomatrixserverlib.PDU) *fclient.MSC2946StrippedEvent {
|
||||||
if ev.StateKey() == nil {
|
if ev.StateKey() == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -556,61 +498,25 @@ func stripped(ev gomatrixserverlib.PDU) *fclient.MSC2946StrippedEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *RoomHierarchyWalker) restrictedJoinRuleAllowedRooms(joinRuleEv *types.HeaderedEvent, allowType string) (allows []string) {
|
func restrictedJoinRuleAllowedRooms(ctx context.Context, joinRuleEv *types.HeaderedEvent, allowType string) (allows []spec.RoomID) {
|
||||||
rule, _ := joinRuleEv.JoinRule()
|
rule, _ := joinRuleEv.JoinRule()
|
||||||
if rule != spec.Restricted {
|
if rule != spec.Restricted {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var jrContent gomatrixserverlib.JoinRuleContent
|
var jrContent gomatrixserverlib.JoinRuleContent
|
||||||
if err := json.Unmarshal(joinRuleEv.Content(), &jrContent); err != nil {
|
if err := json.Unmarshal(joinRuleEv.Content(), &jrContent); err != nil {
|
||||||
util.GetLogger(w.ctx).Warnf("failed to check join_rule on room %s: %s", joinRuleEv.RoomID(), err)
|
util.GetLogger(ctx).Warnf("failed to check join_rule on room %s: %s", joinRuleEv.RoomID(), err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, allow := range jrContent.Allow {
|
for _, allow := range jrContent.Allow {
|
||||||
if allow.Type == allowType {
|
if allow.Type == allowType {
|
||||||
allows = append(allows, allow.RoomID)
|
allowedRoomID, err := spec.NewRoomID(allow.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).Warnf("invalid room ID '%s' found in join_rule on room %s: %s", allow.RoomID, joinRuleEv.RoomID(), err)
|
||||||
|
} else {
|
||||||
|
allows = append(allows, *allowedRoomID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stripped down version of RoomHierarchyWalker suitable for caching (For pagination purposes)
|
|
||||||
//
|
|
||||||
// TODO remove more stuff
|
|
||||||
type CachedRoomHierarchyWalker struct {
|
|
||||||
rootRoomID string
|
|
||||||
caller types.DeviceOrServerName
|
|
||||||
thisServer spec.ServerName
|
|
||||||
rsAPI *Queryer
|
|
||||||
fsAPI fs.RoomserverFederationAPI
|
|
||||||
ctx context.Context
|
|
||||||
cache caching.RoomHierarchyCache
|
|
||||||
suggestedOnly bool
|
|
||||||
maxDepth int
|
|
||||||
|
|
||||||
processed stringSet
|
|
||||||
unvisited []roomVisit
|
|
||||||
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CachedRoomHierarchyWalker) GetWalker() roomserver.RoomHierarchyWalker {
|
|
||||||
return &RoomHierarchyWalker{
|
|
||||||
rootRoomID: c.rootRoomID,
|
|
||||||
caller: c.caller,
|
|
||||||
thisServer: c.thisServer,
|
|
||||||
rsAPI: c.rsAPI,
|
|
||||||
fsAPI: c.fsAPI,
|
|
||||||
ctx: c.ctx,
|
|
||||||
roomHierarchyCache: c.cache,
|
|
||||||
suggestedOnly: c.suggestedOnly,
|
|
||||||
maxDepth: c.maxDepth,
|
|
||||||
processed: c.processed,
|
|
||||||
unvisited: c.unvisited,
|
|
||||||
done: c.done,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CachedRoomHierarchyWalker) ValidateParams(suggestedOnly bool, maxDepth int) bool {
|
|
||||||
return c.suggestedOnly == suggestedOnly && c.maxDepth == maxDepth
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue