diff --git a/clientapi/routing/account_data.go b/clientapi/routing/account_data.go index b28f0bb1f..4742b1240 100644 --- a/clientapi/routing/account_data.go +++ b/clientapi/routing/account_data.go @@ -154,33 +154,31 @@ func SaveReadMarker( return *resErr } - if r.FullyRead == "" { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.BadJSON("Missing m.fully_read mandatory field"), + if r.FullyRead != "" { + 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) } } - 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) - } - - // Handle the read receipt that may be included in the read marker + // Handle the read receipts that may be included in the read marker. if r.Read != "" { - return SetReceipt(req, syncProducer, device, roomID, "m.read", r.Read) + return SetReceipt(req, userAPI, syncProducer, device, roomID, "m.read", r.Read) + } + if r.ReadPrivate != "" { + return SetReceipt(req, userAPI, syncProducer, device, roomID, "m.read.private", r.ReadPrivate) } return util.JSONResponse{ diff --git a/clientapi/routing/receipt.go b/clientapi/routing/receipt.go index 0f9b1b4ff..99217a780 100644 --- a/clientapi/routing/receipt.go +++ b/clientapi/routing/receipt.go @@ -15,19 +15,22 @@ package routing import ( + "encoding/json" "fmt" "net/http" "time" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/producers" "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/util" "github.com/sirupsen/logrus" ) -func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse { +func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse { timestamp := gomatrixserverlib.AsTimestamp(time.Now()) logrus.WithFields(logrus.Fields{ "roomID": roomID, @@ -37,13 +40,32 @@ func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, devi "timestamp": timestamp, }).Debug("Setting receipt") - // currently only m.read is accepted - if receiptType != "m.read" { - return util.MessageResponse(400, fmt.Sprintf("receipt type must be m.read not '%s'", receiptType)) - } + switch receiptType { + case "m.read", "m.read.private": + if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil { + return util.ErrorResponse(err) + } - if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil { - return util.ErrorResponse(err) + case "m.fully_read": + data, err := json.Marshal(fullyReadEvent{EventID: eventID}) + 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) + } + + default: + return util.MessageResponse(400, fmt.Sprintf("Receipt type '%s' not known", receiptType)) } return util.JSONResponse{ diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index f1fa66ca6..e72880ec5 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -1343,7 +1343,7 @@ func Setup( return util.ErrorResponse(err) } - return SetReceipt(req, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"]) + return SetReceipt(req, userAPI, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"]) }), ).Methods(http.MethodPost, http.MethodOptions) v3mux.Handle("/presence/{userId}/status", diff --git a/federationapi/consumers/receipts.go b/federationapi/consumers/receipts.go index 366cb264e..75827cb68 100644 --- a/federationapi/consumers/receipts.go +++ b/federationapi/consumers/receipts.go @@ -81,6 +81,14 @@ func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) Type: msg.Header.Get("type"), } + switch receipt.Type { + case "m.read": + // These are allowed to be sent over federation + case "m.read.private", "m.fully_read": + // These must not be sent over federation + return true + } + // only send receipt events which originated from us _, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID) if err != nil { diff --git a/federationapi/routing/events.go b/federationapi/routing/events.go index 23796edfa..6168912bd 100644 --- a/federationapi/routing/events.go +++ b/federationapi/routing/events.go @@ -20,6 +20,7 @@ import ( "net/http" "time" + "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -95,7 +96,10 @@ func fetchEvent(ctx context.Context, rsAPI api.FederationRoomserverAPI, eventID } if len(eventsResponse.Events) == 0 { - return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil} + return nil, &util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound("Event not found"), + } } return eventsResponse.Events[0].Event, nil diff --git a/federationapi/routing/state.go b/federationapi/routing/state.go index 5377eb88f..1d08d0a82 100644 --- a/federationapi/routing/state.go +++ b/federationapi/routing/state.go @@ -135,23 +135,24 @@ func getState( return nil, nil, &resErr } - if !response.StateKnown { + switch { + case !response.RoomExists: + return nil, nil, &util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound("Room not found"), + } + case !response.StateKnown: return nil, nil, &util.JSONResponse{ Code: http.StatusNotFound, JSON: jsonerror.NotFound("State not known"), } - } - if response.IsRejected { + case response.IsRejected: return nil, nil, &util.JSONResponse{ Code: http.StatusNotFound, JSON: jsonerror.NotFound("Event not found"), } } - if !response.RoomExists { - return nil, nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil} - } - return response.StateEvents, response.AuthChainEvents, nil } diff --git a/internal/eventutil/types.go b/internal/eventutil/types.go index afc62d8c2..18175d6a0 100644 --- a/internal/eventutil/types.go +++ b/internal/eventutil/types.go @@ -35,8 +35,9 @@ type AccountData struct { } type ReadMarkerJSON struct { - FullyRead string `json:"m.fully_read"` - Read string `json:"m.read"` + FullyRead string `json:"m.fully_read"` + Read string `json:"m.read"` + ReadPrivate string `json:"m.read.private"` } // NotificationData contains statistics about notifications, sent from diff --git a/syncapi/storage/shared/storage_sync.go b/syncapi/storage/shared/storage_sync.go index 0e19d97d2..d5b5b3121 100644 --- a/syncapi/storage/shared/storage_sync.go +++ b/syncapi/storage/shared/storage_sync.go @@ -360,34 +360,50 @@ func (d *DatabaseTransaction) GetStateDeltas( newlyJoinedRooms := make(map[string]bool, len(state)) for roomID, stateStreamEvents := range state { for _, ev := range stateStreamEvents { - if membership, prevMembership := getMembershipFromEvent(ev.Event, userID); membership != "" { - if membership == gomatrixserverlib.Join && prevMembership != membership { - // send full room state down instead of a delta + // Look for our membership in the state events and skip over any + // membership events that are not related to us. + membership, prevMembership := getMembershipFromEvent(ev.Event, userID) + if membership == "" { + continue + } + + if membership == gomatrixserverlib.Join { + // If our membership is now join but the previous membership wasn't + // then this is a "join transition", so we'll insert this room. + if prevMembership != membership { + // Get the full room state, as we'll send that down for a newly + // joined room instead of a delta. var s []types.StreamEvent - s, err = d.currentStateStreamEventsForRoom(ctx, roomID, stateFilter) - if err != nil { + if s, err = d.currentStateStreamEventsForRoom(ctx, roomID, stateFilter); err != nil { if err == sql.ErrNoRows { continue } return nil, nil, err } + + // Add the information for this room into the state so that + // it will get added with all of the rest of the joined rooms. state[roomID] = s newlyJoinedRooms[roomID] = true - continue // we'll add this room in when we do joined rooms } - deltas = append(deltas, types.StateDelta{ - Membership: membership, - MembershipPos: ev.StreamPosition, - StateEvents: d.StreamEventsToEvents(device, stateStreamEvents), - RoomID: roomID, - }) - break + // We won't add joined rooms into the delta at this point as they + // are added later on. + continue } + + deltas = append(deltas, types.StateDelta{ + Membership: membership, + MembershipPos: ev.StreamPosition, + StateEvents: d.StreamEventsToEvents(device, stateStreamEvents), + RoomID: roomID, + }) + break } } - // Add in currently joined rooms + // Finally, add in currently joined rooms, including those from the + // join transitions above. for _, joinedRoomID := range joinedRoomIDs { deltas = append(deltas, types.StateDelta{ Membership: gomatrixserverlib.Join, diff --git a/syncapi/streams/stream_receipt.go b/syncapi/streams/stream_receipt.go index bba911022..977815078 100644 --- a/syncapi/streams/stream_receipt.go +++ b/syncapi/streams/stream_receipt.go @@ -67,6 +67,10 @@ func (p *ReceiptStreamProvider) IncrementalSync( if _, ok := req.IgnoredUsers.List[receipt.UserID]; ok { continue } + // Don't send private read receipts to other users + if receipt.Type == "m.read.private" && req.Device.UserID != receipt.UserID { + continue + } receiptsByRoom[receipt.RoomID] = append(receiptsByRoom[receipt.RoomID], receipt) }