diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 84f5891b..2d484908 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -814,7 +814,7 @@ func Setup( if r := rateLimits.rateLimit(req); r != nil { return *r } - return SetPushersByLocalpart(req, userAPI, device) + return SetPusherByLocalpart(req, userAPI, device) }), ).Methods(http.MethodPost, http.MethodOptions) diff --git a/userapi/api/api.go b/userapi/api/api.go index c70fb018..b2b9e98e 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -29,6 +29,7 @@ type UserInternalAPI interface { PerformPasswordUpdate(ctx context.Context, req *PerformPasswordUpdateRequest, res *PerformPasswordUpdateResponse) error PerformDeviceCreation(ctx context.Context, req *PerformDeviceCreationRequest, res *PerformDeviceCreationResponse) error PerformDeviceDeletion(ctx context.Context, req *PerformDeviceDeletionRequest, res *PerformDeviceDeletionResponse) error + PerformPusherDeletion(ctx context.Context, req *PerformPusherDeletionRequest, res *PerformPusherDeletionResponse) error PerformLastSeenUpdate(ctx context.Context, req *PerformLastSeenUpdateRequest, res *PerformLastSeenUpdateResponse) error PerformDeviceUpdate(ctx context.Context, req *PerformDeviceUpdateRequest, res *PerformDeviceUpdateResponse) error PerformAccountDeactivation(ctx context.Context, req *PerformAccountDeactivationRequest, res *PerformAccountDeactivationResponse) error @@ -78,6 +79,14 @@ type PerformDeviceDeletionRequest struct { type PerformDeviceDeletionResponse struct { } +type PerformPusherDeletionRequest struct { + PushKey string + UserID string +} + +type PerformPusherDeletionResponse struct { +} + // QueryDeviceInfosRequest is the request to QueryDeviceInfos type QueryDeviceInfosRequest struct { DeviceIDs []string diff --git a/userapi/internal/api.go b/userapi/internal/api.go index e0a00fc9..da4b91a3 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -151,6 +151,23 @@ func (a *UserInternalAPI) PerformDeviceDeletion(ctx context.Context, req *api.Pe return a.deviceListUpdate(req.UserID, deletedDeviceIDs) } +func (a *UserInternalAPI) PerformPusherDeletion(ctx context.Context, req *api.PerformPusherDeletionRequest, res *api.PerformPusherDeletionResponse) error { + util.GetLogger(ctx).WithField("user_id", req.UserID).WithField("pushkey", req.PushKey).Info("PerformPusherDeletion") + local, domain, err := gomatrixserverlib.SplitID('@', req.UserID) + if err != nil { + return err + } + if domain != a.ServerName { + return fmt.Errorf("cannot PerformPusherDeletion of remote users: got %s want %s", domain, a.ServerName) + } + err = a.PusherDB.RemovePusher(ctx, local, req.PushKey) + if err != nil { + return err + } + // create empty device keys and upload them to delete what was once there and trigger device list changes + return nil +} + func (a *UserInternalAPI) deviceListUpdate(userID string, deviceIDs []string) error { deviceKeys := make([]keyapi.DeviceKeys, len(deviceIDs)) for i, did := range deviceIDs { diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index aa3ab609..b141e484 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -32,6 +32,7 @@ const ( PerformAccountCreationPath = "/userapi/performAccountCreation" PerformPasswordUpdatePath = "/userapi/performPasswordUpdate" PerformDeviceDeletionPath = "/userapi/performDeviceDeletion" + PerformPusherDeletionPath = "/userapi/performPusherDeletion" PerformLastSeenUpdatePath = "/userapi/performLastSeenUpdate" PerformDeviceUpdatePath = "/userapi/performDeviceUpdate" PerformAccountDeactivationPath = "/userapi/performAccountDeactivation" @@ -123,6 +124,18 @@ func (h *httpUserInternalAPI) PerformDeviceDeletion( return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } +func (h *httpUserInternalAPI) PerformPusherDeletion( + ctx context.Context, + request *api.PerformPusherDeletionRequest, + response *api.PerformPusherDeletionResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformPusherDeletion") + defer span.Finish() + + apiURL := h.apiURL + PerformPusherDeletionPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + func (h *httpUserInternalAPI) PerformLastSeenUpdate( ctx context.Context, req *api.PerformLastSeenUpdateRequest, diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index 5fddb2a8..97cddf9d 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -104,6 +104,19 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(PerformPusherDeletionPath, + httputil.MakeInternalAPI("performPusherDeletion", func(req *http.Request) util.JSONResponse { + request := api.PerformPusherDeletionRequest{} + response := api.PerformPusherDeletionResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := s.PerformPusherDeletion(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) internalAPIMux.Handle(PerformAccountDeactivationPath, httputil.MakeInternalAPI("performAccountDeactivation", func(req *http.Request) util.JSONResponse { request := api.PerformAccountDeactivationRequest{} diff --git a/userapi/storage/pushers/interface.go b/userapi/storage/pushers/interface.go index e54cf114..f6559d5a 100644 --- a/userapi/storage/pushers/interface.go +++ b/userapi/storage/pushers/interface.go @@ -22,4 +22,6 @@ import ( type Database interface { GetPushersByLocalpart(ctx context.Context, localpart string) ([]api.Pusher, error) + GetPusherByPushkey(ctx context.Context, pushkey, localpart string) (*api.Pusher, error) + RemovePusher(ctx context.Context, pushkey, localpart string) error } diff --git a/userapi/storage/pushers/postgres/pushers_table.go b/userapi/storage/pushers/postgres/pushers_table.go index 5452853b..b4d5e16f 100644 --- a/userapi/storage/pushers/postgres/pushers_table.go +++ b/userapi/storage/pushers/postgres/pushers_table.go @@ -64,9 +64,12 @@ const selectPushersByLocalpartSQL = "" + const selectPusherByPushkeySQL = "" + "SELECT pushkey, kind, app_id, app_display_name, device_display_name, profile_tag, lang, url, format FROM pusher_pushers WHERE localpart = $1 AND pushkey = $2" +const deletePusherSQL = "" + + "DELETE FROM pusher_pushers WHERE pushkey = $1 AND localpart = $2" type pushersStatements struct { selectPushersByLocalpartStmt *sql.Stmt selectPusherByPushkeyStmt *sql.Stmt + deletePusherStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -82,10 +85,22 @@ func (s *pushersStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN if s.selectPusherByPushkeyStmt, err = db.Prepare(selectPusherByPushkeySQL); err != nil { return } + if s.deletePusherStmt, err = db.Prepare(deletePusherSQL); err != nil { + return + } s.serverName = server return } +// deletePusher removes a single pusher by pushkey and user localpart. +func (s *pushersStatements) deletePusher( + ctx context.Context, txn *sql.Tx, pushkey, localpart string, +) error { + stmt := sqlutil.TxStmt(txn, s.deletePusherStmt) + _, err := stmt.ExecContext(ctx, pushkey, localpart) + return err +} + func (s *pushersStatements) selectPushersByLocalpart( ctx context.Context, txn *sql.Tx, localpart string, ) ([]api.Pusher, error) { diff --git a/userapi/storage/pushers/postgres/storage.go b/userapi/storage/pushers/postgres/storage.go index ca45b80f..e16a85a7 100644 --- a/userapi/storage/pushers/postgres/storage.go +++ b/userapi/storage/pushers/postgres/storage.go @@ -55,16 +55,30 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver return &Database{db, d}, nil } -// GetPushersByLocalpart returns the pusers matching the given localpart. func (d *Database) GetPushersByLocalpart( ctx context.Context, localpart string, ) ([]api.Pusher, error) { return d.pushers.selectPushersByLocalpart(ctx, nil, localpart) } -// GetPushersByPushkey returns the pusers matching the given localpart. -func (d *Database) GetPushersByPushkey( - ctx context.Context, localpart, pushkey string, +// GetPusherByPushkey returns the pusher matching the given localpart. +func (d *Database) GetPusherByPushkey( + ctx context.Context, pushkey, localpart string, ) (*api.Pusher, error) { - return d.pushers.selectPushersByPushkey(ctx, localpart, pushkey) + return d.pushers.selectPusherByPushkey(ctx, localpart, pushkey) +} + +// RemovePusher revokes a pusher by deleting the entry in the database +// matching with the given pushkey and user ID localpart. +// If the pusher doesn't exist, it will not return an error +// If something went wrong during the deletion, it will return the SQL error. +func (d *Database) RemovePusher( + ctx context.Context, pushkey, localpart string, +) error { + return sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { + if err := d.pushers.deletePusher(ctx, txn, pushkey, localpart); err != sql.ErrNoRows { + return err + } + return nil + }) } diff --git a/userapi/storage/pushers/sqlite3/pushers_table.go b/userapi/storage/pushers/sqlite3/pushers_table.go index 31c2fa1c..622f85ce 100644 --- a/userapi/storage/pushers/sqlite3/pushers_table.go +++ b/userapi/storage/pushers/sqlite3/pushers_table.go @@ -48,11 +48,15 @@ const selectPushersByLocalpartSQL = "" + const selectPusherByPushkeySQL = "" + "SELECT pushkey, kind, app_id, app_display_name, device_display_name, profile_tag, lang, url, format FROM pusher_pushers WHERE localpart = $1 AND pushkey = $2" +const deletePusherSQL = "" + + "DELETE FROM pusher_pushers WHERE pushkey = $1 AND localpart = $2" + type pushersStatements struct { db *sql.DB writer sqlutil.Writer selectPushersByLocalpartStmt *sql.Stmt selectPusherByPushkeyStmt *sql.Stmt + deletePusherStmt *sql.Stmt serverName gomatrixserverlib.ServerName } @@ -70,6 +74,9 @@ func (s *pushersStatements) prepare(db *sql.DB, writer sqlutil.Writer, server go if s.selectPusherByPushkeyStmt, err = db.Prepare(selectPusherByPushkeySQL); err != nil { return } + if s.deletePusherStmt, err = db.Prepare(deletePusherSQL); err != nil { + return + } s.serverName = server return } @@ -167,3 +174,11 @@ func (s *pushersStatements) selectPusherByPushkey( } return &pusher, err } + +func (s *pushersStatements) deletePusher( + ctx context.Context, txn *sql.Tx, id, localpart string, +) error { + stmt := sqlutil.TxStmt(txn, s.deletePusherStmt) + _, err := stmt.ExecContext(ctx, id, localpart) + return err +} diff --git a/userapi/storage/pushers/sqlite3/storage.go b/userapi/storage/pushers/sqlite3/storage.go index 43522528..e89f9548 100644 --- a/userapi/storage/pushers/sqlite3/storage.go +++ b/userapi/storage/pushers/sqlite3/storage.go @@ -65,8 +65,23 @@ func (d *Database) GetPushersByLocalpart( } // GetPushersByLocalpart returns the pushers matching the given localpart. -func (d *Database) GetPushersByPushkey( - ctx context.Context, localpart, pushkey string, +func (d *Database) GetPusherByPushkey( + ctx context.Context, pushkey, localpart string, ) (*api.Pusher, error) { - return d.pushers.selectPusherByPushkey(ctx, localpart, pushkey) + return d.pushers.selectPusherByPushkey(ctx, pushkey, localpart) +} + +// RemovePusher revokes a pusher by deleting the entry in the database +// matching with the given pusher pushkey and user ID localpart. +// If the pusher doesn't exist, it will not return an error +// If something went wrong during the deletion, it will return the SQL error. +func (d *Database) RemovePusher( + ctx context.Context, pushkey, localpart string, +) error { + return d.writer.Do(d.db, nil, func(txn *sql.Tx) error { + if err := d.pushers.deletePusher(ctx, txn, pushkey, localpart); err != sql.ErrNoRows { + return err + } + return nil + }) }