mirror of
https://github.com/hoernschen/dendrite.git
synced 2024-12-27 23:48:27 +00:00
Implement history visibility checks for /messages
This commit is contained in:
parent
c8dd962505
commit
5d9c10cb8f
3 changed files with 149 additions and 4 deletions
|
@ -26,7 +26,8 @@ const (
|
||||||
// TODO: This logic should live in gomatrixserverlib
|
// TODO: This logic should live in gomatrixserverlib
|
||||||
|
|
||||||
// IsServerAllowed returns true if the server is allowed to see events in the room
|
// IsServerAllowed returns true if the server is allowed to see events in the room
|
||||||
// at this particular state. This function implements https://matrix.org/docs/spec/client_server/r0.6.0#id87
|
// at this particular state. This function implements a server-based version of
|
||||||
|
// https://matrix.org/docs/spec/client_server/r0.6.0#id87
|
||||||
func IsServerAllowed(
|
func IsServerAllowed(
|
||||||
serverName gomatrixserverlib.ServerName,
|
serverName gomatrixserverlib.ServerName,
|
||||||
serverCurrentlyInRoom bool,
|
serverCurrentlyInRoom bool,
|
||||||
|
|
|
@ -17,13 +17,153 @@ package internal
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type queryState interface {
|
||||||
|
QueryStateAfterEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
request *api.QueryStateAfterEventsRequest,
|
||||||
|
response *api.QueryStateAfterEventsResponse,
|
||||||
|
) error
|
||||||
|
QueryMembershipForUser(
|
||||||
|
ctx context.Context,
|
||||||
|
request *api.QueryMembershipForUserRequest,
|
||||||
|
response *api.QueryMembershipForUserResponse,
|
||||||
|
) error
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyHistoryVisibilityChecks removes items from the input slice if the user is not allowed
|
// ApplyHistoryVisibilityChecks removes items from the input slice if the user is not allowed
|
||||||
// to see these events.
|
// to see these events.
|
||||||
|
//
|
||||||
|
// This works by using QueryStateAfterEvents to pull out the history visibility and membership
|
||||||
|
// events for the user at the time of the each input event, then applying the checks detailled
|
||||||
|
// at https://matrix.org/docs/spec/client_server/r0.6.0#id87
|
||||||
func ApplyHistoryVisibilityChecks(
|
func ApplyHistoryVisibilityChecks(
|
||||||
ctx context.Context, userID string, events []gomatrixserverlib.HeaderedEvent,
|
ctx context.Context, rsAPI queryState, userID string, events []gomatrixserverlib.HeaderedEvent,
|
||||||
) []gomatrixserverlib.HeaderedEvent {
|
) []gomatrixserverlib.HeaderedEvent {
|
||||||
return events
|
result := make([]gomatrixserverlib.HeaderedEvent, 0, len(events))
|
||||||
|
currentMemberships := make(map[string]*api.QueryMembershipForUserResponse) // room_id => membership
|
||||||
|
for _, ev := range events {
|
||||||
|
queryMembership := currentMemberships[ev.RoomID()]
|
||||||
|
if queryMembership == nil {
|
||||||
|
var queryRes api.QueryMembershipForUserResponse
|
||||||
|
// discard errors, we may not actually need to know their *CURRENT* membership to allow
|
||||||
|
// them access, so let's continue rather than giving up.
|
||||||
|
_ = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{
|
||||||
|
RoomID: ev.RoomID(),
|
||||||
|
UserID: userID,
|
||||||
|
}, &queryRes)
|
||||||
|
currentMemberships[ev.RoomID()] = &queryRes
|
||||||
|
queryMembership = &queryRes
|
||||||
|
}
|
||||||
|
currentMembership := "leave"
|
||||||
|
if queryMembership != nil {
|
||||||
|
currentMembership = queryMembership.Membership
|
||||||
|
}
|
||||||
|
if userAllowedToSeeEvent(ctx, rsAPI, userID, currentMembership, ev) {
|
||||||
|
result = append(result, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func userAllowedToSeeEvent(ctx context.Context, rsAPI queryState, userID, currentMembership string, ev gomatrixserverlib.HeaderedEvent) bool {
|
||||||
|
logger := util.GetLogger(ctx).WithFields(logrus.Fields{
|
||||||
|
"requester": userID,
|
||||||
|
"room_id": ev.RoomID(),
|
||||||
|
"event_id": ev.EventID(),
|
||||||
|
})
|
||||||
|
var queryRes api.QueryStateAfterEventsResponse
|
||||||
|
err := rsAPI.QueryStateAfterEvents(ctx, &api.QueryStateAfterEventsRequest{
|
||||||
|
RoomID: ev.RoomID(),
|
||||||
|
PrevEventIDs: ev.PrevEventIDs(),
|
||||||
|
StateToFetch: []gomatrixserverlib.StateKeyTuple{
|
||||||
|
{
|
||||||
|
EventType: gomatrixserverlib.MRoomMember,
|
||||||
|
StateKey: userID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EventType: gomatrixserverlib.MRoomHistoryVisibility,
|
||||||
|
StateKey: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, &queryRes)
|
||||||
|
if err != nil {
|
||||||
|
logger.WithError(err).Error("Failed to lookup state of room at event, denying user access to event")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !queryRes.RoomExists {
|
||||||
|
logger.Error("Unknown room, denying user access to event")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !queryRes.PrevEventsExist {
|
||||||
|
logger.Error("Failed to lookup state of room at event: missing prev_events for this event, denying user access to event")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var membershipEvent, hisVisEvent *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 they recently joined the room and are requesting events from a long time ago then we expect no membership event
|
||||||
|
// so default to leave
|
||||||
|
membership := gomatrixserverlib.Leave
|
||||||
|
if membershipEvent != nil {
|
||||||
|
membership, _ = membershipEvent.Membership()
|
||||||
|
}
|
||||||
|
return historyVisibilityCheckForEvent(&ev, membership, currentMembership, hisVisEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements https://matrix.org/docs/spec/client_server/r0.6.0#id87 for clients. Not to be confused with a similar
|
||||||
|
// function in roomserver which is designed for servers.
|
||||||
|
func historyVisibilityCheckForEvent(
|
||||||
|
ev *gomatrixserverlib.HeaderedEvent, membership, currentMembership string, hisVisEvent *gomatrixserverlib.HeaderedEvent,
|
||||||
|
) bool {
|
||||||
|
// By default if no history_visibility is set, or if the value is not understood, the visibility is assumed to be shared.
|
||||||
|
visibility := "shared"
|
||||||
|
knownStates := []string{"invited", "joined", "shared", "world_readable"}
|
||||||
|
if hisVisEvent != nil {
|
||||||
|
// ignore errors as that means "the value is not understood".
|
||||||
|
vis, _ := hisVisEvent.HistoryVisibility()
|
||||||
|
for _, knownVis := range knownStates {
|
||||||
|
if vis == knownVis {
|
||||||
|
visibility = vis
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. If the history_visibility was set to world_readable, allow.
|
||||||
|
if visibility == "world_readable" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 2. If the user's membership was join, allow.
|
||||||
|
if membership == "join" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 3. If history_visibility was set to shared, and the user joined the room at any point after the event was sent, allow.
|
||||||
|
// TODO: This is actually an annoying calculation to do. We happen to know that these checks are for /messages which
|
||||||
|
// is usually called whilst you're a room member, so we cheat a bit here by checking if we are currently joined.
|
||||||
|
// This will miss cases where you are not in the room when the event is sent, then join then leave then hit /messages.
|
||||||
|
// This is a slightly more conservative solution. We could alternatively use QueryMembershipForUserResponse.HasBeenInRoom
|
||||||
|
// but this would mean a left user could see NEW messages in a room if the history visibility was set to 'shared', without
|
||||||
|
// having to join it (which would make many people upset, particularly IRC folks).
|
||||||
|
if visibility == "shared" && (membership == "join" || currentMembership == "join") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. If the user's membership was invite, and the history_visibility was set to invited, allow.
|
||||||
|
if membership == "invite" && visibility == "invited" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,7 +243,11 @@ func (r *messagesReq) retrieveEvents() (
|
||||||
events = reversed(events)
|
events = reversed(events)
|
||||||
}
|
}
|
||||||
|
|
||||||
events = internal.ApplyHistoryVisibilityChecks(r.ctx, r.requester, events)
|
events = internal.ApplyHistoryVisibilityChecks(r.ctx, r.rsAPI, r.requester, events)
|
||||||
|
// If we fitered out all the events from these checks, return early
|
||||||
|
if len(events) == 0 {
|
||||||
|
return []gomatrixserverlib.ClientEvent{}, *r.from, *r.to, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Convert all of the events into client events.
|
// Convert all of the events into client events.
|
||||||
clientEvents = gomatrixserverlib.HeaderedToClientEvents(events, gomatrixserverlib.FormatAll)
|
clientEvents = gomatrixserverlib.HeaderedToClientEvents(events, gomatrixserverlib.FormatAll)
|
||||||
|
|
Loading…
Reference in a new issue