diff --git a/clientapi/admin_test.go b/clientapi/admin_test.go index cf1c4ef30..69a815321 100644 --- a/clientapi/admin_test.go +++ b/clientapi/admin_test.go @@ -142,8 +142,8 @@ func TestPurgeRoom(t *testing.T) { // this starts the JetStream consumers syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics) - federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) - rsAPI.SetFederationAPI(nil, nil) + fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) + rsAPI.SetFederationAPI(fsAPI, nil) // Create the room if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil { diff --git a/clientapi/clientapi_test.go b/clientapi/clientapi_test.go index 09460ea13..2c34c1098 100644 --- a/clientapi/clientapi_test.go +++ b/clientapi/clientapi_test.go @@ -1758,3 +1758,377 @@ func (d dummyStore) GetEncryptionEvent(roomID id.RoomID) *event.EncryptionEventC func (d dummyStore) FindSharedRooms(userID id.UserID) []id.RoomID { return []id.RoomID{} } + +func TestKeyBackup(t *testing.T) { + alice := test.NewUser(t) + + handleResponseCode := func(t *testing.T, rec *httptest.ResponseRecorder, expectedCode int) { + t.Helper() + if rec.Code != expectedCode { + t.Fatalf("expected HTTP %d, but got %d: %s", expectedCode, rec.Code, rec.Body.String()) + } + } + + testCases := []struct { + name string + request func(t *testing.T) *http.Request + validate func(t *testing.T, rec *httptest.ResponseRecorder) + }{ + { + name: "can not create backup with invalid JSON", + request: func(t *testing.T) *http.Request { + reqBody := strings.NewReader(`{"algorithm":"m.megolm_backup.v1"`) // missing closing braces + return httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/room_keys/version", reqBody) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusBadRequest) + }, + }, + { + name: "can not create backup with missing auth_data", // as this would result in MarshalJSON errors when querying again + request: func(t *testing.T) *http.Request { + reqBody := strings.NewReader(`{"algorithm":"m.megolm_backup.v1"}`) + return httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/room_keys/version", reqBody) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusBadRequest) + }, + }, + { + name: "can create backup", + request: func(t *testing.T) *http.Request { + reqBody := strings.NewReader(`{"algorithm":"m.megolm_backup.v1","auth_data":{"data":"random"}}`) + return httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/room_keys/version", reqBody) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + wantVersion := "1" + if gotVersion := gjson.GetBytes(rec.Body.Bytes(), "version").Str; gotVersion != wantVersion { + t.Fatalf("expected version '%s', got '%s'", wantVersion, gotVersion) + } + }, + }, + { + name: "can not query backup for invalid version", + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/room_keys/version/1337", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusNotFound) + }, + }, + { + name: "can not query backup for invalid version string", + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/room_keys/version/notanumber", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusNotFound) + }, + }, + { + name: "can query backup", + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/room_keys/version", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + wantVersion := "1" + if gotVersion := gjson.GetBytes(rec.Body.Bytes(), "version").Str; gotVersion != wantVersion { + t.Fatalf("expected version '%s', got '%s'", wantVersion, gotVersion) + } + }, + }, + { + name: "can query backup without returning rooms", + request: func(t *testing.T) *http.Request { + req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys", test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + if gotRooms := gjson.GetBytes(rec.Body.Bytes(), "rooms").Map(); len(gotRooms) > 0 { + t.Fatalf("expected no rooms in version, but got %#v", gotRooms) + } + }, + }, + { + name: "can query backup for invalid room", + request: func(t *testing.T) *http.Request { + req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!abc:test", test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + if gotSessions := gjson.GetBytes(rec.Body.Bytes(), "sessions").Map(); len(gotSessions) > 0 { + t.Fatalf("expected no sessions in version, but got %#v", gotSessions) + } + }, + }, + { + name: "can not query backup for invalid session", + request: func(t *testing.T) *http.Request { + req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!abc:test/doesnotexist", test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusNotFound) + }, + }, + { + name: "can not update backup with missing version", + request: func(t *testing.T) *http.Request { + return test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys") + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusBadRequest) + }, + }, + { + name: "can not update backup with invalid data", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, "") + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys", reqBody, test.WithQueryParams(map[string]string{ + "version": "0", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusBadRequest) + }, + }, + { + name: "can not update backup with wrong version", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, map[string]interface{}{ + "rooms": map[string]interface{}{ + "!testroom:test": map[string]interface{}{ + "sessions": map[string]uapi.KeyBackupSession{}, + }, + }, + }) + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys", reqBody, test.WithQueryParams(map[string]string{ + "version": "5", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusForbidden) + }, + }, + { + name: "can update backup with correct version", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, map[string]interface{}{ + "rooms": map[string]interface{}{ + "!testroom:test": map[string]interface{}{ + "sessions": map[string]uapi.KeyBackupSession{ + "dummySession": { + FirstMessageIndex: 1, + }, + }, + }, + }, + }) + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys", reqBody, test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + }, + }, + { + name: "can update backup with correct version for specific room", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, map[string]interface{}{ + "sessions": map[string]uapi.KeyBackupSession{ + "dummySession": { + FirstMessageIndex: 1, + IsVerified: true, + SessionData: json.RawMessage("{}"), + }, + }, + }) + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys/!testroom:test", reqBody, test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + t.Logf("%#v", rec.Body.String()) + }, + }, + { + name: "can update backup with correct version for specific room and session", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, uapi.KeyBackupSession{ + FirstMessageIndex: 1, + SessionData: json.RawMessage("{}"), + IsVerified: true, + ForwardedCount: 0, + }) + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys/!testroom:test/dummySession", reqBody, test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + }, + }, + { + name: "can update backup by version", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, uapi.KeyBackupSession{ + FirstMessageIndex: 1, + SessionData: json.RawMessage("{}"), + IsVerified: true, + ForwardedCount: 0, + }) + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/version/1", reqBody, test.WithQueryParams(map[string]string{"version": "1"})) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + t.Logf("%#v", rec.Body.String()) + }, + }, + { + name: "can not update backup by version for invalid version", + request: func(t *testing.T) *http.Request { + reqBody := test.WithJSONBody(t, uapi.KeyBackupSession{ + FirstMessageIndex: 1, + SessionData: json.RawMessage("{}"), + IsVerified: true, + ForwardedCount: 0, + }) + req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/version/2", reqBody, test.WithQueryParams(map[string]string{"version": "1"})) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + }, + }, + { + name: "can query backup sessions", + request: func(t *testing.T) *http.Request { + req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys", test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + if gotRooms := gjson.GetBytes(rec.Body.Bytes(), "rooms").Map(); len(gotRooms) != 1 { + t.Fatalf("expected one room in response, but got %#v", rec.Body.String()) + } + }, + }, + { + name: "can query backup sessions by room", + request: func(t *testing.T) *http.Request { + req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!testroom:test", test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + if gotRooms := gjson.GetBytes(rec.Body.Bytes(), "sessions").Map(); len(gotRooms) != 1 { + t.Fatalf("expected one session in response, but got %#v", rec.Body.String()) + } + }, + }, + { + name: "can query backup sessions by room and sessionID", + request: func(t *testing.T) *http.Request { + req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!testroom:test/dummySession", test.WithQueryParams(map[string]string{ + "version": "1", + })) + return req + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + if !gjson.GetBytes(rec.Body.Bytes(), "is_verified").Bool() { + t.Fatalf("expected session to be verified, but wasn't: %#v", rec.Body.String()) + } + }, + }, + { + name: "can not delete invalid version backup", + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/2", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusNotFound) + }, + }, + { + name: "can delete version backup", + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/1", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + }, + }, + { + name: "deleting the same backup version twice doesn't error", + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/1", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusOK) + }, + }, + { + name: "deleting an empty version doesn't work", // make sure we can't delete an empty backup version. Handled at the router level + request: func(t *testing.T) *http.Request { + return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/", nil) + }, + validate: func(t *testing.T, rec *httptest.ResponseRecorder) { + handleResponseCode(t, rec, http.StatusNotFound) + }, + }, + } + + test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) { + cfg, processCtx, close := testrig.CreateConfig(t, dbType) + cfg.ClientAPI.RateLimiting.Enabled = false + caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics) + natsInstance := jetstream.NATSInstance{} + defer close() + + routers := httputil.NewRouters() + cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions) + rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics) + userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil) + + // We mostly need the rsAPI for this test, so nil for other APIs/caches etc. + AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) + + accessTokens := map[*test.User]userDevice{ + alice: {}, + } + createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rec := httptest.NewRecorder() + req := tc.request(t) + req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken) + routers.Client.ServeHTTP(rec, req) + tc.validate(t, rec) + }) + } + }) +} diff --git a/clientapi/jsonerror/jsonerror.go b/clientapi/jsonerror/jsonerror.go index be7d13a96..436e168ab 100644 --- a/clientapi/jsonerror/jsonerror.go +++ b/clientapi/jsonerror/jsonerror.go @@ -171,6 +171,23 @@ func LeaveServerNoticeError() *MatrixError { } } +// ErrRoomKeysVersion is an error returned by `PUT /room_keys/keys` +type ErrRoomKeysVersion struct { + MatrixError + CurrentVersion string `json:"current_version"` +} + +// WrongBackupVersionError is an error returned by `PUT /room_keys/keys` +func WrongBackupVersionError(currentVersion string) *ErrRoomKeysVersion { + return &ErrRoomKeysVersion{ + MatrixError: MatrixError{ + ErrCode: "M_WRONG_ROOM_KEYS_VERSION", + Err: "Wrong backup version.", + }, + CurrentVersion: currentVersion, + } +} + type IncompatibleRoomVersionError struct { RoomVersion string `json:"room_version"` Error string `json:"error"` diff --git a/clientapi/routing/admin.go b/clientapi/routing/admin.go index 809d486d2..a5fc4ec48 100644 --- a/clientapi/routing/admin.go +++ b/clientapi/routing/admin.go @@ -3,12 +3,14 @@ package routing import ( "context" "encoding/json" + "errors" "fmt" "net/http" "time" "github.com/gorilla/mux" "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" @@ -28,88 +30,60 @@ func AdminEvacuateRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAP if err != nil { return util.ErrorResponse(err) } - res := &roomserverAPI.PerformAdminEvacuateRoomResponse{} - if err := rsAPI.PerformAdminEvacuateRoom( - req.Context(), - &roomserverAPI.PerformAdminEvacuateRoomRequest{ - RoomID: vars["roomID"], - }, - res, - ); err != nil { + + affected, err := rsAPI.PerformAdminEvacuateRoom(req.Context(), vars["roomID"]) + switch err { + case nil: + case eventutil.ErrRoomNoExists: + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound(err.Error()), + } + default: + logrus.WithError(err).WithField("roomID", vars["roomID"]).Error("Failed to evacuate room") return util.ErrorResponse(err) } - if err := res.Error; err != nil { - return err.JSONResponse() - } return util.JSONResponse{ Code: 200, JSON: map[string]interface{}{ - "affected": res.Affected, + "affected": affected, }, } } -func AdminEvacuateUser(req *http.Request, cfg *config.ClientAPI, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { +func AdminEvacuateUser(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } - userID := vars["userID"] - _, domain, err := gomatrixserverlib.SplitID('@', userID) + affected, err := rsAPI.PerformAdminEvacuateUser(req.Context(), vars["userID"]) if err != nil { + logrus.WithError(err).WithField("userID", vars["userID"]).Error("Failed to evacuate user") return util.MessageResponse(http.StatusBadRequest, err.Error()) } - if !cfg.Matrix.IsLocalServerName(domain) { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.MissingArgument("User ID must belong to this server."), - } - } - res := &roomserverAPI.PerformAdminEvacuateUserResponse{} - if err := rsAPI.PerformAdminEvacuateUser( - req.Context(), - &roomserverAPI.PerformAdminEvacuateUserRequest{ - UserID: userID, - }, - res, - ); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if err := res.Error; err != nil { - return err.JSONResponse() - } + return util.JSONResponse{ Code: 200, JSON: map[string]interface{}{ - "affected": res.Affected, + "affected": affected, }, } } -func AdminPurgeRoom(req *http.Request, cfg *config.ClientAPI, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { +func AdminPurgeRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } - roomID := vars["roomID"] - res := &roomserverAPI.PerformAdminPurgeRoomResponse{} - if err := rsAPI.PerformAdminPurgeRoom( - context.Background(), - &roomserverAPI.PerformAdminPurgeRoomRequest{ - RoomID: roomID, - }, - res, - ); err != nil { + if err = rsAPI.PerformAdminPurgeRoom(context.Background(), vars["roomID"]); err != nil { return util.ErrorResponse(err) } - if err := res.Error; err != nil { - return err.JSONResponse() - } + return util.JSONResponse{ Code: 200, - JSON: res, + JSON: struct{}{}, } } @@ -238,7 +212,7 @@ func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.Clien } } -func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { +func AdminDownloadState(req *http.Request, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) @@ -257,23 +231,22 @@ func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *api.De JSON: jsonerror.MissingArgument("Expecting remote server name."), } } - res := &roomserverAPI.PerformAdminDownloadStateResponse{} - if err := rsAPI.PerformAdminDownloadState( - req.Context(), - &roomserverAPI.PerformAdminDownloadStateRequest{ - UserID: device.UserID, - RoomID: roomID, - ServerName: spec.ServerName(serverName), - }, - res, - ); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if err := res.Error; err != nil { - return err.JSONResponse() + if err = rsAPI.PerformAdminDownloadState(req.Context(), roomID, device.UserID, spec.ServerName(serverName)); err != nil { + if errors.Is(err, eventutil.ErrRoomNoExists) { + return util.JSONResponse{ + Code: 200, + JSON: jsonerror.NotFound(eventutil.ErrRoomNoExists.Error()), + } + } + logrus.WithError(err).WithFields(logrus.Fields{ + "userID": device.UserID, + "serverName": serverName, + "roomID": roomID, + }).Error("failed to download state") + return util.ErrorResponse(err) } return util.JSONResponse{ Code: 200, - JSON: map[string]interface{}{}, + JSON: struct{}{}, } } diff --git a/clientapi/routing/createroom.go b/clientapi/routing/createroom.go index 0d9a9d2a8..a64a735fc 100644 --- a/clientapi/routing/createroom.go +++ b/clientapi/routing/createroom.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/getsentry/sentry-go" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/types" @@ -544,9 +545,10 @@ func createRoom( } // Process the invites. + var inviteEvent *types.HeaderedEvent for _, invitee := range r.Invite { // Build the invite event. - inviteEvent, err := buildMembershipEvent( + inviteEvent, err = buildMembershipEvent( ctx, invitee, "", profileAPI, device, spec.Invite, roomID, r.IsDirect, cfg, evTime, rsAPI, asAPI, ) @@ -559,38 +561,44 @@ func createRoom( fclient.NewInviteV2StrippedState(inviteEvent.PDU), ) // Send the invite event to the roomserver. - var inviteRes roomserverAPI.PerformInviteResponse event := inviteEvent - if err := rsAPI.PerformInvite(ctx, &roomserverAPI.PerformInviteRequest{ + err = rsAPI.PerformInvite(ctx, &roomserverAPI.PerformInviteRequest{ Event: event, InviteRoomState: inviteStrippedState, RoomVersion: event.Version(), SendAsServer: string(userDomain), - }, &inviteRes); err != nil { + }) + switch e := err.(type) { + case roomserverAPI.ErrInvalidID: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown(e.Error()), + } + case roomserverAPI.ErrNotAllowed: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(e.Error()), + } + case nil: + default: util.GetLogger(ctx).WithError(err).Error("PerformInvite failed") + sentry.CaptureException(err) return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: jsonerror.InternalServerError(), } } - if inviteRes.Error != nil { - return inviteRes.Error.JSONResponse() - } } } - if r.Visibility == "public" { + if r.Visibility == spec.Public { // expose this room in the published room list - var pubRes roomserverAPI.PerformPublishResponse - if err := rsAPI.PerformPublish(ctx, &roomserverAPI.PerformPublishRequest{ + if err = rsAPI.PerformPublish(ctx, &roomserverAPI.PerformPublishRequest{ RoomID: roomID, - Visibility: "public", - }, &pubRes); err != nil { - return jsonerror.InternalAPIError(ctx, err) - } - if pubRes.Error != nil { - // treat as non-fatal since the room is already made by this point - util.GetLogger(ctx).WithError(pubRes.Error).Error("failed to visibility:public") + Visibility: spec.Public, + }); err != nil { + util.GetLogger(ctx).WithError(err).Error("failed to publish room") + return jsonerror.InternalServerError() } } diff --git a/clientapi/routing/directory.go b/clientapi/routing/directory.go index decaaf3e5..11ae5739c 100644 --- a/clientapi/routing/directory.go +++ b/clientapi/routing/directory.go @@ -304,16 +304,12 @@ func SetVisibility( return *reqErr } - var publishRes roomserverAPI.PerformPublishResponse - if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{ + if err = rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{ RoomID: roomID, Visibility: v.Visibility, - }, &publishRes); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if publishRes.Error != nil { - util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed") - return publishRes.Error.JSONResponse() + }); err != nil { + util.GetLogger(req.Context()).WithError(err).Error("failed to publish room") + return jsonerror.InternalServerError() } return util.JSONResponse{ @@ -342,18 +338,14 @@ func SetVisibilityAS( return *reqErr } } - var publishRes roomserverAPI.PerformPublishResponse if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{ RoomID: roomID, Visibility: v.Visibility, NetworkID: networkID, AppserviceID: dev.AppserviceID, - }, &publishRes); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if publishRes.Error != nil { - util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed") - return publishRes.Error.JSONResponse() + }); err != nil { + util.GetLogger(req.Context()).WithError(err).Error("failed to publish room") + return jsonerror.InternalServerError() } return util.JSONResponse{ diff --git a/clientapi/routing/joinroom.go b/clientapi/routing/joinroom.go index d736a9588..54a9aaa4b 100644 --- a/clientapi/routing/joinroom.go +++ b/clientapi/routing/joinroom.go @@ -15,14 +15,18 @@ package routing import ( + "encoding/json" + "errors" "net/http" "time" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/internal/eventutil" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" ) @@ -41,7 +45,6 @@ func JoinRoomByIDOrAlias( IsGuest: device.AccountType == api.AccountTypeGuest, Content: map[string]interface{}{}, } - joinRes := roomserverAPI.PerformJoinResponse{} // Check to see if any ?server_name= query parameters were // given in the request. @@ -81,37 +84,66 @@ func JoinRoomByIDOrAlias( done := make(chan util.JSONResponse, 1) go func() { defer close(done) - if err := rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes); err != nil { - done <- jsonerror.InternalAPIError(req.Context(), err) - } else if joinRes.Error != nil { - if joinRes.Error.Code == roomserverAPI.PerformErrorNotAllowed && device.AccountType == api.AccountTypeGuest { - done <- util.JSONResponse{ - Code: http.StatusForbidden, - JSON: jsonerror.GuestAccessForbidden(joinRes.Error.Msg), - } - } else { - done <- joinRes.Error.JSONResponse() - } - } else { - done <- util.JSONResponse{ + roomID, _, err := rsAPI.PerformJoin(req.Context(), &joinReq) + var response util.JSONResponse + + switch e := err.(type) { + case nil: // success case + response = util.JSONResponse{ Code: http.StatusOK, // TODO: Put the response struct somewhere internal. JSON: struct { RoomID string `json:"room_id"` - }{joinRes.RoomID}, + }{roomID}, + } + case roomserverAPI.ErrInvalidID: + response = util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown(e.Error()), + } + case roomserverAPI.ErrNotAllowed: + jsonErr := jsonerror.Forbidden(e.Error()) + if device.AccountType == api.AccountTypeGuest { + jsonErr = jsonerror.GuestAccessForbidden(e.Error()) + } + response = util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonErr, + } + case *gomatrix.HTTPError: // this ensures we proxy responses over federation to the client + response = util.JSONResponse{ + Code: e.Code, + JSON: json.RawMessage(e.Message), + } + default: + response = util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.InternalServerError(), + } + if errors.Is(err, eventutil.ErrRoomNoExists) { + response = util.JSONResponse{ + Code: http.StatusNotFound, + JSON: jsonerror.NotFound(e.Error()), + } } } + done <- response }() // Wait either for the join to finish, or for us to hit a reasonable // timeout, at which point we'll just return a 200 to placate clients. + timer := time.NewTimer(time.Second * 20) select { - case <-time.After(time.Second * 20): + case <-timer.C: return util.JSONResponse{ Code: http.StatusAccepted, JSON: jsonerror.Unknown("The room join will continue in the background."), } case result := <-done: + // Stop and drain the timer + if !timer.Stop() { + <-timer.C + } return result } } diff --git a/clientapi/routing/key_backup.go b/clientapi/routing/key_backup.go index b6f8fe1b9..56b05db15 100644 --- a/clientapi/routing/key_backup.go +++ b/clientapi/routing/key_backup.go @@ -61,28 +61,26 @@ func CreateKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de if resErr != nil { return *resErr } - var performKeyBackupResp userapi.PerformKeyBackupResponse - if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ + if len(kb.AuthData) == 0 { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("missing auth_data"), + } + } + version, err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ UserID: device.UserID, Version: "", AuthData: kb.AuthData, Algorithm: kb.Algorithm, - }, &performKeyBackupResp); err != nil { - return jsonerror.InternalServerError() - } - if performKeyBackupResp.Error != "" { - if performKeyBackupResp.BadInput { - return util.JSONResponse{ - Code: 400, - JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error), - } - } - return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error)) + }) + if err != nil { + return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", err)) } + return util.JSONResponse{ Code: 200, JSON: keyBackupVersionCreateResponse{ - Version: performKeyBackupResp.Version, + Version: version, }, } } @@ -90,15 +88,12 @@ func CreateKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de // KeyBackupVersion returns the key backup version specified. If `version` is empty, the latest `keyBackupVersionResponse` is returned. // Implements GET /_matrix/client/r0/room_keys/version and GET /_matrix/client/r0/room_keys/version/{version} func KeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse { - var queryResp userapi.QueryKeyBackupResponse - if err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{ + queryResp, err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{ UserID: device.UserID, Version: version, - }, &queryResp); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if queryResp.Error != "" { - return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", queryResp.Error)) + }) + if err != nil { + return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", err)) } if !queryResp.Exists { return util.JSONResponse{ @@ -126,31 +121,29 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse if resErr != nil { return *resErr } - var performKeyBackupResp userapi.PerformKeyBackupResponse - if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ + performKeyBackupResp, err := userAPI.UpdateBackupKeyAuthData(req.Context(), &userapi.PerformKeyBackupRequest{ UserID: device.UserID, Version: version, AuthData: kb.AuthData, Algorithm: kb.Algorithm, - }, &performKeyBackupResp); err != nil { - return jsonerror.InternalServerError() - } - if performKeyBackupResp.Error != "" { - if performKeyBackupResp.BadInput { - return util.JSONResponse{ - Code: 400, - JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error), - } + }) + switch e := err.(type) { + case *jsonerror.ErrRoomKeysVersion: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: e, } - return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error)) + case nil: + default: + return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", e)) } + if !performKeyBackupResp.Exists { return util.JSONResponse{ Code: 404, JSON: jsonerror.NotFound("backup version not found"), } } - // Unclear what the 200 body should be return util.JSONResponse{ Code: 200, JSON: keyBackupVersionCreateResponse{ @@ -162,35 +155,19 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse // Delete a version of key backup. Version must not be empty. If the key backup was previously deleted, will return 200 OK. // Implements DELETE /_matrix/client/r0/room_keys/version/{version} func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse { - var performKeyBackupResp userapi.PerformKeyBackupResponse - if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ - UserID: device.UserID, - Version: version, - DeleteBackup: true, - }, &performKeyBackupResp); err != nil { - return jsonerror.InternalServerError() + exists, err := userAPI.DeleteKeyBackup(req.Context(), device.UserID, version) + if err != nil { + return util.ErrorResponse(fmt.Errorf("DeleteKeyBackup: %s", err)) } - if performKeyBackupResp.Error != "" { - if performKeyBackupResp.BadInput { - return util.JSONResponse{ - Code: 400, - JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error), - } - } - return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error)) - } - if !performKeyBackupResp.Exists { + if !exists { return util.JSONResponse{ Code: 404, JSON: jsonerror.NotFound("backup version not found"), } } - // Unclear what the 200 body should be return util.JSONResponse{ Code: 200, - JSON: keyBackupVersionCreateResponse{ - Version: performKeyBackupResp.Version, - }, + JSON: struct{}{}, } } @@ -198,22 +175,21 @@ func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de func UploadBackupKeys( req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string, keys *keyBackupSessionRequest, ) util.JSONResponse { - var performKeyBackupResp userapi.PerformKeyBackupResponse - if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ + performKeyBackupResp, err := userAPI.UpdateBackupKeyAuthData(req.Context(), &userapi.PerformKeyBackupRequest{ UserID: device.UserID, Version: version, Keys: *keys, - }, &performKeyBackupResp); err != nil && performKeyBackupResp.Error == "" { - return jsonerror.InternalServerError() - } - if performKeyBackupResp.Error != "" { - if performKeyBackupResp.BadInput { - return util.JSONResponse{ - Code: 400, - JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error), - } + }) + + switch e := err.(type) { + case *jsonerror.ErrRoomKeysVersion: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: e, } - return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error)) + case nil: + default: + return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", e)) } if !performKeyBackupResp.Exists { return util.JSONResponse{ @@ -234,18 +210,15 @@ func UploadBackupKeys( func GetBackupKeys( req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version, roomID, sessionID string, ) util.JSONResponse { - var queryResp userapi.QueryKeyBackupResponse - if err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{ + queryResp, err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{ UserID: device.UserID, Version: version, ReturnKeys: true, KeysForRoomID: roomID, KeysForSessionID: sessionID, - }, &queryResp); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if queryResp.Error != "" { - return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", queryResp.Error)) + }) + if err != nil { + return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %w", err)) } if !queryResp.Exists { return util.JSONResponse{ @@ -267,17 +240,20 @@ func GetBackupKeys( } } else if roomID != "" { roomData, ok := queryResp.Keys[roomID] - if ok { - // wrap response in "sessions" - return util.JSONResponse{ - Code: 200, - JSON: struct { - Sessions map[string]userapi.KeyBackupSession `json:"sessions"` - }{ - Sessions: roomData, - }, - } + if !ok { + // If no keys are found, then an object with an empty sessions property will be returned + roomData = make(map[string]userapi.KeyBackupSession) } + // wrap response in "sessions" + return util.JSONResponse{ + Code: 200, + JSON: struct { + Sessions map[string]userapi.KeyBackupSession `json:"sessions"` + }{ + Sessions: roomData, + }, + } + } else { // response is the same as the upload request var resp keyBackupSessionRequest diff --git a/clientapi/routing/membership.go b/clientapi/routing/membership.go index 827b5f29c..a4aa963a7 100644 --- a/clientapi/routing/membership.go +++ b/clientapi/routing/membership.go @@ -20,6 +20,7 @@ import ( "net/http" "time" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" @@ -265,22 +266,33 @@ func sendInvite( return jsonerror.InternalServerError(), err } - var inviteRes api.PerformInviteResponse - if err := rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{ + err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{ Event: event, InviteRoomState: nil, // ask the roomserver to draw up invite room state for us RoomVersion: event.Version(), SendAsServer: string(device.UserDomain()), - }, &inviteRes); err != nil { + }) + + switch e := err.(type) { + case roomserverAPI.ErrInvalidID: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown(e.Error()), + }, e + case roomserverAPI.ErrNotAllowed: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(e.Error()), + }, e + case nil: + default: util.GetLogger(ctx).WithError(err).Error("PerformInvite failed") + sentry.CaptureException(err) return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: jsonerror.InternalServerError(), }, err } - if inviteRes.Error != nil { - return inviteRes.Error.JSONResponse(), inviteRes.Error - } return util.JSONResponse{ Code: http.StatusOK, diff --git a/clientapi/routing/peekroom.go b/clientapi/routing/peekroom.go index 9a8f4378a..3937b9ad2 100644 --- a/clientapi/routing/peekroom.go +++ b/clientapi/routing/peekroom.go @@ -15,13 +15,16 @@ package routing import ( + "encoding/json" "net/http" "github.com/matrix-org/dendrite/clientapi/jsonerror" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" + "github.com/sirupsen/logrus" ) func PeekRoomByIDOrAlias( @@ -41,8 +44,6 @@ func PeekRoomByIDOrAlias( UserID: device.UserID, DeviceID: device.ID, } - peekRes := roomserverAPI.PerformPeekResponse{} - // Check to see if any ?server_name= query parameters were // given in the request. if serverNames, ok := req.URL.Query()["server_name"]; ok { @@ -55,11 +56,27 @@ func PeekRoomByIDOrAlias( } // Ask the roomserver to perform the peek. - if err := rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes); err != nil { - return util.ErrorResponse(err) - } - if peekRes.Error != nil { - return peekRes.Error.JSONResponse() + roomID, err := rsAPI.PerformPeek(req.Context(), &peekReq) + switch e := err.(type) { + case roomserverAPI.ErrInvalidID: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown(e.Error()), + } + case roomserverAPI.ErrNotAllowed: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(e.Error()), + } + case *gomatrix.HTTPError: + return util.JSONResponse{ + Code: e.Code, + JSON: json.RawMessage(e.Message), + } + case nil: + default: + logrus.WithError(err).WithField("roomID", roomIDOrAlias).Errorf("Failed to peek room") + return jsonerror.InternalServerError() } // if this user is already joined to the room, we let them peek anyway @@ -75,7 +92,7 @@ func PeekRoomByIDOrAlias( // TODO: Put the response struct somewhere internal. JSON: struct { RoomID string `json:"room_id"` - }{peekRes.RoomID}, + }{roomID}, } } @@ -85,18 +102,17 @@ func UnpeekRoomByID( rsAPI roomserverAPI.ClientRoomserverAPI, roomID string, ) util.JSONResponse { - unpeekReq := roomserverAPI.PerformUnpeekRequest{ - RoomID: roomID, - UserID: device.UserID, - DeviceID: device.ID, - } - unpeekRes := roomserverAPI.PerformUnpeekResponse{} - - if err := rsAPI.PerformUnpeek(req.Context(), &unpeekReq, &unpeekRes); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - if unpeekRes.Error != nil { - return unpeekRes.Error.JSONResponse() + err := rsAPI.PerformUnpeek(req.Context(), roomID, device.UserID, device.ID) + switch e := err.(type) { + case roomserverAPI.ErrInvalidID: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown(e.Error()), + } + case nil: + default: + logrus.WithError(err).WithField("roomID", roomID).Errorf("Failed to un-peek room") + return jsonerror.InternalServerError() } return util.JSONResponse{ diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index aa9b41fc5..70299e14d 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -162,13 +162,13 @@ func Setup( dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}", httputil.MakeAdminAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return AdminEvacuateUser(req, cfg, rsAPI) + return AdminEvacuateUser(req, rsAPI) }), ).Methods(http.MethodPost, http.MethodOptions) dendriteAdminRouter.Handle("/admin/purgeRoom/{roomID}", httputil.MakeAdminAPI("admin_purge_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return AdminPurgeRoom(req, cfg, device, rsAPI) + return AdminPurgeRoom(req, rsAPI) }), ).Methods(http.MethodPost, http.MethodOptions) @@ -180,7 +180,7 @@ func Setup( dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}", httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return AdminDownloadState(req, cfg, device, rsAPI) + return AdminDownloadState(req, device, rsAPI) }), ).Methods(http.MethodGet, http.MethodOptions) diff --git a/clientapi/routing/upgrade_room.go b/clientapi/routing/upgrade_room.go index 34c7eb004..f0936db1f 100644 --- a/clientapi/routing/upgrade_room.go +++ b/clientapi/routing/upgrade_room.go @@ -15,11 +15,13 @@ package routing import ( + "errors" "net/http" appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/internal/eventutil" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/version" "github.com/matrix-org/dendrite/setup/config" @@ -57,38 +59,28 @@ func UpgradeRoom( } } - upgradeReq := roomserverAPI.PerformRoomUpgradeRequest{ - UserID: device.UserID, - RoomID: roomID, - RoomVersion: gomatrixserverlib.RoomVersion(r.NewVersion), - } - upgradeResp := roomserverAPI.PerformRoomUpgradeResponse{} - - if err := rsAPI.PerformRoomUpgrade(req.Context(), &upgradeReq, &upgradeResp); err != nil { - return jsonerror.InternalAPIError(req.Context(), err) - } - - if upgradeResp.Error != nil { - if upgradeResp.Error.Code == roomserverAPI.PerformErrorNoRoom { + newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, device.UserID, gomatrixserverlib.RoomVersion(r.NewVersion)) + switch e := err.(type) { + case nil: + case roomserverAPI.ErrNotAllowed: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(e.Error()), + } + default: + if errors.Is(err, eventutil.ErrRoomNoExists) { return util.JSONResponse{ Code: http.StatusNotFound, JSON: jsonerror.NotFound("Room does not exist"), } - } else if upgradeResp.Error.Code == roomserverAPI.PerformErrorNotAllowed { - return util.JSONResponse{ - Code: http.StatusForbidden, - JSON: jsonerror.Forbidden(upgradeResp.Error.Msg), - } - } else { - return jsonerror.InternalServerError() } - + return jsonerror.InternalServerError() } return util.JSONResponse{ Code: http.StatusOK, JSON: upgradeRoomResponse{ - ReplacementRoom: upgradeResp.NewRoomID, + ReplacementRoom: newRoomID, }, } } diff --git a/federationapi/routing/invite.go b/federationapi/routing/invite.go index 7cc2fef92..0fcb64145 100644 --- a/federationapi/routing/invite.go +++ b/federationapi/routing/invite.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" + "github.com/getsentry/sentry-go" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/types" @@ -205,17 +206,36 @@ func processInvite( SendAsServer: string(api.DoNotSendToOtherServers), TransactionID: nil, } - response := &api.PerformInviteResponse{} - if err := rsAPI.PerformInvite(ctx, request, response); err != nil { + + if err = rsAPI.PerformInvite(ctx, request); err != nil { util.GetLogger(ctx).WithError(err).Error("PerformInvite failed") return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: jsonerror.InternalServerError(), } } - if response.Error != nil { - return response.Error.JSONResponse() + + switch e := err.(type) { + case api.ErrInvalidID: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.Unknown(e.Error()), + } + case api.ErrNotAllowed: + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden(e.Error()), + } + case nil: + default: + util.GetLogger(ctx).WithError(err).Error("PerformInvite failed") + sentry.CaptureException(err) + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.InternalServerError(), + } } + // Return the signed event to the originating server, it should then tell // the other servers in the room that we have been invited. if isInviteV2 { diff --git a/relayapi/internal/perform.go b/relayapi/internal/perform.go index 457652112..79d600abf 100644 --- a/relayapi/internal/perform.go +++ b/relayapi/internal/perform.go @@ -101,11 +101,11 @@ func (r *RelayInternalAPI) QueryTransactions( userID spec.UserID, previousEntry fclient.RelayEntry, ) (api.QueryRelayTransactionsResponse, error) { - logrus.Infof("QueryTransactions for %s", userID.Raw()) + logrus.Infof("QueryTransactions for %s", userID.String()) if previousEntry.EntryID > 0 { logrus.Infof("Cleaning previous entry (%v) from db for %s", previousEntry.EntryID, - userID.Raw(), + userID.String(), ) prevReceipt := receipt.NewReceipt(previousEntry.EntryID) err := r.db.CleanTransactions(ctx, userID, []*receipt.Receipt{&prevReceipt}) @@ -123,12 +123,12 @@ func (r *RelayInternalAPI) QueryTransactions( response := api.QueryRelayTransactionsResponse{} if transaction != nil && receipt != nil { - logrus.Infof("Obtained transaction (%v) for %s", transaction.TransactionID, userID.Raw()) + logrus.Infof("Obtained transaction (%v) for %s", transaction.TransactionID, userID.String()) response.Transaction = *transaction response.EntryID = receipt.GetNID() response.EntriesQueued = true } else { - logrus.Infof("No more entries in the queue for %s", userID.Raw()) + logrus.Infof("No more entries in the queue for %s", userID.String()) response.EntryID = 0 response.EntriesQueued = false } diff --git a/relayapi/routing/relaytxn.go b/relayapi/routing/relaytxn.go index 2fc61373a..9a3ced529 100644 --- a/relayapi/routing/relaytxn.go +++ b/relayapi/routing/relaytxn.go @@ -34,7 +34,7 @@ func GetTransactionFromRelay( relayAPI api.RelayInternalAPI, userID spec.UserID, ) util.JSONResponse { - logrus.Infof("Processing relay_txn for %s", userID.Raw()) + logrus.Infof("Processing relay_txn for %s", userID.String()) var previousEntry fclient.RelayEntry if err := json.Unmarshal(fedReq.Content(), &previousEntry); err != nil { diff --git a/relayapi/routing/relaytxn_test.go b/relayapi/routing/relaytxn_test.go index e6d2d9e5d..1041d8e7e 100644 --- a/relayapi/routing/relaytxn_test.go +++ b/relayapi/routing/relaytxn_test.go @@ -35,7 +35,7 @@ func createQuery( prevEntry fclient.RelayEntry, ) fclient.FederationRequest { var federationPathPrefixV1 = "/_matrix/federation/v1" - path := federationPathPrefixV1 + "/relay_txn/" + userID.Raw() + path := federationPathPrefixV1 + "/relay_txn/" + userID.String() request := fclient.NewFederationRequest("GET", userID.Domain(), "relay", path) request.SetContent(prevEntry) diff --git a/relayapi/routing/sendrelay.go b/relayapi/routing/sendrelay.go index e4794dc4d..6ff08e205 100644 --- a/relayapi/routing/sendrelay.go +++ b/relayapi/routing/sendrelay.go @@ -36,7 +36,7 @@ func SendTransactionToRelay( txnID gomatrixserverlib.TransactionID, userID spec.UserID, ) util.JSONResponse { - logrus.Infof("Processing send_relay for %s", userID.Raw()) + logrus.Infof("Processing send_relay for %s", userID.String()) var txnEvents fclient.RelayEvents if err := json.Unmarshal(fedReq.Content(), &txnEvents); err != nil { diff --git a/relayapi/routing/sendrelay_test.go b/relayapi/routing/sendrelay_test.go index 05dfbe6d1..cac109e19 100644 --- a/relayapi/routing/sendrelay_test.go +++ b/relayapi/routing/sendrelay_test.go @@ -52,7 +52,7 @@ func createFederationRequest( content interface{}, ) fclient.FederationRequest { var federationPathPrefixV1 = "/_matrix/federation/v1" - path := federationPathPrefixV1 + "/send_relay/" + string(txnID) + "/" + userID.Raw() + path := federationPathPrefixV1 + "/send_relay/" + string(txnID) + "/" + userID.String() request := fclient.NewFederationRequest("PUT", origin, destination, path) request.SetContent(content) diff --git a/roomserver/api/api.go b/roomserver/api/api.go index ae67a3d7a..2aaecbbf4 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -11,6 +11,25 @@ import ( userapi "github.com/matrix-org/dendrite/userapi/api" ) +// ErrInvalidID is an error returned if the userID is invalid +type ErrInvalidID struct { + Err error +} + +func (e ErrInvalidID) Error() string { + return e.Err.Error() +} + +// ErrNotAllowed is an error returned if the user is not allowed +// to execute some action (e.g. invite) +type ErrNotAllowed struct { + Err error +} + +func (e ErrNotAllowed) Error() string { + return e.Err.Error() +} + // RoomserverInputAPI is used to write events to the room server. type RoomserverInternalAPI interface { SyncRoomserverAPI @@ -150,17 +169,17 @@ type ClientRoomserverAPI interface { GetAliasesForRoomID(ctx context.Context, req *GetAliasesForRoomIDRequest, res *GetAliasesForRoomIDResponse) error // PerformRoomUpgrade upgrades a room to a newer version - PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse) error - PerformAdminEvacuateRoom(ctx context.Context, req *PerformAdminEvacuateRoomRequest, res *PerformAdminEvacuateRoomResponse) error - PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse) error - PerformAdminPurgeRoom(ctx context.Context, req *PerformAdminPurgeRoomRequest, res *PerformAdminPurgeRoomResponse) error - PerformAdminDownloadState(ctx context.Context, req *PerformAdminDownloadStateRequest, res *PerformAdminDownloadStateResponse) error - PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse) error - PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse) error - PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error - PerformJoin(ctx context.Context, req *PerformJoinRequest, res *PerformJoinResponse) error + PerformRoomUpgrade(ctx context.Context, roomID, userID string, roomVersion gomatrixserverlib.RoomVersion) (newRoomID string, err error) + PerformAdminEvacuateRoom(ctx context.Context, roomID string) (affected []string, err error) + PerformAdminEvacuateUser(ctx context.Context, userID string) (affected []string, err error) + PerformAdminPurgeRoom(ctx context.Context, roomID string) error + PerformAdminDownloadState(ctx context.Context, roomID, userID string, serverName spec.ServerName) error + PerformPeek(ctx context.Context, req *PerformPeekRequest) (roomID string, err error) + PerformUnpeek(ctx context.Context, roomID, userID, deviceID string) error + PerformInvite(ctx context.Context, req *PerformInviteRequest) error + PerformJoin(ctx context.Context, req *PerformJoinRequest) (roomID string, joinedVia spec.ServerName, err error) PerformLeave(ctx context.Context, req *PerformLeaveRequest, res *PerformLeaveResponse) error - PerformPublish(ctx context.Context, req *PerformPublishRequest, res *PerformPublishResponse) error + PerformPublish(ctx context.Context, req *PerformPublishRequest) error // PerformForget forgets a rooms history for a specific user PerformForget(ctx context.Context, req *PerformForgetRequest, resp *PerformForgetResponse) error SetRoomAlias(ctx context.Context, req *SetRoomAliasRequest, res *SetRoomAliasResponse) error @@ -172,8 +191,8 @@ type UserRoomserverAPI interface { KeyserverRoomserverAPI QueryCurrentState(ctx context.Context, req *QueryCurrentStateRequest, res *QueryCurrentStateResponse) error QueryMembershipsForRoom(ctx context.Context, req *QueryMembershipsForRoomRequest, res *QueryMembershipsForRoomResponse) error - PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse) error - PerformJoin(ctx context.Context, req *PerformJoinRequest, res *PerformJoinResponse) error + PerformAdminEvacuateUser(ctx context.Context, userID string) (affected []string, err error) + PerformJoin(ctx context.Context, req *PerformJoinRequest) (roomID string, joinedVia spec.ServerName, err error) } type FederationRoomserverAPI interface { @@ -202,7 +221,7 @@ type FederationRoomserverAPI interface { QueryRoomsForUser(ctx context.Context, req *QueryRoomsForUserRequest, res *QueryRoomsForUserResponse) error QueryRestrictedJoinAllowed(ctx context.Context, req *QueryRestrictedJoinAllowedRequest, res *QueryRestrictedJoinAllowedResponse) error PerformInboundPeek(ctx context.Context, req *PerformInboundPeekRequest, res *PerformInboundPeekResponse) error - PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error + PerformInvite(ctx context.Context, req *PerformInviteRequest) error // Query a given amount (or less) of events prior to a given set of events. PerformBackfill(ctx context.Context, req *PerformBackfillRequest, res *PerformBackfillResponse) error } diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 218a39a0c..c6e5f5a1c 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -1,81 +1,11 @@ package api import ( - "encoding/json" - "fmt" - "net/http" - + "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" - - "github.com/matrix-org/dendrite/clientapi/jsonerror" - "github.com/matrix-org/dendrite/roomserver/types" -) - -type PerformErrorCode int - -type PerformError struct { - Msg string - RemoteCode int // remote HTTP status code, for PerformErrRemote - Code PerformErrorCode -} - -func (p *PerformError) Error() string { - return fmt.Sprintf("%d : %s", p.Code, p.Msg) -} - -// JSONResponse maps error codes to suitable HTTP error codes, defaulting to 500. -func (p *PerformError) JSONResponse() util.JSONResponse { - switch p.Code { - case PerformErrorBadRequest: - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: jsonerror.Unknown(p.Msg), - } - case PerformErrorNoRoom: - return util.JSONResponse{ - Code: http.StatusNotFound, - JSON: jsonerror.NotFound(p.Msg), - } - case PerformErrorNotAllowed: - return util.JSONResponse{ - Code: http.StatusForbidden, - JSON: jsonerror.Forbidden(p.Msg), - } - case PerformErrorNoOperation: - return util.JSONResponse{ - Code: http.StatusForbidden, - JSON: jsonerror.Forbidden(p.Msg), - } - case PerformErrRemote: - // if the code is 0 then something bad happened and it isn't - // a remote HTTP error being encapsulated, e.g network error to remote. - if p.RemoteCode == 0 { - return util.ErrorResponse(fmt.Errorf("%s", p.Msg)) - } - return util.JSONResponse{ - Code: p.RemoteCode, - // TODO: Should we assert this is in fact JSON? E.g gjson parse? - JSON: json.RawMessage(p.Msg), - } - default: - return util.ErrorResponse(p) - } -} - -const ( - // PerformErrorNotAllowed means the user is not allowed to invite/join/etc this room (e.g join_rule:invite or banned) - PerformErrorNotAllowed PerformErrorCode = 1 - // PerformErrorBadRequest means the request was wrong in some way (invalid user ID, wrong server, etc) - PerformErrorBadRequest PerformErrorCode = 2 - // PerformErrorNoRoom means that the room being joined doesn't exist. - PerformErrorNoRoom PerformErrorCode = 3 - // PerformErrorNoOperation means that the request resulted in nothing happening e.g invite->invite or leave->leave. - PerformErrorNoOperation PerformErrorCode = 4 - // PerformErrRemote means that the request failed and the PerformError.Msg is the raw remote JSON error response - PerformErrRemote PerformErrorCode = 5 ) type PerformJoinRequest struct { @@ -87,14 +17,6 @@ type PerformJoinRequest struct { Unsigned map[string]interface{} `json:"unsigned"` } -type PerformJoinResponse struct { - // The room ID, populated on success. - RoomID string `json:"room_id"` - JoinedVia spec.ServerName - // If non-nil, the join request failed. Contains more information why it failed. - Error *PerformError -} - type PerformLeaveRequest struct { RoomID string `json:"room_id"` UserID string `json:"user_id"` @@ -113,10 +35,6 @@ type PerformInviteRequest struct { TransactionID *TransactionID `json:"transaction_id"` } -type PerformInviteResponse struct { - Error *PerformError -} - type PerformPeekRequest struct { RoomIDOrAlias string `json:"room_id_or_alias"` UserID string `json:"user_id"` @@ -124,24 +42,6 @@ type PerformPeekRequest struct { ServerNames []spec.ServerName `json:"server_names"` } -type PerformPeekResponse struct { - // The room ID, populated on success. - RoomID string `json:"room_id"` - // If non-nil, the join request failed. Contains more information why it failed. - Error *PerformError -} - -type PerformUnpeekRequest struct { - RoomID string `json:"room_id"` - UserID string `json:"user_id"` - DeviceID string `json:"device_id"` -} - -type PerformUnpeekResponse struct { - // If non-nil, the join request failed. Contains more information why it failed. - Error *PerformError -} - // PerformBackfillRequest is a request to PerformBackfill. type PerformBackfillRequest struct { // The room to backfill @@ -180,11 +80,6 @@ type PerformPublishRequest struct { NetworkID string } -type PerformPublishResponse struct { - // If non-nil, the publish request failed. Contains more information why it failed. - Error *PerformError -} - type PerformInboundPeekRequest struct { UserID string `json:"user_id"` RoomID string `json:"room_id"` @@ -214,50 +109,3 @@ type PerformForgetRequest struct { } type PerformForgetResponse struct{} - -type PerformRoomUpgradeRequest struct { - RoomID string `json:"room_id"` - UserID string `json:"user_id"` - RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"` -} - -type PerformRoomUpgradeResponse struct { - NewRoomID string - Error *PerformError -} - -type PerformAdminEvacuateRoomRequest struct { - RoomID string `json:"room_id"` -} - -type PerformAdminEvacuateRoomResponse struct { - Affected []string `json:"affected"` - Error *PerformError -} - -type PerformAdminEvacuateUserRequest struct { - UserID string `json:"user_id"` -} - -type PerformAdminEvacuateUserResponse struct { - Affected []string `json:"affected"` - Error *PerformError -} - -type PerformAdminPurgeRoomRequest struct { - RoomID string `json:"room_id"` -} - -type PerformAdminPurgeRoomResponse struct { - Error *PerformError `json:"error,omitempty"` -} - -type PerformAdminDownloadStateRequest struct { - RoomID string `json:"room_id"` - UserID string `json:"user_id"` - ServerName spec.ServerName `json:"server_name"` -} - -type PerformAdminDownloadStateResponse struct { - Error *PerformError `json:"error,omitempty"` -} diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index 975e77966..81904c8b8 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -209,11 +209,9 @@ func (r *RoomserverInternalAPI) SetAppserviceAPI(asAPI asAPI.AppServiceInternalA func (r *RoomserverInternalAPI) PerformInvite( ctx context.Context, req *api.PerformInviteRequest, - res *api.PerformInviteResponse, ) error { - outputEvents, err := r.Inviter.PerformInvite(ctx, req, res) + outputEvents, err := r.Inviter.PerformInvite(ctx, req) if err != nil { - sentry.CaptureException(err) return err } if len(outputEvents) == 0 { diff --git a/roomserver/internal/perform/perform_admin.go b/roomserver/internal/perform/perform_admin.go index 56c4a034c..59675cd9b 100644 --- a/roomserver/internal/perform/perform_admin.go +++ b/roomserver/internal/perform/perform_admin.go @@ -29,6 +29,7 @@ import ( "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/sirupsen/logrus" ) @@ -41,61 +42,44 @@ type Admin struct { Leaver *Leaver } -// PerformEvacuateRoom will remove all local users from the given room. +// PerformAdminEvacuateRoom will remove all local users from the given room. func (r *Admin) PerformAdminEvacuateRoom( ctx context.Context, - req *api.PerformAdminEvacuateRoomRequest, - res *api.PerformAdminEvacuateRoomResponse, -) error { - roomInfo, err := r.DB.RoomInfo(ctx, req.RoomID) + roomID string, +) (affected []string, err error) { + roomInfo, err := r.DB.RoomInfo(ctx, roomID) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.RoomInfo: %s", err), - } - return nil + return nil, err } if roomInfo == nil || roomInfo.IsStub() { - res.Error = &api.PerformError{ - Code: api.PerformErrorNoRoom, - Msg: fmt.Sprintf("Room %s not found", req.RoomID), - } - return nil + return nil, eventutil.ErrRoomNoExists } memberNIDs, err := r.DB.GetMembershipEventNIDsForRoom(ctx, roomInfo.RoomNID, true, true) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.GetMembershipEventNIDsForRoom: %s", err), - } - return nil + return nil, err } memberEvents, err := r.DB.Events(ctx, roomInfo, memberNIDs) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.Events: %s", err), - } - return nil + return nil, err } inputEvents := make([]api.InputRoomEvent, 0, len(memberEvents)) - res.Affected = make([]string, 0, len(memberEvents)) + affected = make([]string, 0, len(memberEvents)) latestReq := &api.QueryLatestEventsAndStateRequest{ - RoomID: req.RoomID, + RoomID: roomID, } latestRes := &api.QueryLatestEventsAndStateResponse{} if err = r.Queryer.QueryLatestEventsAndState(ctx, latestReq, latestRes); err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.Queryer.QueryLatestEventsAndState: %s", err), - } - return nil + return nil, err } prevEvents := latestRes.LatestEvents + var senderDomain spec.ServerName + var eventsNeeded gomatrixserverlib.StateNeeded + var identity *fclient.SigningIdentity + var event *types.HeaderedEvent for _, memberEvent := range memberEvents { if memberEvent.StateKey() == nil { continue @@ -103,57 +87,41 @@ func (r *Admin) PerformAdminEvacuateRoom( var memberContent gomatrixserverlib.MemberContent if err = json.Unmarshal(memberEvent.Content(), &memberContent); err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("json.Unmarshal: %s", err), - } - return nil + return nil, err } memberContent.Membership = spec.Leave stateKey := *memberEvent.StateKey() fledglingEvent := &gomatrixserverlib.EventBuilder{ - RoomID: req.RoomID, + RoomID: roomID, Type: spec.MRoomMember, StateKey: &stateKey, Sender: stateKey, PrevEvents: prevEvents, } - _, senderDomain, err := gomatrixserverlib.SplitID('@', fledglingEvent.Sender) + _, senderDomain, err = gomatrixserverlib.SplitID('@', fledglingEvent.Sender) if err != nil { continue } if fledglingEvent.Content, err = json.Marshal(memberContent); err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("json.Marshal: %s", err), - } - return nil + return nil, err } - eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(fledglingEvent) + eventsNeeded, err = gomatrixserverlib.StateNeededForEventBuilder(fledglingEvent) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("gomatrixserverlib.StateNeededForEventBuilder: %s", err), - } - return nil + return nil, err } - identity, err := r.Cfg.Matrix.SigningIdentityFor(senderDomain) + identity, err = r.Cfg.Matrix.SigningIdentityFor(senderDomain) if err != nil { continue } - event, err := eventutil.BuildEvent(ctx, fledglingEvent, r.Cfg.Matrix, identity, time.Now(), &eventsNeeded, latestRes) + event, err = eventutil.BuildEvent(ctx, fledglingEvent, r.Cfg.Matrix, identity, time.Now(), &eventsNeeded, latestRes) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("eventutil.BuildEvent: %s", err), - } - return nil + return nil, err } inputEvents = append(inputEvents, api.InputRoomEvent{ @@ -162,7 +130,7 @@ func (r *Admin) PerformAdminEvacuateRoom( Origin: senderDomain, SendAsServer: string(senderDomain), }) - res.Affected = append(res.Affected, stateKey) + affected = append(affected, stateKey) prevEvents = []gomatrixserverlib.EventReference{ event.EventReference(), } @@ -173,108 +141,85 @@ func (r *Admin) PerformAdminEvacuateRoom( Asynchronous: true, } inputRes := &api.InputRoomEventsResponse{} - return r.Inputer.InputRoomEvents(ctx, inputReq, inputRes) + err = r.Inputer.InputRoomEvents(ctx, inputReq, inputRes) + return affected, err } +// PerformAdminEvacuateUser will remove the given user from all rooms. func (r *Admin) PerformAdminEvacuateUser( ctx context.Context, - req *api.PerformAdminEvacuateUserRequest, - res *api.PerformAdminEvacuateUserResponse, -) error { - _, domain, err := gomatrixserverlib.SplitID('@', req.UserID) + userID string, +) (affected []string, err error) { + _, domain, err := gomatrixserverlib.SplitID('@', userID) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Malformed user ID: %s", err), - } - return nil + return nil, err } if !r.Cfg.Matrix.IsLocalServerName(domain) { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: "Can only evacuate local users using this endpoint", - } - return nil + return nil, fmt.Errorf("can only evacuate local users using this endpoint") } - roomIDs, err := r.DB.GetRoomsByMembership(ctx, req.UserID, spec.Join) + roomIDs, err := r.DB.GetRoomsByMembership(ctx, userID, spec.Join) + if err != nil { + return nil, err + } + + inviteRoomIDs, err := r.DB.GetRoomsByMembership(ctx, userID, spec.Invite) if err != nil && err != sql.ErrNoRows { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.GetRoomsByMembership: %s", err), - } - return nil + return nil, err } - inviteRoomIDs, err := r.DB.GetRoomsByMembership(ctx, req.UserID, spec.Invite) - if err != nil && err != sql.ErrNoRows { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.GetRoomsByMembership: %s", err), - } - return nil - } - - for _, roomID := range append(roomIDs, inviteRoomIDs...) { + allRooms := append(roomIDs, inviteRoomIDs...) + affected = make([]string, 0, len(allRooms)) + for _, roomID := range allRooms { leaveReq := &api.PerformLeaveRequest{ RoomID: roomID, - UserID: req.UserID, + UserID: userID, } leaveRes := &api.PerformLeaveResponse{} outputEvents, err := r.Leaver.PerformLeave(ctx, leaveReq, leaveRes) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.Leaver.PerformLeave: %s", err), - } - return nil + return nil, err } - res.Affected = append(res.Affected, roomID) + affected = append(affected, roomID) if len(outputEvents) == 0 { continue } if err := r.Inputer.OutputProducer.ProduceRoomEvents(roomID, outputEvents); err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.Inputer.WriteOutputEvents: %s", err), - } - return nil + return nil, err } } - return nil + return affected, nil } +// PerformAdminPurgeRoom removes all traces for the given room from the database. func (r *Admin) PerformAdminPurgeRoom( ctx context.Context, - req *api.PerformAdminPurgeRoomRequest, - res *api.PerformAdminPurgeRoomResponse, + roomID string, ) error { // Validate we actually got a room ID and nothing else - if _, _, err := gomatrixserverlib.SplitID('!', req.RoomID); err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Malformed room ID: %s", err), - } - return nil + if _, _, err := gomatrixserverlib.SplitID('!', roomID); err != nil { + return err } - logrus.WithField("room_id", req.RoomID).Warn("Purging room from roomserver") - if err := r.DB.PurgeRoom(ctx, req.RoomID); err != nil { - logrus.WithField("room_id", req.RoomID).WithError(err).Warn("Failed to purge room from roomserver") - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: err.Error(), - } - return nil + // Evacuate the room before purging it from the database + if _, err := r.PerformAdminEvacuateRoom(ctx, roomID); err != nil { + logrus.WithField("room_id", roomID).WithError(err).Warn("Failed to evacuate room before purging") + return err } - logrus.WithField("room_id", req.RoomID).Warn("Room purged from roomserver") + logrus.WithField("room_id", roomID).Warn("Purging room from roomserver") + if err := r.DB.PurgeRoom(ctx, roomID); err != nil { + logrus.WithField("room_id", roomID).WithError(err).Warn("Failed to purge room from roomserver") + return err + } - return r.Inputer.OutputProducer.ProduceRoomEvents(req.RoomID, []api.OutputEvent{ + logrus.WithField("room_id", roomID).Warn("Room purged from roomserver") + + return r.Inputer.OutputProducer.ProduceRoomEvents(roomID, []api.OutputEvent{ { Type: api.OutputTypePurgeRoom, PurgeRoom: &api.OutputPurgeRoom{ - RoomID: req.RoomID, + RoomID: roomID, }, }, }) @@ -282,42 +227,25 @@ func (r *Admin) PerformAdminPurgeRoom( func (r *Admin) PerformAdminDownloadState( ctx context.Context, - req *api.PerformAdminDownloadStateRequest, - res *api.PerformAdminDownloadStateResponse, + roomID, userID string, serverName spec.ServerName, ) error { - _, senderDomain, err := r.Cfg.Matrix.SplitLocalID('@', req.UserID) + _, senderDomain, err := r.Cfg.Matrix.SplitLocalID('@', userID) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.Cfg.Matrix.SplitLocalID: %s", err), - } - return nil + return err } - roomInfo, err := r.DB.RoomInfo(ctx, req.RoomID) + roomInfo, err := r.DB.RoomInfo(ctx, roomID) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.RoomInfo: %s", err), - } - return nil + return err } if roomInfo == nil || roomInfo.IsStub() { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("room %q not found", req.RoomID), - } - return nil + return eventutil.ErrRoomNoExists } fwdExtremities, _, depth, err := r.DB.LatestEventIDs(ctx, roomInfo.RoomNID) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.DB.LatestEventIDs: %s", err), - } - return nil + return err } authEventMap := map[string]gomatrixserverlib.PDU{} @@ -325,13 +253,9 @@ func (r *Admin) PerformAdminDownloadState( for _, fwdExtremity := range fwdExtremities { var state gomatrixserverlib.StateResponse - state, err = r.Inputer.FSAPI.LookupState(ctx, r.Inputer.ServerName, req.ServerName, req.RoomID, fwdExtremity.EventID, roomInfo.RoomVersion) + state, err = r.Inputer.FSAPI.LookupState(ctx, r.Inputer.ServerName, serverName, roomID, fwdExtremity.EventID, roomInfo.RoomVersion) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.Inputer.FSAPI.LookupState (%q): %s", fwdExtremity.EventID, err), - } - return nil + return fmt.Errorf("r.Inputer.FSAPI.LookupState (%q): %s", fwdExtremity.EventID, err) } for _, authEvent := range state.GetAuthEvents().UntrustedEvents(roomInfo.RoomVersion) { if err = gomatrixserverlib.VerifyEventSignatures(ctx, authEvent, r.Inputer.KeyRing); err != nil { @@ -361,18 +285,14 @@ func (r *Admin) PerformAdminDownloadState( builder := &gomatrixserverlib.EventBuilder{ Type: "org.matrix.dendrite.state_download", - Sender: req.UserID, - RoomID: req.RoomID, + Sender: userID, + RoomID: roomID, Content: spec.RawJSON("{}"), } eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("gomatrixserverlib.StateNeededForEventBuilder: %s", err), - } - return nil + return fmt.Errorf("gomatrixserverlib.StateNeededForEventBuilder: %w", err) } queryRes := &api.QueryLatestEventsAndStateResponse{ @@ -390,11 +310,7 @@ func (r *Admin) PerformAdminDownloadState( ev, err := eventutil.BuildEvent(ctx, builder, r.Cfg.Matrix, identity, time.Now(), &eventsNeeded, queryRes) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("eventutil.BuildEvent: %s", err), - } - return nil + return fmt.Errorf("eventutil.BuildEvent: %w", err) } inputReq := &api.InputRoomEventsRequest{ @@ -418,19 +334,12 @@ func (r *Admin) PerformAdminDownloadState( SendAsServer: string(r.Cfg.Matrix.ServerName), }) - if err := r.Inputer.InputRoomEvents(ctx, inputReq, inputRes); err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("r.Inputer.InputRoomEvents: %s", err), - } - return nil + if err = r.Inputer.InputRoomEvents(ctx, inputReq, inputRes); err != nil { + return fmt.Errorf("r.Inputer.InputRoomEvents: %w", err) } if inputRes.ErrMsg != "" { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: inputRes.ErrMsg, - } + return inputRes.Err() } return nil diff --git a/roomserver/internal/perform/perform_invite.go b/roomserver/internal/perform/perform_invite.go index bf6e8b6be..a920811d8 100644 --- a/roomserver/internal/perform/perform_invite.go +++ b/roomserver/internal/perform/perform_invite.go @@ -45,7 +45,6 @@ type Inviter struct { func (r *Inviter) PerformInvite( ctx context.Context, req *api.PerformInviteRequest, - res *api.PerformInviteResponse, ) ([]api.OutputEvent, error) { var outputUpdates []api.OutputEvent event := req.Event @@ -66,20 +65,12 @@ func (r *Inviter) PerformInvite( _, domain, err := gomatrixserverlib.SplitID('@', targetUserID) if err != nil { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("The user ID %q is invalid!", targetUserID), - } - return nil, nil + return nil, api.ErrInvalidID{Err: fmt.Errorf("the user ID %s is invalid", targetUserID)} } isTargetLocal := r.Cfg.Matrix.IsLocalServerName(domain) isOriginLocal := r.Cfg.Matrix.IsLocalServerName(senderDomain) if !isOriginLocal && !isTargetLocal { - res.Error = &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: "The invite must be either from or to a local user", - } - return nil, nil + return nil, api.ErrInvalidID{Err: fmt.Errorf("the invite must be either from or to a local user")} } logger := util.GetLogger(ctx).WithFields(map[string]interface{}{ @@ -175,12 +166,8 @@ func (r *Inviter) PerformInvite( // For now we will implement option 2. Since in the abesence of a retry // mechanism it will be equivalent to option 1, and we don't have a // signalling mechanism to implement option 3. - res.Error = &api.PerformError{ - Code: api.PerformErrorNotAllowed, - Msg: "User is already joined to room", - } logger.Debugf("user already joined") - return nil, nil + return nil, api.ErrNotAllowed{Err: fmt.Errorf("user is already joined to room")} } // If the invite originated remotely then we can't send an @@ -201,11 +188,7 @@ func (r *Inviter) PerformInvite( logger.WithError(err).WithField("event_id", event.EventID()).WithField("auth_event_ids", event.AuthEventIDs()).Error( "processInviteEvent.checkAuthEvents failed for event", ) - res.Error = &api.PerformError{ - Msg: err.Error(), - Code: api.PerformErrorNotAllowed, - } - return nil, nil + return nil, api.ErrNotAllowed{Err: err} } // If the invite originated from us and the target isn't local then we @@ -220,12 +203,8 @@ func (r *Inviter) PerformInvite( } fsRes := &federationAPI.PerformInviteResponse{} if err = r.FSAPI.PerformInvite(ctx, fsReq, fsRes); err != nil { - res.Error = &api.PerformError{ - Msg: err.Error(), - Code: api.PerformErrorNotAllowed, - } logger.WithError(err).WithField("event_id", event.EventID()).Error("r.FSAPI.PerformInvite failed") - return nil, nil + return nil, api.ErrNotAllowed{Err: err} } event = fsRes.Event logger.Debugf("Federated PerformInvite success with event ID %s", event.EventID()) @@ -251,11 +230,8 @@ func (r *Inviter) PerformInvite( return nil, fmt.Errorf("r.Inputer.InputRoomEvents: %w", err) } if err = inputRes.Err(); err != nil { - res.Error = &api.PerformError{ - Msg: fmt.Sprintf("r.InputRoomEvents: %s", err.Error()), - Code: api.PerformErrorNotAllowed, - } logger.WithError(err).WithField("event_id", event.EventID()).Error("r.InputRoomEvents failed") + return nil, api.ErrNotAllowed{Err: err} } // Don't notify the sync api of this event in the same way as a federated invite so the invitee diff --git a/roomserver/internal/perform/perform_join.go b/roomserver/internal/perform/perform_join.go index e752b2495..06cc0d673 100644 --- a/roomserver/internal/perform/perform_join.go +++ b/roomserver/internal/perform/perform_join.go @@ -54,32 +54,22 @@ type Joiner struct { func (r *Joiner) PerformJoin( ctx context.Context, req *rsAPI.PerformJoinRequest, - res *rsAPI.PerformJoinResponse, -) error { +) (roomID string, joinedVia spec.ServerName, err error) { logger := logrus.WithContext(ctx).WithFields(logrus.Fields{ "room_id": req.RoomIDOrAlias, "user_id": req.UserID, "servers": req.ServerNames, }) logger.Info("User requested to room join") - roomID, joinedVia, err := r.performJoin(context.Background(), req) + roomID, joinedVia, err = r.performJoin(context.Background(), req) if err != nil { logger.WithError(err).Error("Failed to join room") sentry.CaptureException(err) - perr, ok := err.(*rsAPI.PerformError) - if ok { - res.Error = perr - } else { - res.Error = &rsAPI.PerformError{ - Msg: err.Error(), - } - } - return nil + return "", "", err } logger.Info("User joined room successfully") - res.RoomID = roomID - res.JoinedVia = joinedVia - return nil + + return roomID, joinedVia, nil } func (r *Joiner) performJoin( @@ -88,16 +78,10 @@ func (r *Joiner) performJoin( ) (string, spec.ServerName, error) { _, domain, err := gomatrixserverlib.SplitID('@', req.UserID) if err != nil { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorBadRequest, - Msg: fmt.Sprintf("Supplied user ID %q in incorrect format", req.UserID), - } + return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("supplied user ID %q in incorrect format", req.UserID)} } if !r.Cfg.Matrix.IsLocalServerName(domain) { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorBadRequest, - Msg: fmt.Sprintf("User %q does not belong to this homeserver", req.UserID), - } + return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("user %q does not belong to this homeserver", req.UserID)} } if strings.HasPrefix(req.RoomIDOrAlias, "!") { return r.performJoinRoomByID(ctx, req) @@ -105,10 +89,7 @@ func (r *Joiner) performJoin( if strings.HasPrefix(req.RoomIDOrAlias, "#") { return r.performJoinRoomByAlias(ctx, req) } - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorBadRequest, - Msg: fmt.Sprintf("Room ID or alias %q is invalid", req.RoomIDOrAlias), - } + return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("room ID or alias %q is invalid", req.RoomIDOrAlias)} } func (r *Joiner) performJoinRoomByAlias( @@ -183,10 +164,7 @@ func (r *Joiner) performJoinRoomByID( // Get the domain part of the room ID. _, domain, err := gomatrixserverlib.SplitID('!', req.RoomIDOrAlias) if err != nil { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorBadRequest, - Msg: fmt.Sprintf("Room ID %q is invalid: %s", req.RoomIDOrAlias, err), - } + return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid: %w", req.RoomIDOrAlias, err)} } // If the server name in the room ID isn't ours then it's a @@ -200,10 +178,7 @@ func (r *Joiner) performJoinRoomByID( userID := req.UserID _, userDomain, err := r.Cfg.Matrix.SplitLocalID('@', userID) if err != nil { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorBadRequest, - Msg: fmt.Sprintf("User ID %q is invalid: %s", userID, err), - } + return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("user ID %q is invalid: %w", userID, err)} } eb := gomatrixserverlib.EventBuilder{ Type: spec.MRoomMember, @@ -287,10 +262,7 @@ func (r *Joiner) performJoinRoomByID( // Servers MUST only allow guest users to join rooms if the m.room.guest_access state event // is present on the room and has the guest_access value can_join. if guestAccess != "can_join" { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorNotAllowed, - Msg: "Guest access is forbidden", - } + return "", "", rsAPI.ErrNotAllowed{Err: fmt.Errorf("guest access is forbidden")} } } @@ -342,16 +314,10 @@ func (r *Joiner) performJoinRoomByID( } inputRes := rsAPI.InputRoomEventsResponse{} if err = r.Inputer.InputRoomEvents(ctx, &inputReq, &inputRes); err != nil { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorNoOperation, - Msg: fmt.Sprintf("InputRoomEvents failed: %s", err), - } + return "", "", rsAPI.ErrNotAllowed{Err: err} } if err = inputRes.Err(); err != nil { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorNotAllowed, - Msg: fmt.Sprintf("InputRoomEvents auth failed: %s", err), - } + return "", "", rsAPI.ErrNotAllowed{Err: err} } } @@ -364,10 +330,7 @@ func (r *Joiner) performJoinRoomByID( // Otherwise we'll try a federated join as normal, since it's quite // possible that the room still exists on other servers. if len(req.ServerNames) == 0 { - return "", "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorNoRoom, - Msg: fmt.Sprintf("room ID %q does not exist", req.RoomIDOrAlias), - } + return "", "", eventutil.ErrRoomNoExists } } @@ -402,11 +365,7 @@ func (r *Joiner) performFederatedJoinRoomByID( fedRes := fsAPI.PerformJoinResponse{} r.FSAPI.PerformJoin(ctx, &fedReq, &fedRes) if fedRes.LastError != nil { - return "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrRemote, - Msg: fedRes.LastError.Message, - RemoteCode: fedRes.LastError.Code, - } + return "", fedRes.LastError } return fedRes.JoinedVia, nil } @@ -430,10 +389,7 @@ func (r *Joiner) populateAuthorisedViaUserForRestrictedJoin( return "", nil } if !res.Allowed { - return "", &rsAPI.PerformError{ - Code: rsAPI.PerformErrorNotAllowed, - Msg: fmt.Sprintf("The join to room %s was not allowed.", joinReq.RoomIDOrAlias), - } + return "", rsAPI.ErrNotAllowed{Err: fmt.Errorf("the join to room %s was not allowed", joinReq.RoomIDOrAlias)} } return res.AuthorisedVia, nil } diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 2f39050dd..661fe20a8 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -44,21 +44,8 @@ type Peeker struct { func (r *Peeker) PerformPeek( ctx context.Context, req *api.PerformPeekRequest, - res *api.PerformPeekResponse, -) error { - roomID, err := r.performPeek(ctx, req) - if err != nil { - perr, ok := err.(*api.PerformError) - if ok { - res.Error = perr - } else { - res.Error = &api.PerformError{ - Msg: err.Error(), - } - } - } - res.RoomID = roomID - return nil +) (roomID string, err error) { + return r.performPeek(ctx, req) } func (r *Peeker) performPeek( @@ -68,16 +55,10 @@ func (r *Peeker) performPeek( // FIXME: there's way too much duplication with performJoin _, domain, err := gomatrixserverlib.SplitID('@', req.UserID) if err != nil { - return "", &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Supplied user ID %q in incorrect format", req.UserID), - } + return "", api.ErrInvalidID{Err: fmt.Errorf("supplied user ID %q in incorrect format", req.UserID)} } if !r.Cfg.Matrix.IsLocalServerName(domain) { - return "", &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("User %q does not belong to this homeserver", req.UserID), - } + return "", api.ErrInvalidID{Err: fmt.Errorf("user %q does not belong to this homeserver", req.UserID)} } if strings.HasPrefix(req.RoomIDOrAlias, "!") { return r.performPeekRoomByID(ctx, req) @@ -85,10 +66,7 @@ func (r *Peeker) performPeek( if strings.HasPrefix(req.RoomIDOrAlias, "#") { return r.performPeekRoomByAlias(ctx, req) } - return "", &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Room ID or alias %q is invalid", req.RoomIDOrAlias), - } + return "", api.ErrInvalidID{Err: fmt.Errorf("room ID or alias %q is invalid", req.RoomIDOrAlias)} } func (r *Peeker) performPeekRoomByAlias( @@ -98,7 +76,7 @@ func (r *Peeker) performPeekRoomByAlias( // Get the domain part of the room alias. _, domain, err := gomatrixserverlib.SplitID('#', req.RoomIDOrAlias) if err != nil { - return "", fmt.Errorf("alias %q is not in the correct format", req.RoomIDOrAlias) + return "", api.ErrInvalidID{Err: fmt.Errorf("alias %q is not in the correct format", req.RoomIDOrAlias)} } req.ServerNames = append(req.ServerNames, domain) @@ -147,10 +125,7 @@ func (r *Peeker) performPeekRoomByID( // Get the domain part of the room ID. _, domain, err := gomatrixserverlib.SplitID('!', roomID) if err != nil { - return "", &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Room ID %q is invalid: %s", roomID, err), - } + return "", api.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid: %w", roomID, err)} } // handle federated peeks @@ -169,11 +144,7 @@ func (r *Peeker) performPeekRoomByID( fedRes := fsAPI.PerformOutboundPeekResponse{} _ = r.FSAPI.PerformOutboundPeek(ctx, &fedReq, &fedRes) if fedRes.LastError != nil { - return "", &api.PerformError{ - Code: api.PerformErrRemote, - Msg: fedRes.LastError.Message, - RemoteCode: fedRes.LastError.Code, - } + return "", fedRes.LastError } } @@ -194,17 +165,11 @@ func (r *Peeker) performPeekRoomByID( } if !worldReadable { - return "", &api.PerformError{ - Code: api.PerformErrorNotAllowed, - Msg: "Room is not world-readable", - } + return "", api.ErrNotAllowed{Err: fmt.Errorf("room is not world-readable")} } if ev, _ := r.DB.GetStateEvent(ctx, roomID, "m.room.encryption", ""); ev != nil { - return "", &api.PerformError{ - Code: api.PerformErrorNotAllowed, - Msg: "Cannot peek into an encrypted room", - } + return "", api.ErrNotAllowed{Err: fmt.Errorf("Cannot peek into an encrypted room")} } // TODO: handle federated peeks diff --git a/roomserver/internal/perform/perform_publish.go b/roomserver/internal/perform/perform_publish.go index fbbfc3219..297a4a189 100644 --- a/roomserver/internal/perform/perform_publish.go +++ b/roomserver/internal/perform/perform_publish.go @@ -25,16 +25,10 @@ type Publisher struct { DB storage.Database } +// PerformPublish publishes or unpublishes a room from the room directory. Returns a database error, if any. func (r *Publisher) PerformPublish( ctx context.Context, req *api.PerformPublishRequest, - res *api.PerformPublishResponse, ) error { - err := r.DB.PublishRoom(ctx, req.RoomID, req.AppserviceID, req.NetworkID, req.Visibility == "public") - if err != nil { - res.Error = &api.PerformError{ - Msg: err.Error(), - } - } - return nil + return r.DB.PublishRoom(ctx, req.RoomID, req.AppserviceID, req.NetworkID, req.Visibility == "public") } diff --git a/roomserver/internal/perform/perform_unpeek.go b/roomserver/internal/perform/perform_unpeek.go index 28486fa14..1ea8079d4 100644 --- a/roomserver/internal/perform/perform_unpeek.go +++ b/roomserver/internal/perform/perform_unpeek.go @@ -34,84 +34,48 @@ type Unpeeker struct { Inputer *input.Inputer } -// PerformPeek handles peeking into matrix rooms, including over federation by talking to the federationapi. +// PerformUnpeek handles un-peeking matrix rooms, including over federation by talking to the federationapi. func (r *Unpeeker) PerformUnpeek( ctx context.Context, - req *api.PerformUnpeekRequest, - res *api.PerformUnpeekResponse, -) error { - if err := r.performUnpeek(ctx, req); err != nil { - perr, ok := err.(*api.PerformError) - if ok { - res.Error = perr - } else { - res.Error = &api.PerformError{ - Msg: err.Error(), - } - } - } - return nil -} - -func (r *Unpeeker) performUnpeek( - ctx context.Context, - req *api.PerformUnpeekRequest, + roomID, userID, deviceID string, ) error { // FIXME: there's way too much duplication with performJoin - _, domain, err := gomatrixserverlib.SplitID('@', req.UserID) + _, domain, err := gomatrixserverlib.SplitID('@', userID) if err != nil { - return &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Supplied user ID %q in incorrect format", req.UserID), - } + return api.ErrInvalidID{Err: fmt.Errorf("supplied user ID %q in incorrect format", userID)} } if !r.Cfg.Matrix.IsLocalServerName(domain) { - return &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("User %q does not belong to this homeserver", req.UserID), - } + return api.ErrInvalidID{Err: fmt.Errorf("user %q does not belong to this homeserver", userID)} } - if strings.HasPrefix(req.RoomID, "!") { - return r.performUnpeekRoomByID(ctx, req) - } - return &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Room ID %q is invalid", req.RoomID), + if strings.HasPrefix(roomID, "!") { + return r.performUnpeekRoomByID(ctx, roomID, userID, deviceID) } + return api.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid", roomID)} } func (r *Unpeeker) performUnpeekRoomByID( _ context.Context, - req *api.PerformUnpeekRequest, + roomID, userID, deviceID string, ) (err error) { // Get the domain part of the room ID. - _, _, err = gomatrixserverlib.SplitID('!', req.RoomID) + _, _, err = gomatrixserverlib.SplitID('!', roomID) if err != nil { - return &api.PerformError{ - Code: api.PerformErrorBadRequest, - Msg: fmt.Sprintf("Room ID %q is invalid: %s", req.RoomID, err), - } + return api.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid: %w", roomID, err)} } // TODO: handle federated peeks - - err = r.Inputer.OutputProducer.ProduceRoomEvents(req.RoomID, []api.OutputEvent{ - { - Type: api.OutputTypeRetirePeek, - RetirePeek: &api.OutputRetirePeek{ - RoomID: req.RoomID, - UserID: req.UserID, - DeviceID: req.DeviceID, - }, - }, - }) - if err != nil { - return - } - // By this point, if req.RoomIDOrAlias contained an alias, then // it will have been overwritten with a room ID by performPeekRoomByAlias. // We should now include this in the response so that the CS API can // return the right room ID. - return nil + return r.Inputer.OutputProducer.ProduceRoomEvents(roomID, []api.OutputEvent{ + { + Type: api.OutputTypeRetirePeek, + RetirePeek: &api.OutputRetirePeek{ + RoomID: roomID, + UserID: userID, + DeviceID: deviceID, + }, + }, + }) } diff --git a/roomserver/internal/perform/perform_upgrade.go b/roomserver/internal/perform/perform_upgrade.go index 4090d467d..b4561ca89 100644 --- a/roomserver/internal/perform/perform_upgrade.go +++ b/roomserver/internal/perform/perform_upgrade.go @@ -45,46 +45,29 @@ type fledglingEvent struct { // PerformRoomUpgrade upgrades a room from one version to another func (r *Upgrader) PerformRoomUpgrade( ctx context.Context, - req *api.PerformRoomUpgradeRequest, - res *api.PerformRoomUpgradeResponse, -) error { - res.NewRoomID, res.Error = r.performRoomUpgrade(ctx, req) - if res.Error != nil { - res.NewRoomID = "" - logrus.WithContext(ctx).WithError(res.Error).Error("Room upgrade failed") - } - return nil + roomID, userID string, roomVersion gomatrixserverlib.RoomVersion, +) (newRoomID string, err error) { + return r.performRoomUpgrade(ctx, roomID, userID, roomVersion) } func (r *Upgrader) performRoomUpgrade( ctx context.Context, - req *api.PerformRoomUpgradeRequest, -) (string, *api.PerformError) { - roomID := req.RoomID - userID := req.UserID + roomID, userID string, roomVersion gomatrixserverlib.RoomVersion, +) (string, error) { _, userDomain, err := r.Cfg.Matrix.SplitLocalID('@', userID) if err != nil { - return "", &api.PerformError{ - Code: api.PerformErrorNotAllowed, - Msg: "Error validating the user ID", - } + return "", api.ErrNotAllowed{Err: fmt.Errorf("error validating the user ID")} } evTime := time.Now() // Return an immediate error if the room does not exist if err := r.validateRoomExists(ctx, roomID); err != nil { - return "", &api.PerformError{ - Code: api.PerformErrorNoRoom, - Msg: "Error validating that the room exists", - } + return "", err } // 1. Check if the user is authorized to actually perform the upgrade (can send m.room.tombstone) if !r.userIsAuthorized(ctx, userID, roomID) { - return "", &api.PerformError{ - Code: api.PerformErrorNotAllowed, - Msg: "You don't have permission to upgrade the room, power level too low.", - } + return "", api.ErrNotAllowed{Err: fmt.Errorf("You don't have permission to upgrade the room, power level too low.")} } // TODO (#267): Check room ID doesn't clash with an existing one, and we @@ -97,9 +80,7 @@ func (r *Upgrader) performRoomUpgrade( } oldRoomRes := &api.QueryLatestEventsAndStateResponse{} if err := r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil { - return "", &api.PerformError{ - Msg: fmt.Sprintf("Failed to get latest state: %s", err), - } + return "", fmt.Errorf("Failed to get latest state: %s", err) } // Make the tombstone event @@ -110,13 +91,13 @@ func (r *Upgrader) performRoomUpgrade( // Generate the initial events we need to send into the new room. This includes copied state events and bans // as well as the power level events needed to set up the room - eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, userID, roomID, string(req.RoomVersion), tombstoneEvent) + eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, userID, roomID, roomVersion, tombstoneEvent) if pErr != nil { return "", pErr } // Send the setup events to the new room - if pErr = r.sendInitialEvents(ctx, evTime, userID, userDomain, newRoomID, string(req.RoomVersion), eventsToMake); pErr != nil { + if pErr = r.sendInitialEvents(ctx, evTime, userID, userDomain, newRoomID, roomVersion, eventsToMake); pErr != nil { return "", pErr } @@ -148,22 +129,15 @@ func (r *Upgrader) performRoomUpgrade( return newRoomID, nil } -func (r *Upgrader) getRoomPowerLevels(ctx context.Context, roomID string) (*gomatrixserverlib.PowerLevelContent, *api.PerformError) { +func (r *Upgrader) getRoomPowerLevels(ctx context.Context, roomID string) (*gomatrixserverlib.PowerLevelContent, error) { oldPowerLevelsEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{ EventType: spec.MRoomPowerLevels, StateKey: "", }) - powerLevelContent, err := oldPowerLevelsEvent.PowerLevels() - if err != nil { - util.GetLogger(ctx).WithError(err).Error() - return nil, &api.PerformError{ - Msg: "Power level event was invalid or malformed", - } - } - return powerLevelContent, nil + return oldPowerLevelsEvent.PowerLevels() } -func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.Time, userID string, userDomain spec.ServerName, roomID string) *api.PerformError { +func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.Time, userID string, userDomain spec.ServerName, roomID string) error { restrictedPowerLevelContent, pErr := r.getRoomPowerLevels(ctx, roomID) if pErr != nil { return pErr @@ -185,54 +159,46 @@ func (r *Upgrader) restrictOldRoomPowerLevels(ctx context.Context, evTime time.T StateKey: "", Content: restrictedPowerLevelContent, }) - if resErr != nil { - if resErr.Code == api.PerformErrorNotAllowed { - util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not restrict power levels in old room") - } else { - return resErr - } - } else { - if resErr = r.sendHeaderedEvent(ctx, userDomain, restrictedPowerLevelsHeadered, api.DoNotSendToOtherServers); resErr != nil { - return resErr - } + + switch resErr.(type) { + case api.ErrNotAllowed: + util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not restrict power levels in old room") + case nil: + return r.sendHeaderedEvent(ctx, userDomain, restrictedPowerLevelsHeadered, api.DoNotSendToOtherServers) + default: + return resErr } return nil } func moveLocalAliases(ctx context.Context, roomID, newRoomID, userID string, - URSAPI api.RoomserverInternalAPI) *api.PerformError { - var err error + URSAPI api.RoomserverInternalAPI, +) (err error) { aliasReq := api.GetAliasesForRoomIDRequest{RoomID: roomID} aliasRes := api.GetAliasesForRoomIDResponse{} if err = URSAPI.GetAliasesForRoomID(ctx, &aliasReq, &aliasRes); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to get old room aliases: %s", err), - } + return fmt.Errorf("Failed to get old room aliases: %w", err) } for _, alias := range aliasRes.Aliases { removeAliasReq := api.RemoveRoomAliasRequest{UserID: userID, Alias: alias} removeAliasRes := api.RemoveRoomAliasResponse{} if err = URSAPI.RemoveRoomAlias(ctx, &removeAliasReq, &removeAliasRes); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to remove old room alias: %s", err), - } + return fmt.Errorf("Failed to remove old room alias: %w", err) } setAliasReq := api.SetRoomAliasRequest{UserID: userID, Alias: alias, RoomID: newRoomID} setAliasRes := api.SetRoomAliasResponse{} if err = URSAPI.SetRoomAlias(ctx, &setAliasReq, &setAliasRes); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to set new room alias: %s", err), - } + return fmt.Errorf("Failed to set new room alias: %w", err) } } return nil } -func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, evTime time.Time, userID string, userDomain spec.ServerName, roomID string) *api.PerformError { +func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, evTime time.Time, userID string, userDomain spec.ServerName, roomID string) error { for _, event := range oldRoom.StateEvents { if event.Type() != spec.MRoomCanonicalAlias || !event.StateKeyEquals("") { continue @@ -242,9 +208,7 @@ func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api AltAliases []string `json:"alt_aliases"` } if err := json.Unmarshal(event.Content(), &aliasContent); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to unmarshal canonical aliases: %s", err), - } + return fmt.Errorf("failed to unmarshal canonical aliases: %w", err) } if aliasContent.Alias == "" && len(aliasContent.AltAliases) == 0 { // There are no canonical aliases to clear, therefore do nothing. @@ -256,30 +220,25 @@ func (r *Upgrader) clearOldCanonicalAliasEvent(ctx context.Context, oldRoom *api Type: spec.MRoomCanonicalAlias, Content: map[string]interface{}{}, }) - if resErr != nil { - if resErr.Code == api.PerformErrorNotAllowed { - util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not set empty canonical alias event in old room") - } else { - return resErr - } - } else { - if resErr = r.sendHeaderedEvent(ctx, userDomain, emptyCanonicalAliasEvent, api.DoNotSendToOtherServers); resErr != nil { - return resErr - } + switch resErr.(type) { + case api.ErrNotAllowed: + util.GetLogger(ctx).WithField(logrus.ErrorKey, resErr).Warn("UpgradeRoom: Could not set empty canonical alias event in old room") + case nil: + return r.sendHeaderedEvent(ctx, userDomain, emptyCanonicalAliasEvent, api.DoNotSendToOtherServers) + default: + return resErr } return nil } -func (r *Upgrader) publishIfOldRoomWasPublic(ctx context.Context, roomID, newRoomID string) *api.PerformError { +func (r *Upgrader) publishIfOldRoomWasPublic(ctx context.Context, roomID, newRoomID string) error { // check if the old room was published var pubQueryRes api.QueryPublishedRoomsResponse err := r.URSAPI.QueryPublishedRooms(ctx, &api.QueryPublishedRoomsRequest{ RoomID: roomID, }, &pubQueryRes) if err != nil { - return &api.PerformError{ - Msg: "QueryPublishedRooms failed", - } + return err } // if the old room is published (was public), publish the new room @@ -295,36 +254,27 @@ func publishNewRoomAndUnpublishOldRoom( oldRoomID, newRoomID string, ) { // expose this room in the published room list - var pubNewRoomRes api.PerformPublishResponse if err := URSAPI.PerformPublish(ctx, &api.PerformPublishRequest{ RoomID: newRoomID, - Visibility: "public", - }, &pubNewRoomRes); err != nil { - util.GetLogger(ctx).WithError(err).Error("failed to reach internal API") - } else if pubNewRoomRes.Error != nil { + Visibility: spec.Public, + }); err != nil { // treat as non-fatal since the room is already made by this point - util.GetLogger(ctx).WithError(pubNewRoomRes.Error).Error("failed to visibility:public") + util.GetLogger(ctx).WithError(err).Error("failed to publish room") } - var unpubOldRoomRes api.PerformPublishResponse // remove the old room from the published room list if err := URSAPI.PerformPublish(ctx, &api.PerformPublishRequest{ RoomID: oldRoomID, Visibility: "private", - }, &unpubOldRoomRes); err != nil { - util.GetLogger(ctx).WithError(err).Error("failed to reach internal API") - } else if unpubOldRoomRes.Error != nil { + }); err != nil { // treat as non-fatal since the room is already made by this point - util.GetLogger(ctx).WithError(unpubOldRoomRes.Error).Error("failed to visibility:private") + util.GetLogger(ctx).WithError(err).Error("failed to un-publish room") } } func (r *Upgrader) validateRoomExists(ctx context.Context, roomID string) error { if _, err := r.URSAPI.QueryRoomVersionForRoom(ctx, roomID); err != nil { - return &api.PerformError{ - Code: api.PerformErrorNoRoom, - Msg: "Room does not exist", - } + return eventutil.ErrRoomNoExists } return nil } @@ -348,7 +298,7 @@ func (r *Upgrader) userIsAuthorized(ctx context.Context, userID, roomID string, } // nolint:gocyclo -func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, userID, roomID, newVersion string, tombstoneEvent *types.HeaderedEvent) ([]fledglingEvent, *api.PerformError) { +func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, userID, roomID string, newVersion gomatrixserverlib.RoomVersion, tombstoneEvent *types.HeaderedEvent) ([]fledglingEvent, error) { state := make(map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent, len(oldRoom.StateEvents)) for _, event := range oldRoom.StateEvents { if event.StateKey() == nil { @@ -391,9 +341,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query // old room state. Check that they are there. for tuple := range override { if _, ok := state[tuple]; !ok { - return nil, &api.PerformError{ - Msg: fmt.Sprintf("Essential event of type %q state key %q is missing", tuple.EventType, tuple.StateKey), - } + return nil, fmt.Errorf("essential event of type %q state key %q is missing", tuple.EventType, tuple.StateKey) } } @@ -440,9 +388,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query powerLevelContent, err := oldPowerLevelsEvent.PowerLevels() if err != nil { util.GetLogger(ctx).WithError(err).Error() - return nil, &api.PerformError{ - Msg: "Power level event content was invalid", - } + return nil, fmt.Errorf("Power level event content was invalid") } tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(powerLevelContent, userID) @@ -506,7 +452,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query return eventsToMake, nil } -func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, userID string, userDomain spec.ServerName, newRoomID, newVersion string, eventsToMake []fledglingEvent) *api.PerformError { +func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, userID string, userDomain spec.ServerName, newRoomID string, newVersion gomatrixserverlib.RoomVersion, eventsToMake []fledglingEvent) error { var err error var builtEvents []*types.HeaderedEvent authEvents := gomatrixserverlib.NewAuthEvents(nil) @@ -522,34 +468,27 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, user } err = builder.SetContent(e.Content) if err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to set content of new %q event: %s", builder.Type, err), - } + return fmt.Errorf("failed to set content of new %q event: %w", builder.Type, err) } if i > 0 { builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()} } var event *gomatrixserverlib.Event - event, err = builder.AddAuthEventsAndBuild(userDomain, &authEvents, evTime, gomatrixserverlib.RoomVersion(newVersion), r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey) + event, err = builder.AddAuthEventsAndBuild(userDomain, &authEvents, evTime, newVersion, r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey) if err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to build new %q event: %s", builder.Type, err), - } + return fmt.Errorf("failed to build new %q event: %w", builder.Type, err) + } if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to auth new %q event: %s", builder.Type, err), - } + return fmt.Errorf("Failed to auth new %q event: %w", builder.Type, err) } // Add the event to the list of auth events builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: event}) err = authEvents.AddEvent(event) if err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to add new %q event to auth set: %s", builder.Type, err), - } + return fmt.Errorf("failed to add new %q event to auth set: %w", builder.Type, err) } } @@ -563,9 +502,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, user }) } if err = api.SendInputRoomEvents(ctx, r.URSAPI, userDomain, inputs, false); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to send new room %q to roomserver: %s", newRoomID, err), - } + return fmt.Errorf("failed to send new room %q to roomserver: %w", newRoomID, err) } return nil } @@ -574,7 +511,7 @@ func (r *Upgrader) makeTombstoneEvent( ctx context.Context, evTime time.Time, userID, roomID, newRoomID string, -) (*types.HeaderedEvent, *api.PerformError) { +) (*types.HeaderedEvent, error) { content := map[string]interface{}{ "body": "This room has been replaced", "replacement_room": newRoomID, @@ -586,7 +523,7 @@ func (r *Upgrader) makeTombstoneEvent( return r.makeHeaderedEvent(ctx, evTime, userID, roomID, event) } -func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, userID, roomID string, event fledglingEvent) (*types.HeaderedEvent, *api.PerformError) { +func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, userID, roomID string, event fledglingEvent) (*types.HeaderedEvent, error) { builder := gomatrixserverlib.EventBuilder{ Sender: userID, RoomID: roomID, @@ -595,47 +532,27 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user } err := builder.SetContent(event.Content) if err != nil { - return nil, &api.PerformError{ - Msg: fmt.Sprintf("Failed to set new %q event content: %s", builder.Type, err), - } + return nil, fmt.Errorf("failed to set new %q event content: %w", builder.Type, err) } // Get the sender domain. _, senderDomain, serr := r.Cfg.Matrix.SplitLocalID('@', builder.Sender) if serr != nil { - return nil, &api.PerformError{ - Msg: fmt.Sprintf("Failed to split user ID %q: %s", builder.Sender, err), - } + return nil, fmt.Errorf("Failed to split user ID %q: %w", builder.Sender, err) } identity, err := r.Cfg.Matrix.SigningIdentityFor(senderDomain) if err != nil { - return nil, &api.PerformError{ - Msg: fmt.Sprintf("Failed to get signing identity for %q: %s", senderDomain, err), - } + return nil, fmt.Errorf("failed to get signing identity for %q: %w", senderDomain, err) } var queryRes api.QueryLatestEventsAndStateResponse headeredEvent, err := eventutil.QueryAndBuildEvent(ctx, &builder, r.Cfg.Matrix, identity, evTime, r.URSAPI, &queryRes) if err == eventutil.ErrRoomNoExists { - return nil, &api.PerformError{ - Code: api.PerformErrorNoRoom, - Msg: "Room does not exist", - } + return nil, err } else if e, ok := err.(gomatrixserverlib.BadJSONError); ok { - return nil, &api.PerformError{ - Msg: e.Error(), - } + return nil, e } else if e, ok := err.(gomatrixserverlib.EventValidationError); ok { - if e.Code == gomatrixserverlib.EventValidationTooLarge { - return nil, &api.PerformError{ - Msg: e.Error(), - } - } - return nil, &api.PerformError{ - Msg: e.Error(), - } + return nil, e } else if err != nil { - return nil, &api.PerformError{ - Msg: fmt.Sprintf("Failed to build new %q event: %s", builder.Type, err), - } + return nil, fmt.Errorf("failed to build new %q event: %w", builder.Type, err) } // check to see if this user can perform this operation stateEvents := make([]gomatrixserverlib.PDU, len(queryRes.StateEvents)) @@ -644,10 +561,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, user } provider := gomatrixserverlib.NewAuthEvents(stateEvents) if err = gomatrixserverlib.Allowed(headeredEvent.PDU, &provider); err != nil { - return nil, &api.PerformError{ - Code: api.PerformErrorNotAllowed, - Msg: fmt.Sprintf("Failed to auth new %q event: %s", builder.Type, err), // TODO: Is this error string comprehensible to the client? - } + return nil, api.ErrNotAllowed{Err: fmt.Errorf("failed to auth new %q event: %w", builder.Type, err)} // TODO: Is this error string comprehensible to the client? } return headeredEvent, nil @@ -695,7 +609,7 @@ func (r *Upgrader) sendHeaderedEvent( serverName spec.ServerName, headeredEvent *types.HeaderedEvent, sendAsServer string, -) *api.PerformError { +) error { var inputs []api.InputRoomEvent inputs = append(inputs, api.InputRoomEvent{ Kind: api.KindNew, @@ -703,11 +617,5 @@ func (r *Upgrader) sendHeaderedEvent( Origin: serverName, SendAsServer: sendAsServer, }) - if err := api.SendInputRoomEvents(ctx, r.URSAPI, serverName, inputs, false); err != nil { - return &api.PerformError{ - Msg: fmt.Sprintf("Failed to send new %q event to roomserver: %s", headeredEvent.Type(), err), - } - } - - return nil + return api.SendInputRoomEvents(ctx, r.URSAPI, serverName, inputs, false) } diff --git a/roomserver/roomserver_test.go b/roomserver/roomserver_test.go index 3b918349f..702432b15 100644 --- a/roomserver/roomserver_test.go +++ b/roomserver/roomserver_test.go @@ -245,8 +245,8 @@ func TestPurgeRoom(t *testing.T) { // this starts the JetStream consumers syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics) - federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) - rsAPI.SetFederationAPI(nil, nil) + fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) + rsAPI.SetFederationAPI(fsAPI, nil) // Create the room if err = api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil { @@ -254,13 +254,9 @@ func TestPurgeRoom(t *testing.T) { } // some dummy entries to validate after purging - publishResp := &api.PerformPublishResponse{} - if err = rsAPI.PerformPublish(ctx, &api.PerformPublishRequest{RoomID: room.ID, Visibility: "public"}, publishResp); err != nil { + if err = rsAPI.PerformPublish(ctx, &api.PerformPublishRequest{RoomID: room.ID, Visibility: spec.Public}); err != nil { t.Fatal(err) } - if publishResp.Error != nil { - t.Fatal(publishResp.Error) - } isPublished, err := db.GetPublishedRoom(ctx, room.ID) if err != nil { @@ -328,8 +324,7 @@ func TestPurgeRoom(t *testing.T) { } // purge the room from the database - purgeResp := &api.PerformAdminPurgeRoomResponse{} - if err = rsAPI.PerformAdminPurgeRoom(ctx, &api.PerformAdminPurgeRoomRequest{RoomID: room.ID}, purgeResp); err != nil { + if err = rsAPI.PerformAdminPurgeRoom(ctx, room.ID); err != nil { t.Fatal(err) } @@ -926,7 +921,7 @@ func TestUpgrade(t *testing.T) { if err := rsAPI.PerformPublish(ctx, &api.PerformPublishRequest{ RoomID: r.ID, Visibility: spec.Public, - }, &api.PerformPublishResponse{}); err != nil { + }); err != nil { t.Fatal(err) } @@ -1070,25 +1065,19 @@ func TestUpgrade(t *testing.T) { } roomID := tc.roomFunc(rsAPI) - upgradeReq := api.PerformRoomUpgradeRequest{ - RoomID: roomID, - UserID: tc.upgradeUser, - RoomVersion: version.DefaultRoomVersion(), // always upgrade to the latest version - } - upgradeRes := api.PerformRoomUpgradeResponse{} - - if err := rsAPI.PerformRoomUpgrade(processCtx.Context(), &upgradeReq, &upgradeRes); err != nil { + newRoomID, err := rsAPI.PerformRoomUpgrade(processCtx.Context(), roomID, tc.upgradeUser, version.DefaultRoomVersion()) + if err != nil && tc.wantNewRoom { t.Fatal(err) } - if tc.wantNewRoom && upgradeRes.NewRoomID == "" { + if tc.wantNewRoom && newRoomID == "" { t.Fatalf("expected a new room, but the upgrade failed") } - if !tc.wantNewRoom && upgradeRes.NewRoomID != "" { + if !tc.wantNewRoom && newRoomID != "" { t.Fatalf("expected no new room, but the upgrade succeeded") } if tc.validateFunc != nil { - tc.validateFunc(t, roomID, upgradeRes.NewRoomID, rsAPI) + tc.validateFunc(t, roomID, newRoomID, rsAPI) } }) } diff --git a/roomserver/storage/interface.go b/roomserver/storage/interface.go index cc0e667e9..8da6b350e 100644 --- a/roomserver/storage/interface.go +++ b/roomserver/storage/interface.go @@ -139,7 +139,7 @@ type Database interface { // not found. // Returns an error if the retrieval went wrong. EventsFromIDs(ctx context.Context, roomInfo *types.RoomInfo, eventIDs []string) ([]types.Event, error) - // Publish or unpublish a room from the room directory. + // PerformPublish publishes or unpublishes a room from the room directory. Returns a database error, if any. PublishRoom(ctx context.Context, roomID, appserviceID, networkID string, publish bool) error // Returns a list of room IDs for rooms which are published. GetPublishedRooms(ctx context.Context, networkID string, includeAllNetworks bool) ([]string, error) diff --git a/userapi/api/api.go b/userapi/api/api.go index 3fe6a383d..4e13a3b94 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -87,6 +87,7 @@ type ClientUserAPI interface { UserLoginAPI ClientKeyAPI ProfileAPI + KeyBackupAPI QueryNumericLocalpart(ctx context.Context, req *QueryNumericLocalpartRequest, res *QueryNumericLocalpartResponse) error QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error QueryAccountData(ctx context.Context, req *QueryAccountDataRequest, res *QueryAccountDataResponse) error @@ -105,8 +106,6 @@ type ClientUserAPI interface { PerformOpenIDTokenCreation(ctx context.Context, req *PerformOpenIDTokenCreationRequest, res *PerformOpenIDTokenCreationResponse) error QueryNotifications(ctx context.Context, req *QueryNotificationsRequest, res *QueryNotificationsResponse) error InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error - PerformKeyBackup(ctx context.Context, req *PerformKeyBackupRequest, res *PerformKeyBackupResponse) error - QueryKeyBackup(ctx context.Context, req *QueryKeyBackupRequest, res *QueryKeyBackupResponse) error QueryThreePIDsForLocalpart(ctx context.Context, req *QueryThreePIDsForLocalpartRequest, res *QueryThreePIDsForLocalpartResponse) error QueryLocalpartForThreePID(ctx context.Context, req *QueryLocalpartForThreePIDRequest, res *QueryLocalpartForThreePIDResponse) error @@ -114,6 +113,13 @@ type ClientUserAPI interface { PerformSaveThreePIDAssociation(ctx context.Context, req *PerformSaveThreePIDAssociationRequest, res *struct{}) error } +type KeyBackupAPI interface { + DeleteKeyBackup(ctx context.Context, userID, version string) (bool, error) + PerformKeyBackup(ctx context.Context, req *PerformKeyBackupRequest) (string, error) + QueryKeyBackup(ctx context.Context, req *QueryKeyBackupRequest) (*QueryKeyBackupResponse, error) + UpdateBackupKeyAuthData(ctx context.Context, req *PerformKeyBackupRequest) (*PerformKeyBackupResponse, error) +} + type ProfileAPI interface { QueryProfile(ctx context.Context, userID string) (*authtypes.Profile, error) SetAvatarURL(ctx context.Context, localpart string, serverName spec.ServerName, avatarURL string) (*authtypes.Profile, bool, error) @@ -135,11 +141,10 @@ type UserLoginAPI interface { } type PerformKeyBackupRequest struct { - UserID string - Version string // optional if modifying a key backup - AuthData json.RawMessage - Algorithm string - DeleteBackup bool // if true will delete the backup based on 'Version'. + UserID string + Version string // optional if modifying a key backup + AuthData json.RawMessage + Algorithm string // The keys to upload, if any. If blank, creates/updates/deletes key version metadata only. Keys struct { @@ -180,9 +185,6 @@ type InternalKeyBackupSession struct { } type PerformKeyBackupResponse struct { - Error string // set if there was a problem performing the request - BadInput bool // if set, the Error was due to bad input (HTTP 400) - Exists bool // set to true if the Version exists Version string // the newly created version @@ -200,7 +202,6 @@ type QueryKeyBackupRequest struct { } type QueryKeyBackupResponse struct { - Error string Exists bool Algorithm string `json:"algorithm"` diff --git a/userapi/internal/user_api.go b/userapi/internal/user_api.go index 1b6a4ebfa..ea97fd353 100644 --- a/userapi/internal/user_api.go +++ b/userapi/internal/user_api.go @@ -25,6 +25,7 @@ import ( appserviceAPI "github.com/matrix-org/dendrite/appservice/api" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/clientapi/jsonerror" fedsenderapi "github.com/matrix-org/dendrite/federationapi/api" "github.com/matrix-org/dendrite/internal/pushrules" "github.com/matrix-org/gomatrixserverlib" @@ -172,8 +173,8 @@ func addUserToRoom( UserID: userID, Content: addGroupContent, } - joinRes := rsapi.PerformJoinResponse{} - return rsAPI.PerformJoin(ctx, &joinReq, &joinRes) + _, _, err := rsAPI.PerformJoin(ctx, &joinReq) + return err } func (a *UserInternalAPI) PerformAccountCreation(ctx context.Context, req *api.PerformAccountCreationRequest, res *api.PerformAccountCreationResponse) error { @@ -624,33 +625,28 @@ func (a *UserInternalAPI) PerformAccountDeactivation(ctx context.Context, req *a return fmt.Errorf("server name %q not locally configured", serverName) } - evacuateReq := &rsapi.PerformAdminEvacuateUserRequest{ - UserID: fmt.Sprintf("@%s:%s", req.Localpart, serverName), - } - evacuateRes := &rsapi.PerformAdminEvacuateUserResponse{} - if err := a.RSAPI.PerformAdminEvacuateUser(ctx, evacuateReq, evacuateRes); err != nil { - return err - } - if err := evacuateRes.Error; err != nil { - logrus.WithError(err).Errorf("Failed to evacuate user after account deactivation") + userID := fmt.Sprintf("@%s:%s", req.Localpart, serverName) + _, err := a.RSAPI.PerformAdminEvacuateUser(ctx, userID) + if err != nil { + logrus.WithError(err).WithField("userID", userID).Errorf("Failed to evacuate user after account deactivation") } deviceReq := &api.PerformDeviceDeletionRequest{ UserID: fmt.Sprintf("@%s:%s", req.Localpart, serverName), } deviceRes := &api.PerformDeviceDeletionResponse{} - if err := a.PerformDeviceDeletion(ctx, deviceReq, deviceRes); err != nil { + if err = a.PerformDeviceDeletion(ctx, deviceReq, deviceRes); err != nil { return err } pusherReq := &api.PerformPusherDeletionRequest{ Localpart: req.Localpart, } - if err := a.PerformPusherDeletion(ctx, pusherReq, &struct{}{}); err != nil { + if err = a.PerformPusherDeletion(ctx, pusherReq, &struct{}{}); err != nil { return err } - err := a.DB.DeactivateAccount(ctx, req.Localpart, serverName) + err = a.DB.DeactivateAccount(ctx, req.Localpart, serverName) res.AccountDeactivated = err == nil return err } @@ -683,62 +679,43 @@ func (a *UserInternalAPI) QueryOpenIDToken(ctx context.Context, req *api.QueryOp return nil } -func (a *UserInternalAPI) PerformKeyBackup(ctx context.Context, req *api.PerformKeyBackupRequest, res *api.PerformKeyBackupResponse) error { - // Delete metadata - if req.DeleteBackup { - if req.Version == "" { - res.BadInput = true - res.Error = "must specify a version to delete" - return nil - } - exists, err := a.DB.DeleteKeyBackup(ctx, req.UserID, req.Version) - if err != nil { - res.Error = fmt.Sprintf("failed to delete backup: %s", err) - } - res.Exists = exists - res.Version = req.Version - return nil - } +func (a *UserInternalAPI) DeleteKeyBackup(ctx context.Context, userID, version string) (bool, error) { + return a.DB.DeleteKeyBackup(ctx, userID, version) +} + +func (a *UserInternalAPI) PerformKeyBackup(ctx context.Context, req *api.PerformKeyBackupRequest) (string, error) { // Create metadata - if req.Version == "" { - version, err := a.DB.CreateKeyBackup(ctx, req.UserID, req.Algorithm, req.AuthData) - if err != nil { - res.Error = fmt.Sprintf("failed to create backup: %s", err) - } - res.Exists = err == nil - res.Version = version - return nil - } + return a.DB.CreateKeyBackup(ctx, req.UserID, req.Algorithm, req.AuthData) +} + +func (a *UserInternalAPI) UpdateBackupKeyAuthData(ctx context.Context, req *api.PerformKeyBackupRequest) (*api.PerformKeyBackupResponse, error) { + res := &api.PerformKeyBackupResponse{} // Update metadata if len(req.Keys.Rooms) == 0 { err := a.DB.UpdateKeyBackupAuthData(ctx, req.UserID, req.Version, req.AuthData) - if err != nil { - res.Error = fmt.Sprintf("failed to update backup: %s", err) - } res.Exists = err == nil res.Version = req.Version - return nil + if err != nil { + return res, fmt.Errorf("failed to update backup: %w", err) + } + return res, nil } // Upload Keys for a specific version metadata - a.uploadBackupKeys(ctx, req, res) - return nil + return a.uploadBackupKeys(ctx, req) } -func (a *UserInternalAPI) uploadBackupKeys(ctx context.Context, req *api.PerformKeyBackupRequest, res *api.PerformKeyBackupResponse) { +func (a *UserInternalAPI) uploadBackupKeys(ctx context.Context, req *api.PerformKeyBackupRequest) (*api.PerformKeyBackupResponse, error) { + res := &api.PerformKeyBackupResponse{} // you can only upload keys for the CURRENT version version, _, _, _, deleted, err := a.DB.GetKeyBackup(ctx, req.UserID, "") if err != nil { - res.Error = fmt.Sprintf("failed to query version: %s", err) - return + return res, fmt.Errorf("failed to query version: %w", err) } if deleted { - res.Error = "backup was deleted" - return + return res, fmt.Errorf("backup was deleted") } if version != req.Version { - res.BadInput = true - res.Error = fmt.Sprintf("%s isn't the current version, %s is.", req.Version, version) - return + return res, jsonerror.WrongBackupVersionError(version) } res.Exists = true res.Version = version @@ -756,23 +733,25 @@ func (a *UserInternalAPI) uploadBackupKeys(ctx context.Context, req *api.Perform } count, etag, err := a.DB.UpsertBackupKeys(ctx, version, req.UserID, uploads) if err != nil { - res.Error = fmt.Sprintf("failed to upsert keys: %s", err) - return + return res, fmt.Errorf("failed to upsert keys: %w", err) } res.KeyCount = count res.KeyETag = etag + return res, nil } -func (a *UserInternalAPI) QueryKeyBackup(ctx context.Context, req *api.QueryKeyBackupRequest, res *api.QueryKeyBackupResponse) error { +func (a *UserInternalAPI) QueryKeyBackup(ctx context.Context, req *api.QueryKeyBackupRequest) (*api.QueryKeyBackupResponse, error) { + res := &api.QueryKeyBackupResponse{} version, algorithm, authData, etag, deleted, err := a.DB.GetKeyBackup(ctx, req.UserID, req.Version) res.Version = version if err != nil { - if err == sql.ErrNoRows { - res.Exists = false - return nil + if errors.Is(err, sql.ErrNoRows) { + return res, nil } - res.Error = fmt.Sprintf("failed to query key backup: %s", err) - return nil + if errors.Is(err, strconv.ErrSyntax) { + return res, nil + } + return res, fmt.Errorf("failed to query key backup: %s", err) } res.Algorithm = algorithm res.AuthData = authData @@ -782,18 +761,17 @@ func (a *UserInternalAPI) QueryKeyBackup(ctx context.Context, req *api.QueryKeyB if !req.ReturnKeys { res.Count, err = a.DB.CountBackupKeys(ctx, version, req.UserID) if err != nil { - res.Error = fmt.Sprintf("failed to count keys: %s", err) + return res, fmt.Errorf("failed to count keys: %w", err) } - return nil + return res, nil } result, err := a.DB.GetBackupKeys(ctx, version, req.UserID, req.KeysForRoomID, req.KeysForSessionID) if err != nil { - res.Error = fmt.Sprintf("failed to query keys: %s", err) - return nil + return res, fmt.Errorf("failed to query keys: %s", err) } res.Keys = result - return nil + return res, nil } func (a *UserInternalAPI) QueryNotifications(ctx context.Context, req *api.QueryNotificationsRequest, res *api.QueryNotificationsResponse) error {