From f3e8ae01efb0abd0904509ddaa2ae85017ca4aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFck=20Bonniot?= Date: Fri, 9 Oct 2020 10:15:35 +0200 Subject: [PATCH] Implement fully read markers (#1475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See #653 Signed-off-by: Loïck Bonniot Co-authored-by: Kegsay --- clientapi/routing/account_data.go | 75 ++++++++++++++++++++++++++++++- clientapi/routing/routing.go | 9 ++-- sytest-whitelist | 3 ++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/clientapi/routing/account_data.go b/clientapi/routing/account_data.go index d5fafedb1..48303c97f 100644 --- a/clientapi/routing/account_data.go +++ b/clientapi/routing/account_data.go @@ -20,8 +20,10 @@ import ( "io/ioutil" "net/http" + "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/producers" + roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/util" @@ -91,6 +93,13 @@ func SaveAccountData( } } + if dataType == "m.fully_read" { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Unable to set read marker"), + } + } + body, err := ioutil.ReadAll(req.Body) if err != nil { util.GetLogger(req.Context()).WithError(err).Error("ioutil.ReadAll failed") @@ -112,7 +121,7 @@ func SaveAccountData( } dataRes := api.InputAccountDataResponse{} if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil { - util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryAccountData failed") + util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed") return util.ErrorResponse(err) } @@ -127,3 +136,67 @@ func SaveAccountData( JSON: struct{}{}, } } + +type readMarkerJSON struct { + FullyRead string `json:"m.fully_read"` + Read string `json:"m.read"` +} + +type fullyReadEvent struct { + EventID string `json:"event_id"` +} + +// SaveReadMarker implements POST /rooms/{roomId}/read_markers +func SaveReadMarker( + req *http.Request, userAPI api.UserInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI, + syncProducer *producers.SyncAPIProducer, device *api.Device, roomID string, +) util.JSONResponse { + // Verify that the user is a member of this room + resErr := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID) + if resErr != nil { + return *resErr + } + + var r readMarkerJSON + resErr = httputil.UnmarshalJSONRequest(req, &r) + if resErr != nil { + return *resErr + } + + if r.FullyRead == "" { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("Missing m.fully_read mandatory field"), + } + } + + data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead}) + if err != nil { + return jsonerror.InternalServerError() + } + + dataReq := api.InputAccountDataRequest{ + UserID: device.UserID, + DataType: "m.fully_read", + RoomID: roomID, + AccountData: data, + } + dataRes := api.InputAccountDataResponse{} + if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil { + util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed") + return util.ErrorResponse(err) + } + + if err := syncProducer.SendData(device.UserID, roomID, "m.fully_read"); err != nil { + util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed") + return jsonerror.InternalServerError() + } + + // TODO handle the read receipt that may be included in the read marker + // See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-rooms-roomid-read-markers + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: struct{}{}, + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 8606f69c3..b547efb4b 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -695,12 +695,15 @@ func Setup( ).Methods(http.MethodGet, http.MethodOptions) r0mux.Handle("/rooms/{roomID}/read_markers", - httputil.MakeExternalAPI("rooms_read_markers", func(req *http.Request) util.JSONResponse { + httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { if r := rateLimits.rateLimit(req); r != nil { return *r } - // TODO: return the read_markers. - return util.JSONResponse{Code: http.StatusOK, JSON: struct{}{}} + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + return SaveReadMarker(req, userAPI, rsAPI, syncProducer, device, vars["roomID"]) }), ).Methods(http.MethodPost, http.MethodOptions) diff --git a/sytest-whitelist b/sytest-whitelist index e0f1f311e..420acb22c 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -456,6 +456,9 @@ After changing password, can log in with new password After changing password, existing session still works After changing password, different sessions can optionally be kept After changing password, a different session no longer works by default +Read markers appear in incremental v2 /sync +Read markers appear in initial v2 /sync +Read markers can be updated Local users can peek into world_readable rooms by room ID We can't peek into rooms with shared history_visibility We can't peek into rooms with invited history_visibility