mirror of
https://github.com/hoernschen/dendrite.git
synced 2024-12-26 15:08:28 +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
|
||||
|
||||
// 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(
|
||||
serverName gomatrixserverlib.ServerName,
|
||||
serverCurrentlyInRoom bool,
|
||||
|
|
|
@ -17,13 +17,153 @@ package internal
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"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
|
||||
// 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(
|
||||
ctx context.Context, userID string, events []gomatrixserverlib.HeaderedEvent,
|
||||
ctx context.Context, rsAPI queryState, userID string, events []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 = 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.
|
||||
clientEvents = gomatrixserverlib.HeaderedToClientEvents(events, gomatrixserverlib.FormatAll)
|
||||
|
|
Loading…
Reference in a new issue