aboutsummaryrefslogtreecommitdiff
path: root/clientapi
diff options
context:
space:
mode:
Diffstat (limited to 'clientapi')
-rw-r--r--clientapi/clientapi.go3
-rw-r--r--clientapi/producers/syncapi.go7
-rw-r--r--clientapi/routing/account_data.go12
-rw-r--r--clientapi/routing/notification.go63
-rw-r--r--clientapi/routing/password.go15
-rw-r--r--clientapi/routing/pusher.go114
-rw-r--r--clientapi/routing/pushrules.go386
-rw-r--r--clientapi/routing/room_tagging.go4
-rw-r--r--clientapi/routing/routing.go165
9 files changed, 741 insertions, 28 deletions
diff --git a/clientapi/clientapi.go b/clientapi/clientapi.go
index a65f3b70..91847667 100644
--- a/clientapi/clientapi.go
+++ b/clientapi/clientapi.go
@@ -59,6 +59,7 @@ func AddPublicRoutes(
routing.Setup(
router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI,
accountsDB, userAPI, federation,
- syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, mscCfg,
+ syncProducer, transactionsCache, fsAPI, keyAPI,
+ extRoomsProvider, mscCfg,
)
}
diff --git a/clientapi/producers/syncapi.go b/clientapi/producers/syncapi.go
index 9b1d6b1a..9ab90391 100644
--- a/clientapi/producers/syncapi.go
+++ b/clientapi/producers/syncapi.go
@@ -30,7 +30,7 @@ type SyncAPIProducer struct {
}
// SendData sends account data to the sync API server
-func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string) error {
+func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string, readMarker *eventutil.ReadMarkerJSON) error {
m := &nats.Msg{
Subject: p.Topic,
Header: nats.Header{},
@@ -38,8 +38,9 @@ func (p *SyncAPIProducer) SendData(userID string, roomID string, dataType string
m.Header.Set(jetstream.UserID, userID)
data := eventutil.AccountData{
- RoomID: roomID,
- Type: dataType,
+ RoomID: roomID,
+ Type: dataType,
+ ReadMarker: readMarker,
}
var err error
m.Data, err = json.Marshal(data)
diff --git a/clientapi/routing/account_data.go b/clientapi/routing/account_data.go
index 03025f1d..d8e98269 100644
--- a/clientapi/routing/account_data.go
+++ b/clientapi/routing/account_data.go
@@ -24,6 +24,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/producers"
eduserverAPI "github.com/matrix-org/dendrite/eduserver/api"
+ "github.com/matrix-org/dendrite/internal/eventutil"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/userapi/api"
@@ -127,7 +128,7 @@ func SaveAccountData(
}
// TODO: user API should do this since it's account data
- if err := syncProducer.SendData(userID, roomID, dataType); err != nil {
+ if err := syncProducer.SendData(userID, roomID, dataType, nil); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
return jsonerror.InternalServerError()
}
@@ -138,11 +139,6 @@ func SaveAccountData(
}
}
-type readMarkerJSON struct {
- FullyRead string `json:"m.fully_read"`
- Read string `json:"m.read"`
-}
-
type fullyReadEvent struct {
EventID string `json:"event_id"`
}
@@ -159,7 +155,7 @@ func SaveReadMarker(
return *resErr
}
- var r readMarkerJSON
+ var r eventutil.ReadMarkerJSON
resErr = httputil.UnmarshalJSONRequest(req, &r)
if resErr != nil {
return *resErr
@@ -189,7 +185,7 @@ func SaveReadMarker(
return util.ErrorResponse(err)
}
- if err := syncProducer.SendData(device.UserID, roomID, "m.fully_read"); err != nil {
+ if err := syncProducer.SendData(device.UserID, roomID, "m.fully_read", &r); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("syncProducer.SendData failed")
return jsonerror.InternalServerError()
}
diff --git a/clientapi/routing/notification.go b/clientapi/routing/notification.go
new file mode 100644
index 00000000..ee715d32
--- /dev/null
+++ b/clientapi/routing/notification.go
@@ -0,0 +1,63 @@
+// Copyright 2021 Dan Peleg <dan@globekeeper.com>
+//
+// 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 (
+ "net/http"
+ "strconv"
+
+ "github.com/matrix-org/dendrite/clientapi/jsonerror"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/util"
+)
+
+// GetNotifications handles /_matrix/client/r0/notifications
+func GetNotifications(
+ req *http.Request, device *userapi.Device,
+ userAPI userapi.UserInternalAPI,
+) util.JSONResponse {
+ var limit int64
+ if limitStr := req.URL.Query().Get("limit"); limitStr != "" {
+ var err error
+ limit, err = strconv.ParseInt(limitStr, 10, 64)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("ParseInt(limit) failed")
+ return jsonerror.InternalServerError()
+ }
+ }
+
+ var queryRes userapi.QueryNotificationsResponse
+ localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
+ return jsonerror.InternalServerError()
+ }
+ err = userAPI.QueryNotifications(req.Context(), &userapi.QueryNotificationsRequest{
+ Localpart: localpart,
+ From: req.URL.Query().Get("from"),
+ Limit: int(limit),
+ Only: req.URL.Query().Get("only"),
+ }, &queryRes)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("QueryNotifications failed")
+ return jsonerror.InternalServerError()
+ }
+ util.GetLogger(req.Context()).WithField("from", req.URL.Query().Get("from")).WithField("limit", limit).WithField("only", req.URL.Query().Get("only")).WithField("next", queryRes.NextToken).Infof("QueryNotifications: len %d", len(queryRes.Notifications))
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: queryRes,
+ }
+}
diff --git a/clientapi/routing/password.go b/clientapi/routing/password.go
index acac60fa..c63412d0 100644
--- a/clientapi/routing/password.go
+++ b/clientapi/routing/password.go
@@ -12,6 +12,7 @@ import (
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
+ "github.com/sirupsen/logrus"
)
type newPasswordRequest struct {
@@ -37,6 +38,11 @@ func Password(
var r newPasswordRequest
r.LogoutDevices = true
+ logrus.WithFields(logrus.Fields{
+ "sessionId": device.SessionID,
+ "userId": device.UserID,
+ }).Debug("Changing password")
+
// Unmarshal the request.
resErr := httputil.UnmarshalJSONRequest(req, &r)
if resErr != nil {
@@ -116,6 +122,15 @@ func Password(
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
return jsonerror.InternalServerError()
}
+
+ pushersReq := &api.PerformPusherDeletionRequest{
+ Localpart: localpart,
+ SessionID: device.SessionID,
+ }
+ if err := userAPI.PerformPusherDeletion(req.Context(), pushersReq, &struct{}{}); err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("PerformPusherDeletion failed")
+ return jsonerror.InternalServerError()
+ }
}
// Return a success code.
diff --git a/clientapi/routing/pusher.go b/clientapi/routing/pusher.go
new file mode 100644
index 00000000..9d6bef8b
--- /dev/null
+++ b/clientapi/routing/pusher.go
@@ -0,0 +1,114 @@
+// Copyright 2021 Dan Peleg <dan@globekeeper.com>
+//
+// 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 (
+ "net/http"
+ "net/url"
+
+ "github.com/matrix-org/dendrite/clientapi/httputil"
+ "github.com/matrix-org/dendrite/clientapi/jsonerror"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/util"
+)
+
+// GetPushers handles /_matrix/client/r0/pushers
+func GetPushers(
+ req *http.Request, device *userapi.Device,
+ userAPI userapi.UserInternalAPI,
+) util.JSONResponse {
+ var queryRes userapi.QueryPushersResponse
+ localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
+ return jsonerror.InternalServerError()
+ }
+ err = userAPI.QueryPushers(req.Context(), &userapi.QueryPushersRequest{
+ Localpart: localpart,
+ }, &queryRes)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("QueryPushers failed")
+ return jsonerror.InternalServerError()
+ }
+ for i := range queryRes.Pushers {
+ queryRes.Pushers[i].SessionID = 0
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: queryRes,
+ }
+}
+
+// SetPusher handles /_matrix/client/r0/pushers/set
+// This endpoint allows the creation, modification and deletion of pushers for this user ID.
+// The behaviour of this endpoint varies depending on the values in the JSON body.
+func SetPusher(
+ req *http.Request, device *userapi.Device,
+ userAPI userapi.UserInternalAPI,
+) util.JSONResponse {
+ localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
+ return jsonerror.InternalServerError()
+ }
+ body := userapi.PerformPusherSetRequest{}
+ if resErr := httputil.UnmarshalJSONRequest(req, &body); resErr != nil {
+ return *resErr
+ }
+ if len(body.AppID) > 64 {
+ return invalidParam("length of app_id must be no more than 64 characters")
+ }
+ if len(body.PushKey) > 512 {
+ return invalidParam("length of pushkey must be no more than 512 bytes")
+ }
+ uInt := body.Data["url"]
+ if uInt != nil {
+ u, ok := uInt.(string)
+ if !ok {
+ return invalidParam("url must be string")
+ }
+ if u != "" {
+ var pushUrl *url.URL
+ pushUrl, err = url.Parse(u)
+ if err != nil {
+ return invalidParam("malformed url passed")
+ }
+ if pushUrl.Scheme != "https" {
+ return invalidParam("only https scheme is allowed")
+ }
+ }
+
+ }
+ body.Localpart = localpart
+ body.SessionID = device.SessionID
+ err = userAPI.PerformPusherSet(req.Context(), &body, &struct{}{})
+ if err != nil {
+ util.GetLogger(req.Context()).WithError(err).Error("PerformPusherSet failed")
+ return jsonerror.InternalServerError()
+ }
+
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: struct{}{},
+ }
+}
+
+func invalidParam(msg string) util.JSONResponse {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidParam(msg),
+ }
+}
diff --git a/clientapi/routing/pushrules.go b/clientapi/routing/pushrules.go
new file mode 100644
index 00000000..81a33b25
--- /dev/null
+++ b/clientapi/routing/pushrules.go
@@ -0,0 +1,386 @@
+package routing
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "reflect"
+
+ "github.com/matrix-org/dendrite/clientapi/jsonerror"
+ "github.com/matrix-org/dendrite/internal/pushrules"
+ userapi "github.com/matrix-org/dendrite/userapi/api"
+ "github.com/matrix-org/util"
+)
+
+func errorResponse(ctx context.Context, err error, msg string, args ...interface{}) util.JSONResponse {
+ if eerr, ok := err.(*jsonerror.MatrixError); ok {
+ var status int
+ switch eerr.ErrCode {
+ case "M_INVALID_ARGUMENT_VALUE":
+ status = http.StatusBadRequest
+ case "M_NOT_FOUND":
+ status = http.StatusNotFound
+ default:
+ status = http.StatusInternalServerError
+ }
+ return util.MatrixErrorResponse(status, eerr.ErrCode, eerr.Err)
+ }
+ util.GetLogger(ctx).WithError(err).Errorf(msg, args...)
+ return jsonerror.InternalServerError()
+}
+
+func GetAllPushRules(ctx context.Context, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRulesJSON failed")
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: ruleSets,
+ }
+}
+
+func GetPushRulesByScope(ctx context.Context, scope string, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRulesJSON failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: ruleSet,
+ }
+}
+
+func GetPushRulesByKind(ctx context.Context, scope, kind string, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRules failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
+ if rulesPtr == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: *rulesPtr,
+ }
+}
+
+func GetPushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRules failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
+ if rulesPtr == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
+ }
+ i := pushRuleIndexByID(*rulesPtr, ruleID)
+ if i < 0 {
+ return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: (*rulesPtr)[i],
+ }
+}
+
+func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID, beforeRuleID string, body io.Reader, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ var newRule pushrules.Rule
+ if err := json.NewDecoder(body).Decode(&newRule); err != nil {
+ return errorResponse(ctx, err, "JSON Decode failed")
+ }
+ newRule.RuleID = ruleID
+
+ errs := pushrules.ValidateRule(pushrules.Kind(kind), &newRule)
+ if len(errs) > 0 {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue(errs[0].Error()), "rule sanity check failed: %v", errs)
+ }
+
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRules failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
+ if rulesPtr == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
+ }
+ i := pushRuleIndexByID(*rulesPtr, ruleID)
+ if i >= 0 && afterRuleID == "" && beforeRuleID == "" {
+ // Modify rule at the same index.
+
+ // TODO: The spec does not say what to do in this case, but
+ // this feels reasonable.
+ *((*rulesPtr)[i]) = newRule
+ util.GetLogger(ctx).Infof("Modified existing push rule at %d", i)
+ } else {
+ if i >= 0 {
+ // Delete old rule.
+ *rulesPtr = append((*rulesPtr)[:i], (*rulesPtr)[i+1:]...)
+ util.GetLogger(ctx).Infof("Deleted old push rule at %d", i)
+ } else {
+ // SPEC: When creating push rules, they MUST be enabled by default.
+ //
+ // TODO: it's unclear if we must reject disabled rules, or force
+ // the value to true. Sytests fail if we don't force it.
+ newRule.Enabled = true
+ }
+
+ // Add new rule.
+ i, err := findPushRuleInsertionIndex(*rulesPtr, afterRuleID, beforeRuleID)
+ if err != nil {
+ return errorResponse(ctx, err, "findPushRuleInsertionIndex failed")
+ }
+
+ *rulesPtr = append((*rulesPtr)[:i], append([]*pushrules.Rule{&newRule}, (*rulesPtr)[i:]...)...)
+ util.GetLogger(ctx).WithField("after", afterRuleID).WithField("before", beforeRuleID).Infof("Added new push rule at %d", i)
+ }
+
+ if err := putPushRules(ctx, device.UserID, ruleSets, userAPI); err != nil {
+ return errorResponse(ctx, err, "putPushRules failed")
+ }
+
+ return util.JSONResponse{Code: http.StatusOK, JSON: struct{}{}}
+}
+
+func DeletePushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRules failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
+ if rulesPtr == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
+ }
+ i := pushRuleIndexByID(*rulesPtr, ruleID)
+ if i < 0 {
+ return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
+ }
+
+ *rulesPtr = append((*rulesPtr)[:i], (*rulesPtr)[i+1:]...)
+
+ if err := putPushRules(ctx, device.UserID, ruleSets, userAPI); err != nil {
+ return errorResponse(ctx, err, "putPushRules failed")
+ }
+
+ return util.JSONResponse{Code: http.StatusOK, JSON: struct{}{}}
+}
+
+func GetPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr string, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ attrGet, err := pushRuleAttrGetter(attr)
+ if err != nil {
+ return errorResponse(ctx, err, "pushRuleAttrGetter failed")
+ }
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRules failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
+ if rulesPtr == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
+ }
+ i := pushRuleIndexByID(*rulesPtr, ruleID)
+ if i < 0 {
+ return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: map[string]interface{}{
+ attr: attrGet((*rulesPtr)[i]),
+ },
+ }
+}
+
+func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr string, body io.Reader, device *userapi.Device, userAPI userapi.UserInternalAPI) util.JSONResponse {
+ var newPartialRule pushrules.Rule
+ if err := json.NewDecoder(body).Decode(&newPartialRule); err != nil {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.BadJSON(err.Error()),
+ }
+ }
+ if newPartialRule.Actions == nil {
+ // This ensures json.Marshal encodes the empty list as [] rather than null.
+ newPartialRule.Actions = []*pushrules.Action{}
+ }
+
+ attrGet, err := pushRuleAttrGetter(attr)
+ if err != nil {
+ return errorResponse(ctx, err, "pushRuleAttrGetter failed")
+ }
+ attrSet, err := pushRuleAttrSetter(attr)
+ if err != nil {
+ return errorResponse(ctx, err, "pushRuleAttrSetter failed")
+ }
+
+ ruleSets, err := queryPushRules(ctx, device.UserID, userAPI)
+ if err != nil {
+ return errorResponse(ctx, err, "queryPushRules failed")
+ }
+ ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
+ if ruleSet == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
+ }
+ rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
+ if rulesPtr == nil {
+ return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
+ }
+ i := pushRuleIndexByID(*rulesPtr, ruleID)
+ if i < 0 {
+ return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
+ }
+
+ if !reflect.DeepEqual(attrGet((*rulesPtr)[i]), attrGet(&newPartialRule)) {
+ attrSet((*rulesPtr)[i], &newPartialRule)
+
+ if err := putPushRules(ctx, device.UserID, ruleSets, userAPI); err != nil {
+ return errorResponse(ctx, err, "putPushRules failed")
+ }
+ }
+
+ return util.JSONResponse{Code: http.StatusOK, JSON: struct{}{}}
+}
+
+func queryPushRules(ctx context.Context, userID string, userAPI userapi.UserInternalAPI) (*pushrules.AccountRuleSets, error) {
+ var res userapi.QueryPushRulesResponse
+ if err := userAPI.QueryPushRules(ctx, &userapi.QueryPushRulesRequest{UserID: userID}, &res); err != nil {
+ util.GetLogger(ctx).WithError(err).Error("userAPI.QueryPushRules failed")
+ return nil, err
+ }
+ return res.RuleSets, nil
+}
+
+func putPushRules(ctx context.Context, userID string, ruleSets *pushrules.AccountRuleSets, userAPI userapi.UserInternalAPI) error {
+ req := userapi.PerformPushRulesPutRequest{
+ UserID: userID,
+ RuleSets: ruleSets,
+ }
+ var res struct{}
+ if err := userAPI.PerformPushRulesPut(ctx, &req, &res); err != nil {
+ util.GetLogger(ctx).WithError(err).Error("userAPI.PerformPushRulesPut failed")
+ return err
+ }
+ return nil
+}
+
+func pushRuleSetByScope(ruleSets *pushrules.AccountRuleSets, scope pushrules.Scope) *pushrules.RuleSet {
+ switch scope {
+ case pushrules.GlobalScope:
+ return &ruleSets.Global
+ default:
+ return nil
+ }
+}
+
+func pushRuleSetKindPointer(ruleSet *pushrules.RuleSet, kind pushrules.Kind) *[]*pushrules.Rule {
+ switch kind {
+ case pushrules.OverrideKind:
+ return &ruleSet.Override
+ case pushrules.ContentKind:
+ return &ruleSet.Content
+ case pushrules.RoomKind:
+ return &ruleSet.Room
+ case pushrules.SenderKind:
+ return &ruleSet.Sender
+ case pushrules.UnderrideKind:
+ return &ruleSet.Underride
+ default:
+ return nil
+ }
+}
+
+func pushRuleIndexByID(rules []*pushrules.Rule, id string) int {
+ for i, rule := range rules {
+ if rule.RuleID == id {
+ return i
+ }
+ }
+ return -1
+}
+
+func pushRuleAttrGetter(attr string) (func(*pushrules.Rule) interface{}, error) {
+ switch attr {
+ case "actions":
+ return func(rule *pushrules.Rule) interface{} { return rule.Actions }, nil
+ case "enabled":
+ return func(rule *pushrules.Rule) interface{} { return rule.Enabled }, nil
+ default:
+ return nil, jsonerror.InvalidArgumentValue("invalid push rule attribute")
+ }
+}
+
+func pushRuleAttrSetter(attr string) (func(dest, src *pushrules.Rule), error) {
+ switch attr {
+ case "actions":
+ return func(dest, src *pushrules.Rule) { dest.Actions = src.Actions }, nil
+ case "enabled":
+ return func(dest, src *pushrules.Rule) { dest.Enabled = src.Enabled }, nil
+ default:
+ return nil, jsonerror.InvalidArgumentValue("invalid push rule attribute")
+ }
+}
+
+func findPushRuleInsertionIndex(rules []*pushrules.Rule, afterID, beforeID string) (int, error) {
+ var i int
+
+ if afterID != "" {
+ for ; i < len(rules); i++ {
+ if rules[i].RuleID == afterID {
+ break
+ }
+ }
+ if i == len(rules) {
+ return 0, jsonerror.NotFound("after: rule ID not found")
+ }
+ if rules[i].Default {
+ return 0, jsonerror.NotFound("after: rule ID must not be a default rule")
+ }
+ // We stopped on the "after" match to differentiate
+ // not-found from is-last-entry. Now we move to the earliest
+ // insertion point.
+ i++
+ }
+
+ if beforeID != "" {
+ for ; i < len(rules); i++ {
+ if rules[i].RuleID == beforeID {
+ break
+ }
+ }
+ if i == len(rules) {
+ return 0, jsonerror.NotFound("before: rule ID not found")
+ }
+ if rules[i].Default {
+ return 0, jsonerror.NotFound("before: rule ID must not be a default rule")
+ }
+ }
+
+ // UNSPEC: The spec does not say what to do if no after/before is
+ // given. Sytest fails if it doesn't go first.
+ return i, nil
+}
diff --git a/clientapi/routing/room_tagging.go b/clientapi/routing/room_tagging.go
index c683cc94..83294b18 100644
--- a/clientapi/routing/room_tagging.go
+++ b/clientapi/routing/room_tagging.go
@@ -98,7 +98,7 @@ func PutTag(
return jsonerror.InternalServerError()
}
- if err = syncProducer.SendData(userID, roomID, "m.tag"); err != nil {
+ if err = syncProducer.SendData(userID, roomID, "m.tag", nil); err != nil {
logrus.WithError(err).Error("Failed to send m.tag account data update to syncapi")
}
@@ -151,7 +151,7 @@ func DeleteTag(
}
// TODO: user API should do this since it's account data
- if err := syncProducer.SendData(userID, roomID, "m.tag"); err != nil {
+ if err := syncProducer.SendData(userID, roomID, "m.tag", nil); err != nil {
logrus.WithError(err).Error("Failed to send m.tag account data update to syncapi")
}
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index d75f58b8..d22fbd80 100644
--- a/clientapi/routing/routing.go
+++ b/clientapi/routing/routing.go
@@ -16,7 +16,6 @@ package routing
import (
"context"
- "encoding/json"
"net/http"
"strings"
@@ -561,25 +560,142 @@ func Setup(
}),
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
+ // Push rules
+
+ v3mux.Handle("/pushrules",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidArgumentValue("missing trailing slash"),
+ }
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
v3mux.Handle("/pushrules/",
- httputil.MakeExternalAPI("push_rules", func(req *http.Request) util.JSONResponse {
- // TODO: Implement push rules API
- res := json.RawMessage(`{
- "global": {
- "content": [],
- "override": [],
- "room": [],
- "sender": [],
- "underride": []
- }
- }`)
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return GetAllPushRules(req.Context(), device, userAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushrules/",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return util.JSONResponse{
- Code: http.StatusOK,
- JSON: &res,
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidArgumentValue("scope, kind and rule ID must be specified"),
+ }
+ }),
+ ).Methods(http.MethodPut)
+
+ v3mux.Handle("/pushrules/{scope}/",
+ httputil.MakeAuthAPI("push_rules", 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 GetPushRulesByScope(req.Context(), vars["scope"], device, userAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushrules/{scope}",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidArgumentValue("missing trailing slash after scope"),
+ }
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushrules/{scope:[^/]+/?}",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidArgumentValue("kind and rule ID must be specified"),
+ }
+ }),
+ ).Methods(http.MethodPut)
+
+ v3mux.Handle("/pushrules/{scope}/{kind}/",
+ httputil.MakeAuthAPI("push_rules", 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 GetPushRulesByKind(req.Context(), vars["scope"], vars["kind"], device, userAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushrules/{scope}/{kind}",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidArgumentValue("missing trailing slash after kind"),
+ }
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushrules/{scope}/{kind:[^/]+/?}",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.InvalidArgumentValue("rule ID must be specified"),
+ }
+ }),
+ ).Methods(http.MethodPut)
+
+ v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
+ httputil.MakeAuthAPI("push_rules", 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 GetPushRuleByRuleID(req.Context(), vars["scope"], vars["kind"], vars["ruleID"], device, userAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)
+ v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
+ httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ if r := rateLimits.Limit(req); r != nil {
+ return *r
+ }
+ vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
+ if err != nil {
+ return util.ErrorResponse(err)
+ }
+ query := req.URL.Query()
+ return PutPushRuleByRuleID(req.Context(), vars["scope"], vars["kind"], vars["ruleID"], query.Get("after"), query.Get("before"), req.Body, device, userAPI)
+ }),
+ ).Methods(http.MethodPut)
+
+ v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}",
+ httputil.MakeAuthAPI("push_rules", 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 DeletePushRuleByRuleID(req.Context(), vars["scope"], vars["kind"], vars["ruleID"], device, userAPI)
+ }),
+ ).Methods(http.MethodDelete)
+
+ v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}",
+ httputil.MakeAuthAPI("push_rules", 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 GetPushRuleAttrByRuleID(req.Context(), vars["scope"], vars["kind"], vars["ruleID"], vars["attr"], device, userAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushrules/{scope}/{kind}/{ruleID}/{attr}",
+ httputil.MakeAuthAPI("push_rules", 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 PutPushRuleAttrByRuleID(req.Context(), vars["scope"], vars["kind"], vars["ruleID"], vars["attr"], req.Body, device, userAPI)
+ }),
+ ).Methods(http.MethodPut)
+
// Element user settings
v3mux.Handle("/profile/{userID}",
@@ -885,6 +1001,27 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)
+ v3mux.Handle("/notifications",
+ httputil.MakeAuthAPI("get_notifications", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return GetNotifications(req, device, userAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushers",
+ httputil.MakeAuthAPI("get_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return GetPushers(req, device, userAPI)
+ }),
+ ).Methods(http.MethodGet, http.MethodOptions)
+
+ v3mux.Handle("/pushers/set",
+ httputil.MakeAuthAPI("set_pushers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ if r := rateLimits.Limit(req); r != nil {
+ return *r
+ }
+ return SetPusher(req, device, userAPI)
+ }),
+ ).Methods(http.MethodPost, http.MethodOptions)
+
// Stub implementations for sytest
v3mux.Handle("/events",
httputil.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {