diff options
author | Kegsay <kegan@matrix.org> | 2020-06-26 15:34:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-26 15:34:41 +0100 |
commit | 1ad7219e4b6c71f64e4d44db17a6a8d729e6198a (patch) | |
tree | c13db3fd184c0c9bd7d879793be7e5aba2066121 /syncapi/routing | |
parent | 164057a3be1e666d6fb68398d616da9a8a665a18 (diff) |
Implement /sync `limited` and read timeline limit from stored filters (#1168)
* Move filter table to syncapi where it is used
* Implement /sync `limited` and read timeline limit from stored filters
We now fully handle `room.timeline.limit` filters (in-line + stored) and
return the right value for `limited` syncs.
* Update whitelist
* Default to the default timeline limit if it's unset, also strip the extra event correctly
* Update whitelist
Diffstat (limited to 'syncapi/routing')
-rw-r--r-- | syncapi/routing/filter.go | 128 | ||||
-rw-r--r-- | syncapi/routing/routing.go | 20 |
2 files changed, 148 insertions, 0 deletions
diff --git a/syncapi/routing/filter.go b/syncapi/routing/filter.go new file mode 100644 index 00000000..baa4d841 --- /dev/null +++ b/syncapi/routing/filter.go @@ -0,0 +1,128 @@ +// Copyright 2017 Jan Christian Grünhage +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/syncapi/storage" + "github.com/matrix-org/dendrite/syncapi/sync" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" + "github.com/tidwall/gjson" +) + +// GetFilter implements GET /_matrix/client/r0/user/{userId}/filter/{filterId} +func GetFilter( + req *http.Request, device *api.Device, syncDB storage.Database, userID string, filterID string, +) util.JSONResponse { + if userID != device.UserID { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Cannot get filters for other users"), + } + } + localpart, _, err := gomatrixserverlib.SplitID('@', userID) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") + return jsonerror.InternalServerError() + } + + filter, err := syncDB.GetFilter(req.Context(), localpart, filterID) + if err != nil { + //TODO better error handling. This error message is *probably* right, + // but if there are obscure db errors, this will also be returned, + // even though it is not correct. + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.NotFound("No such filter"), + } + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: filter, + } +} + +type filterResponse struct { + FilterID string `json:"filter_id"` +} + +//PutFilter implements POST /_matrix/client/r0/user/{userId}/filter +func PutFilter( + req *http.Request, device *api.Device, syncDB storage.Database, userID string, +) util.JSONResponse { + if userID != device.UserID { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Cannot create filters for other users"), + } + } + + localpart, _, err := gomatrixserverlib.SplitID('@', userID) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") + return jsonerror.InternalServerError() + } + + var filter gomatrixserverlib.Filter + + defer req.Body.Close() // nolint:errcheck + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("The request body could not be read. " + err.Error()), + } + } + + if err = json.Unmarshal(body, &filter); err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()), + } + } + // the filter `limit` is `int` which defaults to 0 if not set which is not what we want. We want to use the default + // limit if it is unset, which is what this does. + limitRes := gjson.GetBytes(body, "room.timeline.limit") + if !limitRes.Exists() { + util.GetLogger(req.Context()).Infof("missing timeline limit, using default") + filter.Room.Timeline.Limit = sync.DefaultTimelineLimit + } + + // Validate generates a user-friendly error + if err = filter.Validate(); err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("Invalid filter: " + err.Error()), + } + } + + filterID, err := syncDB.PutFilter(req.Context(), localpart, &filter) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("syncDB.PutFilter failed") + return jsonerror.InternalServerError() + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: filterResponse{FilterID: filterID}, + } +} diff --git a/syncapi/routing/routing.go b/syncapi/routing/routing.go index 5744de05..a98955c5 100644 --- a/syncapi/routing/routing.go +++ b/syncapi/routing/routing.go @@ -55,4 +55,24 @@ func Setup( } return OnIncomingMessagesRequest(req, syncDB, vars["roomID"], federation, rsAPI, cfg) })).Methods(http.MethodGet, http.MethodOptions) + + r0mux.Handle("/user/{userId}/filter", + httputil.MakeAuthAPI("put_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + return PutFilter(req, device, syncDB, vars["userId"]) + }), + ).Methods(http.MethodPost, http.MethodOptions) + + r0mux.Handle("/user/{userId}/filter/{filterId}", + httputil.MakeAuthAPI("get_filter", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + return GetFilter(req, device, syncDB, vars["userId"], vars["filterId"]) + }), + ).Methods(http.MethodGet, http.MethodOptions) } |