aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKegsay <kegan@matrix.org>2020-07-28 10:09:10 +0100
committerGitHub <noreply@github.com>2020-07-28 10:09:10 +0100
commitc63286713570e1274759db971b15405665fa391a (patch)
tree43d96424d0eeedd83399cfaf2caa9e4b565c49af
parent83f038e12ba0bb727ff89e1d719d105520f7152c (diff)
Modify /state/{eventType}/{stateKey} to return the event at the time the user left (#1222)
* Modify /state/{eventType}/{stateKey} to return the event at the time the user left Or live, depending on their current state. Hopefully fixes some sytests! * Linting * Set HasBeenInRoom * Fix cases for world-readable history visibility * Fix bug in finding the requested state event Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
-rw-r--r--clientapi/routing/routing.go4
-rw-r--r--clientapi/routing/state.go152
-rw-r--r--roomserver/internal/query.go1
-rw-r--r--sytest-whitelist1
4 files changed, 136 insertions, 22 deletions
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index 311f64d1..5724a20c 100644
--- a/clientapi/routing/routing.go
+++ b/clientapi/routing/routing.go
@@ -216,7 +216,7 @@ func Setup(
eventType = eventType[:len(eventType)-1]
}
eventFormat := req.URL.Query().Get("format") == "event"
- return OnIncomingStateTypeRequest(req.Context(), rsAPI, vars["roomID"], eventType, "", eventFormat)
+ return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], eventType, "", eventFormat)
})).Methods(http.MethodGet, http.MethodOptions)
r0mux.Handle("/rooms/{roomID}/state/{type}/{stateKey}", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
@@ -225,7 +225,7 @@ func Setup(
return util.ErrorResponse(err)
}
eventFormat := req.URL.Query().Get("format") == "event"
- return OnIncomingStateTypeRequest(req.Context(), rsAPI, vars["roomID"], vars["type"], vars["stateKey"], eventFormat)
+ return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], vars["type"], vars["stateKey"], eventFormat)
})).Methods(http.MethodGet, http.MethodOptions)
r0mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}",
diff --git a/clientapi/routing/state.go b/clientapi/routing/state.go
index 2ec7a33f..c4f7c4f2 100644
--- a/clientapi/routing/state.go
+++ b/clientapi/routing/state.go
@@ -17,11 +17,13 @@ package routing
import (
"context"
"encoding/json"
+ "fmt"
"net/http"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/syncapi/types"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
log "github.com/sirupsen/logrus"
@@ -99,40 +101,150 @@ func OnIncomingStateRequest(ctx context.Context, rsAPI api.RoomserverInternalAPI
// state to see if there is an event with that type and state key, if there
// is then (by default) we return the content, otherwise a 404.
// If eventFormat=true, sends the whole event else just the content.
-func OnIncomingStateTypeRequest(ctx context.Context, rsAPI api.RoomserverInternalAPI, roomID, evType, stateKey string, eventFormat bool) util.JSONResponse {
- // TODO(#287): Auth request and handle the case where the user has left (where
- // we should return the state at the poin they left)
- util.GetLogger(ctx).WithFields(log.Fields{
- "roomID": roomID,
- "evType": evType,
- "stateKey": stateKey,
- }).Info("Fetching state")
+// nolint:gocyclo
+func OnIncomingStateTypeRequest(
+ ctx context.Context, device *userapi.Device, rsAPI api.RoomserverInternalAPI,
+ roomID, evType, stateKey string, eventFormat bool,
+) util.JSONResponse {
+ var worldReadable bool
+ var wantLatestState bool
- stateReq := api.QueryLatestEventsAndStateRequest{
- RoomID: roomID,
- StateToFetch: []gomatrixserverlib.StateKeyTuple{
- gomatrixserverlib.StateKeyTuple{
- EventType: evType,
- StateKey: stateKey,
- },
+ // Always fetch visibility so that we can work out whether to show
+ // the latest events or the last event from when the user was joined.
+ // Then include the requested event type and state key, assuming it
+ // isn't for the same.
+ stateToFetch := []gomatrixserverlib.StateKeyTuple{
+ {
+ EventType: evType,
+ StateKey: stateKey,
},
}
- stateRes := api.QueryLatestEventsAndStateResponse{}
+ if evType != gomatrixserverlib.MRoomHistoryVisibility && stateKey != "" {
+ stateToFetch = append(stateToFetch, gomatrixserverlib.StateKeyTuple{
+ EventType: gomatrixserverlib.MRoomHistoryVisibility,
+ StateKey: "",
+ })
+ }
- if err := rsAPI.QueryLatestEventsAndState(ctx, &stateReq, &stateRes); err != nil {
+ // First of all, get the latest state of the room. We need to do this
+ // so that we can look at the history visibility of the room. If the
+ // room is world-readable then we will always return the latest state.
+ stateRes := api.QueryLatestEventsAndStateResponse{}
+ if err := rsAPI.QueryLatestEventsAndState(ctx, &api.QueryLatestEventsAndStateRequest{
+ RoomID: roomID,
+ StateToFetch: stateToFetch,
+ }, &stateRes); err != nil {
util.GetLogger(ctx).WithError(err).Error("queryAPI.QueryLatestEventsAndState failed")
return jsonerror.InternalServerError()
}
- if len(stateRes.StateEvents) == 0 {
+ // Look at the room state and see if we have a history visibility event
+ // that marks the room as world-readable. If we don't then we assume that
+ // the room is not world-readable.
+ for _, ev := range stateRes.StateEvents {
+ if ev.Type() == gomatrixserverlib.MRoomHistoryVisibility {
+ content := map[string]string{}
+ if err := json.Unmarshal(ev.Content(), &content); err != nil {
+ util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed")
+ return jsonerror.InternalServerError()
+ }
+ if visibility, ok := content["history_visibility"]; ok {
+ worldReadable = visibility == "world_readable"
+ break
+ }
+ }
+ }
+
+ // If the room isn't world-readable then we will instead try to find out
+ // the state of the room based on the user's membership. If the user is
+ // in the room then we'll want the latest state. If the user has never
+ // been in the room and the room isn't world-readable, then we won't
+ // return any state. If the user was in the room previously but is no
+ // longer then we will return the state at the time that the user left.
+ // membershipRes will only be populated if the room is not world-readable.
+ var membershipRes api.QueryMembershipForUserResponse
+ if !worldReadable {
+ // The room isn't world-readable so try to work out based on the
+ // user's membership if we want the latest state or not.
+ err := rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{
+ RoomID: roomID,
+ UserID: device.UserID,
+ }, &membershipRes)
+ if err != nil {
+ util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
+ return jsonerror.InternalServerError()
+ }
+ // If the user has never been in the room then stop at this point.
+ // We won't tell the user about a room they have never joined.
+ if !membershipRes.HasBeenInRoom {
+ return util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: jsonerror.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
+ }
+ }
+ // Otherwise, if the user has been in the room, whether or not we
+ // give them the latest state will depend on if they are *still* in
+ // the room.
+ wantLatestState = membershipRes.IsInRoom
+ } else {
+ // The room is world-readable so the user join state is irrelevant,
+ // just get the latest room state instead.
+ wantLatestState = true
+ }
+
+ util.GetLogger(ctx).WithFields(log.Fields{
+ "roomID": roomID,
+ "evType": evType,
+ "stateKey": stateKey,
+ "state_at_event": !wantLatestState,
+ }).Info("Fetching state")
+
+ var event *gomatrixserverlib.HeaderedEvent
+ if wantLatestState {
+ // If we are happy to use the latest state, either because the user is
+ // still in the room, or because the room is world-readable, then just
+ // use the result of the previous QueryLatestEventsAndState response
+ // to find the state event, if provided.
+ for _, ev := range stateRes.StateEvents {
+ if ev.Type() == evType && ev.StateKeyEquals(stateKey) {
+ event = &ev
+ break
+ }
+ }
+ } else {
+ // Otherwise, take the event ID of their leave event and work out what
+ // the state of the room was before that event.
+ var stateAfterRes api.QueryStateAfterEventsResponse
+ err := rsAPI.QueryStateAfterEvents(ctx, &api.QueryStateAfterEventsRequest{
+ RoomID: roomID,
+ PrevEventIDs: []string{membershipRes.EventID},
+ StateToFetch: []gomatrixserverlib.StateKeyTuple{
+ {
+ EventType: evType,
+ StateKey: stateKey,
+ },
+ },
+ }, &stateAfterRes)
+ if err != nil {
+ util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
+ return jsonerror.InternalServerError()
+ }
+ if len(stateAfterRes.StateEvents) > 0 {
+ event = &stateAfterRes.StateEvents[0]
+ }
+ }
+
+ // If there was no event found that matches all of the above criteria then
+ // return an error.
+ if event == nil {
return util.JSONResponse{
Code: http.StatusNotFound,
- JSON: jsonerror.NotFound("cannot find state"),
+ JSON: jsonerror.NotFound(fmt.Sprintf("Cannot find state event for %q", evType)),
}
}
stateEvent := stateEventInStateResp{
- ClientEvent: gomatrixserverlib.HeaderedToClientEvent(stateRes.StateEvents[0], gomatrixserverlib.FormatAll),
+ ClientEvent: gomatrixserverlib.HeaderedToClientEvent(*event, gomatrixserverlib.FormatAll),
}
var res interface{}
diff --git a/roomserver/internal/query.go b/roomserver/internal/query.go
index bede6c88..828e5fd3 100644
--- a/roomserver/internal/query.go
+++ b/roomserver/internal/query.go
@@ -226,6 +226,7 @@ func (r *RoomserverInternalAPI) QueryMembershipForUser(
}
response.IsInRoom = stillInRoom
+ response.HasBeenInRoom = true
evs, err := r.DB.Events(ctx, []types.EventNID{membershipEventNID})
if err != nil {
diff --git a/sytest-whitelist b/sytest-whitelist
index 234eae39..5087186b 100644
--- a/sytest-whitelist
+++ b/sytest-whitelist
@@ -415,3 +415,4 @@ We don't send redundant membership state across incremental syncs by default
Typing notifications don't leak
Users cannot kick users from a room they are not in
Users cannot kick users who have already left a room
+Can get 'm.room.name' state for a departed room (SPEC-216)