mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-31 13:22:46 +00:00
Implement history visibility on /messages
, /context
, /sync
(#2511)
* Add possibility to set history_visibility and user AccountType * Add new DB queries * Add actual history_visibility changes for /messages * Add passing tests * Extract check function * Cleanup * Cleanup * Fix build on 386 * Move ApplyHistoryVisibilityFilter to internal * Move queries to topology table * Add filtering to /sync and /context Some cleanup * Add passing tests; Remove failing tests :( * Re-add passing tests * Move filtering to own function to avoid duplication * Re-add passing test * Use newly added GMSL HistoryVisibility * Update gomatrixserverlib * Set the visibility when creating events * Default to shared history visibility * Remove unused query * Update history visibility checks to use gmsl Update tests * Remove unused statement * Update migrations to set "correct" history visibility * Add method to fetch the membership at a given event * Tweaks and logging * Use actual internal rsAPI, default to shared visibility in tests * Revert "Move queries to topology table" This reverts commit 4f0d41be9c194a46379796435ce73e79203edbd6. * Remove noise/unneeded code * More cleanup * Try to optimize database requests * Fix imports * PR peview fixes/changes * Move setting history visibility to own migration, be more restrictive * Fix unit tests * Lint * Fix missing entries * Tweaks for incremental syncs * Adapt generic changes Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com> Co-authored-by: kegsay <kegan@matrix.org>
This commit is contained in:
parent
371336c6b5
commit
05cafbd197
31 changed files with 1043 additions and 224 deletions
|
@ -21,10 +21,12 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
roomserver "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/syncapi/internal"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
|
@ -95,24 +97,6 @@ func Context(
|
|||
ContainsURL: filter.ContainsURL,
|
||||
}
|
||||
|
||||
// TODO: Get the actual state at the last event returned by SelectContextAfterEvent
|
||||
state, _ := syncDB.CurrentState(ctx, roomID, &stateFilter, nil)
|
||||
// verify the user is allowed to see the context for this room/event
|
||||
for _, x := range state {
|
||||
var hisVis gomatrixserverlib.HistoryVisibility
|
||||
hisVis, err = x.HistoryVisibility()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
allowed := hisVis == gomatrixserverlib.WorldReadable || membershipRes.Membership == gomatrixserverlib.Join
|
||||
if !allowed {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("User is not allowed to query context"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id, requestedEvent, err := syncDB.SelectContextEvent(ctx, roomID, eventID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -125,6 +109,24 @@ func Context(
|
|||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
// verify the user is allowed to see the context for this room/event
|
||||
startTime := time.Now()
|
||||
filteredEvents, err := internal.ApplyHistoryVisibilityFilter(ctx, syncDB, rsAPI, []*gomatrixserverlib.HeaderedEvent{&requestedEvent}, nil, device.UserID, "context")
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("unable to apply history visibility filter")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"duration": time.Since(startTime),
|
||||
"room_id": roomID,
|
||||
}).Debug("applied history visibility (context)")
|
||||
if len(filteredEvents) == 0 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("User is not allowed to query context"),
|
||||
}
|
||||
}
|
||||
|
||||
eventsBefore, err := syncDB.SelectContextBeforeEvent(ctx, id, roomID, filter)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
logrus.WithError(err).Error("unable to fetch before events")
|
||||
|
@ -137,8 +139,27 @@ func Context(
|
|||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
eventsBeforeClient := gomatrixserverlib.HeaderedToClientEvents(eventsBefore, gomatrixserverlib.FormatAll)
|
||||
eventsAfterClient := gomatrixserverlib.HeaderedToClientEvents(eventsAfter, gomatrixserverlib.FormatAll)
|
||||
startTime = time.Now()
|
||||
eventsBeforeFiltered, eventsAfterFiltered, err := applyHistoryVisibilityOnContextEvents(ctx, syncDB, rsAPI, eventsBefore, eventsAfter, device.UserID)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("unable to apply history visibility filter")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"duration": time.Since(startTime),
|
||||
"room_id": roomID,
|
||||
}).Debug("applied history visibility (context eventsBefore/eventsAfter)")
|
||||
|
||||
// TODO: Get the actual state at the last event returned by SelectContextAfterEvent
|
||||
state, err := syncDB.CurrentState(ctx, roomID, &stateFilter, nil)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("unable to fetch current room state")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
eventsBeforeClient := gomatrixserverlib.HeaderedToClientEvents(eventsBeforeFiltered, gomatrixserverlib.FormatAll)
|
||||
eventsAfterClient := gomatrixserverlib.HeaderedToClientEvents(eventsAfterFiltered, gomatrixserverlib.FormatAll)
|
||||
newState := applyLazyLoadMembers(device, filter, eventsAfterClient, eventsBeforeClient, state, lazyLoadCache)
|
||||
|
||||
response := ContextRespsonse{
|
||||
|
@ -162,6 +183,44 @@ func Context(
|
|||
}
|
||||
}
|
||||
|
||||
// applyHistoryVisibilityOnContextEvents is a helper function to avoid roundtrips to the roomserver
|
||||
// by combining the events before and after the context event. Returns the filtered events,
|
||||
// and an error, if any.
|
||||
func applyHistoryVisibilityOnContextEvents(
|
||||
ctx context.Context, syncDB storage.Database, rsAPI roomserver.SyncRoomserverAPI,
|
||||
eventsBefore, eventsAfter []*gomatrixserverlib.HeaderedEvent,
|
||||
userID string,
|
||||
) (filteredBefore, filteredAfter []*gomatrixserverlib.HeaderedEvent, err error) {
|
||||
eventIDsBefore := make(map[string]struct{}, len(eventsBefore))
|
||||
eventIDsAfter := make(map[string]struct{}, len(eventsAfter))
|
||||
|
||||
// Remember before/after eventIDs, so we can restore them
|
||||
// after applying history visibility checks
|
||||
for _, ev := range eventsBefore {
|
||||
eventIDsBefore[ev.EventID()] = struct{}{}
|
||||
}
|
||||
for _, ev := range eventsAfter {
|
||||
eventIDsAfter[ev.EventID()] = struct{}{}
|
||||
}
|
||||
|
||||
allEvents := append(eventsBefore, eventsAfter...)
|
||||
filteredEvents, err := internal.ApplyHistoryVisibilityFilter(ctx, syncDB, rsAPI, allEvents, nil, userID, "context")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// "Restore" events in the correct context
|
||||
for _, ev := range filteredEvents {
|
||||
if _, ok := eventIDsBefore[ev.EventID()]; ok {
|
||||
filteredBefore = append(filteredBefore, ev)
|
||||
}
|
||||
if _, ok := eventIDsAfter[ev.EventID()]; ok {
|
||||
filteredAfter = append(filteredAfter, ev)
|
||||
}
|
||||
}
|
||||
return filteredBefore, filteredAfter, nil
|
||||
}
|
||||
|
||||
func getStartEnd(ctx context.Context, syncDB storage.Database, startEvents, endEvents []*gomatrixserverlib.HeaderedEvent) (start, end types.TopologyToken, err error) {
|
||||
if len(startEvents) > 0 {
|
||||
start, err = syncDB.EventPositionInTopology(ctx, startEvents[0].EventID())
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
@ -28,6 +29,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/syncapi/internal"
|
||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
|
@ -324,6 +326,9 @@ func (r *messagesReq) retrieveEvents() (
|
|||
// reliable way to define it), it would be easier and less troublesome to
|
||||
// only have to change it in one place, i.e. the database.
|
||||
start, end, err = r.getStartEnd(events)
|
||||
if err != nil {
|
||||
return []gomatrixserverlib.ClientEvent{}, *r.from, *r.to, err
|
||||
}
|
||||
|
||||
// Sort the events to ensure we send them in the right order.
|
||||
if r.backwardOrdering {
|
||||
|
@ -337,97 +342,18 @@ func (r *messagesReq) retrieveEvents() (
|
|||
}
|
||||
events = reversed(events)
|
||||
}
|
||||
events = r.filterHistoryVisible(events)
|
||||
if len(events) == 0 {
|
||||
return []gomatrixserverlib.ClientEvent{}, *r.from, *r.to, nil
|
||||
}
|
||||
|
||||
// Convert all of the events into client events.
|
||||
clientEvents = gomatrixserverlib.HeaderedToClientEvents(events, gomatrixserverlib.FormatAll)
|
||||
return clientEvents, start, end, err
|
||||
}
|
||||
|
||||
func (r *messagesReq) filterHistoryVisible(events []*gomatrixserverlib.HeaderedEvent) []*gomatrixserverlib.HeaderedEvent {
|
||||
// TODO FIXME: We don't fully implement history visibility yet. To avoid leaking events which the
|
||||
// user shouldn't see, we check the recent events and remove any prior to the join event of the user
|
||||
// which is equiv to history_visibility: joined
|
||||
joinEventIndex := -1
|
||||
for i, ev := range events {
|
||||
if ev.Type() == gomatrixserverlib.MRoomMember && ev.StateKeyEquals(r.device.UserID) {
|
||||
membership, _ := ev.Membership()
|
||||
if membership == "join" {
|
||||
joinEventIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result []*gomatrixserverlib.HeaderedEvent
|
||||
var eventsToCheck []*gomatrixserverlib.HeaderedEvent
|
||||
if joinEventIndex != -1 {
|
||||
if r.backwardOrdering {
|
||||
result = events[:joinEventIndex+1]
|
||||
eventsToCheck = append(eventsToCheck, result[0])
|
||||
} else {
|
||||
result = events[joinEventIndex:]
|
||||
eventsToCheck = append(eventsToCheck, result[len(result)-1])
|
||||
}
|
||||
} else {
|
||||
eventsToCheck = []*gomatrixserverlib.HeaderedEvent{events[0], events[len(events)-1]}
|
||||
result = events
|
||||
}
|
||||
// make sure the user was in the room for both the earliest and latest events, we need this because
|
||||
// some backpagination results will not have the join event (e.g if they hit /messages at the join event itself)
|
||||
wasJoined := true
|
||||
for _, ev := range eventsToCheck {
|
||||
var queryRes api.QueryStateAfterEventsResponse
|
||||
err := r.rsAPI.QueryStateAfterEvents(r.ctx, &api.QueryStateAfterEventsRequest{
|
||||
RoomID: ev.RoomID(),
|
||||
PrevEventIDs: ev.PrevEventIDs(),
|
||||
StateToFetch: []gomatrixserverlib.StateKeyTuple{
|
||||
{EventType: gomatrixserverlib.MRoomMember, StateKey: r.device.UserID},
|
||||
{EventType: gomatrixserverlib.MRoomHistoryVisibility, StateKey: ""},
|
||||
},
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
wasJoined = false
|
||||
break
|
||||
}
|
||||
var hisVisEvent, membershipEvent *gomatrixserverlib.HeaderedEvent
|
||||
for i := range queryRes.StateEvents {
|
||||
switch queryRes.StateEvents[i].Type() {
|
||||
case gomatrixserverlib.MRoomMember:
|
||||
membershipEvent = queryRes.StateEvents[i]
|
||||
case gomatrixserverlib.MRoomHistoryVisibility:
|
||||
hisVisEvent = queryRes.StateEvents[i]
|
||||
}
|
||||
}
|
||||
if hisVisEvent == nil {
|
||||
return events // apply no filtering as it defaults to Shared.
|
||||
}
|
||||
hisVis, _ := hisVisEvent.HistoryVisibility()
|
||||
if hisVis == "shared" || hisVis == "world_readable" {
|
||||
return events // apply no filtering
|
||||
}
|
||||
if membershipEvent == nil {
|
||||
wasJoined = false
|
||||
break
|
||||
}
|
||||
membership, err := membershipEvent.Membership()
|
||||
if err != nil {
|
||||
wasJoined = false
|
||||
break
|
||||
}
|
||||
if membership != "join" {
|
||||
wasJoined = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !wasJoined {
|
||||
util.GetLogger(r.ctx).WithField("num_events", len(events)).Warnf("%s was not joined to room during these events, omitting them", r.device.UserID)
|
||||
return []*gomatrixserverlib.HeaderedEvent{}
|
||||
}
|
||||
return result
|
||||
// Apply room history visibility filter
|
||||
startTime := time.Now()
|
||||
filteredEvents, err := internal.ApplyHistoryVisibilityFilter(r.ctx, r.db, r.rsAPI, events, nil, r.device.UserID, "messages")
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"duration": time.Since(startTime),
|
||||
"room_id": r.roomID,
|
||||
}).Debug("applied history visibility (messages)")
|
||||
return gomatrixserverlib.HeaderedToClientEvents(filteredEvents, gomatrixserverlib.FormatAll), start, end, err
|
||||
}
|
||||
|
||||
func (r *messagesReq) getStartEnd(events []*gomatrixserverlib.HeaderedEvent) (start, end types.TopologyToken, err error) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue