aboutsummaryrefslogtreecommitdiff
path: root/syncapi/routing
diff options
context:
space:
mode:
authorKegsay <kegan@matrix.org>2020-06-26 15:34:41 +0100
committerGitHub <noreply@github.com>2020-06-26 15:34:41 +0100
commit1ad7219e4b6c71f64e4d44db17a6a8d729e6198a (patch)
treec13db3fd184c0c9bd7d879793be7e5aba2066121 /syncapi/routing
parent164057a3be1e666d6fb68398d616da9a8a665a18 (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.go128
-rw-r--r--syncapi/routing/routing.go20
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)
}