aboutsummaryrefslogtreecommitdiff
path: root/clientapi
diff options
context:
space:
mode:
authorTill <2353100+S7evinK@users.noreply.github.com>2023-03-31 10:15:01 +0200
committerGitHub <noreply@github.com>2023-03-31 10:15:01 +0200
commit2854ffeb7d933f76cbdfe0eb6775a8f5699f4170 (patch)
tree67de00500ab6132184239b547fc22e993bc23492 /clientapi
parent28d3e296a8adaeea34bca01c7e4e6a0e22918390 (diff)
Add CS API device tests (#3029)
Adds tests for - `/devices` - `/delete_devices` (also adds UIA)
Diffstat (limited to 'clientapi')
-rw-r--r--clientapi/admin_test.go67
-rw-r--r--clientapi/clientapi_test.go373
-rw-r--r--clientapi/routing/deactivate.go5
-rw-r--r--clientapi/routing/device.go40
-rw-r--r--clientapi/routing/routing.go2
5 files changed, 422 insertions, 65 deletions
diff --git a/clientapi/admin_test.go b/clientapi/admin_test.go
index 3e7cb875..79e88d13 100644
--- a/clientapi/admin_test.go
+++ b/clientapi/admin_test.go
@@ -8,7 +8,6 @@ import (
"testing"
"time"
- "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/federationapi"
"github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/httputil"
@@ -54,10 +53,10 @@ func TestAdminResetPassword(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login
- accessTokens := map[*test.User]string{
- aliceAdmin: "",
- bob: "",
- vhUser: "",
+ accessTokens := map[*test.User]userDevice{
+ aliceAdmin: {},
+ bob: {},
+ vhUser: {},
}
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@@ -103,7 +102,7 @@ func TestAdminResetPassword(t *testing.T) {
}
if tc.withHeader {
- req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser])
+ req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
}
rec := httptest.NewRecorder()
@@ -154,8 +153,8 @@ func TestPurgeRoom(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login
- accessTokens := map[*test.User]string{
- aliceAdmin: "",
+ accessTokens := map[*test.User]userDevice{
+ aliceAdmin: {},
}
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@@ -174,7 +173,7 @@ func TestPurgeRoom(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/purgeRoom/"+tc.roomID)
- req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin])
+ req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req)
@@ -224,8 +223,8 @@ func TestAdminEvacuateRoom(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login
- accessTokens := map[*test.User]string{
- aliceAdmin: "",
+ accessTokens := map[*test.User]userDevice{
+ aliceAdmin: {},
}
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@@ -243,7 +242,7 @@ func TestAdminEvacuateRoom(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/evacuateRoom/"+tc.roomID)
- req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin])
+ req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req)
@@ -308,8 +307,8 @@ func TestAdminEvacuateUser(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login
- accessTokens := map[*test.User]string{
- aliceAdmin: "",
+ accessTokens := map[*test.User]userDevice{
+ aliceAdmin: {},
}
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@@ -329,7 +328,7 @@ func TestAdminEvacuateUser(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/evacuateUser/"+tc.userID)
- req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin])
+ req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req)
@@ -390,8 +389,8 @@ func TestAdminMarkAsStale(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login
- accessTokens := map[*test.User]string{
- aliceAdmin: "",
+ accessTokens := map[*test.User]userDevice{
+ aliceAdmin: {},
}
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@@ -409,7 +408,7 @@ func TestAdminMarkAsStale(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/refreshDevices/"+tc.userID)
- req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin])
+ req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req)
@@ -421,35 +420,3 @@ func TestAdminMarkAsStale(t *testing.T) {
}
})
}
-
-func createAccessTokens(t *testing.T, accessTokens map[*test.User]string, userAPI uapi.UserInternalAPI, ctx context.Context, routers httputil.Routers) {
- t.Helper()
- for u := range accessTokens {
- localpart, serverName, _ := gomatrixserverlib.SplitID('@', u.ID)
- userRes := &uapi.PerformAccountCreationResponse{}
- password := util.RandomString(8)
- if err := userAPI.PerformAccountCreation(ctx, &uapi.PerformAccountCreationRequest{
- AccountType: u.AccountType,
- Localpart: localpart,
- ServerName: serverName,
- Password: password,
- }, userRes); err != nil {
- t.Errorf("failed to create account: %s", err)
- }
-
- req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
- "type": authtypes.LoginTypePassword,
- "identifier": map[string]interface{}{
- "type": "m.id.user",
- "user": u.ID,
- },
- "password": password,
- }))
- rec := httptest.NewRecorder()
- routers.Client.ServeHTTP(rec, req)
- if rec.Code != http.StatusOK {
- t.Fatalf("failed to login: %s", rec.Body.String())
- }
- accessTokens[u] = gjson.GetBytes(rec.Body.Bytes(), "access_token").String()
- }
-}
diff --git a/clientapi/clientapi_test.go b/clientapi/clientapi_test.go
new file mode 100644
index 00000000..d9091552
--- /dev/null
+++ b/clientapi/clientapi_test.go
@@ -0,0 +1,373 @@
+package clientapi
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
+ "github.com/matrix-org/dendrite/internal/caching"
+ "github.com/matrix-org/dendrite/internal/httputil"
+ "github.com/matrix-org/dendrite/internal/sqlutil"
+ "github.com/matrix-org/dendrite/roomserver"
+ "github.com/matrix-org/dendrite/setup/jetstream"
+ "github.com/matrix-org/dendrite/test"
+ "github.com/matrix-org/dendrite/test/testrig"
+ "github.com/matrix-org/dendrite/userapi"
+ uapi "github.com/matrix-org/dendrite/userapi/api"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/util"
+ "github.com/tidwall/gjson"
+)
+
+type userDevice struct {
+ accessToken string
+ deviceID string
+ password string
+}
+
+func TestGetPutDevices(t *testing.T) {
+ alice := test.NewUser(t)
+ bob := test.NewUser(t)
+
+ test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
+ testCases := []struct {
+ name string
+ requestUser *test.User
+ deviceUser *test.User
+ request *http.Request
+ wantStatusCode int
+ validateFunc func(t *testing.T, device userDevice, routers httputil.Routers)
+ }{
+ {
+ name: "can get all devices",
+ requestUser: alice,
+ request: httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/devices", strings.NewReader("")),
+ wantStatusCode: http.StatusOK,
+ },
+ {
+ name: "can get specific own device",
+ requestUser: alice,
+ deviceUser: alice,
+ request: httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/devices/", strings.NewReader("")),
+ wantStatusCode: http.StatusOK,
+ },
+ {
+ name: "can not get device for different user",
+ requestUser: alice,
+ deviceUser: bob,
+ request: httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/devices/", strings.NewReader("")),
+ wantStatusCode: http.StatusNotFound,
+ },
+ {
+ name: "can update own device",
+ requestUser: alice,
+ deviceUser: alice,
+ request: httptest.NewRequest(http.MethodPut, "/_matrix/client/v3/devices/", strings.NewReader(`{"display_name":"my new displayname"}`)),
+ wantStatusCode: http.StatusOK,
+ validateFunc: func(t *testing.T, device userDevice, routers httputil.Routers) {
+ req := httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/devices/"+device.deviceID, strings.NewReader(""))
+ req.Header.Set("Authorization", "Bearer "+device.accessToken)
+ rec := httptest.NewRecorder()
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
+ }
+ gotDisplayName := gjson.GetBytes(rec.Body.Bytes(), "display_name").Str
+ if gotDisplayName != "my new displayname" {
+ t.Fatalf("expected displayname '%s', got '%s'", "my new displayname", gotDisplayName)
+ }
+ },
+ },
+ {
+ // this should return "device does not exist"
+ name: "can not update device for different user",
+ requestUser: alice,
+ deviceUser: bob,
+ request: httptest.NewRequest(http.MethodPut, "/_matrix/client/v3/devices/", strings.NewReader(`{"display_name":"my new displayname"}`)),
+ wantStatusCode: http.StatusNotFound,
+ },
+ }
+
+ cfg, processCtx, close := testrig.CreateConfig(t, dbType)
+ 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: {},
+ bob: {},
+ }
+ createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ dev := accessTokens[tc.requestUser]
+ if tc.deviceUser != nil {
+ tc.request = httptest.NewRequest(tc.request.Method, tc.request.RequestURI+accessTokens[tc.deviceUser].deviceID, tc.request.Body)
+ }
+ tc.request.Header.Set("Authorization", "Bearer "+dev.accessToken)
+ rec := httptest.NewRecorder()
+ routers.Client.ServeHTTP(rec, tc.request)
+ if rec.Code != tc.wantStatusCode {
+ t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
+ }
+ if tc.wantStatusCode != http.StatusOK && rec.Code != http.StatusOK {
+ return
+ }
+ if tc.validateFunc != nil {
+ tc.validateFunc(t, dev, routers)
+ }
+ })
+ }
+ })
+}
+
+// Deleting devices requires the UIA dance, so do this in a different test
+func TestDeleteDevice(t *testing.T) {
+ alice := test.NewUser(t)
+ localpart, serverName, _ := gomatrixserverlib.SplitID('@', alice.ID)
+
+ test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
+ cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
+ defer closeDB()
+
+ natsInstance := jetstream.NATSInstance{}
+ routers := httputil.NewRouters()
+ cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
+ caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
+ 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: {},
+ }
+
+ // create the account and an initial device
+ createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
+
+ // create some more devices
+ accessToken := util.RandomString(8)
+ devRes := &uapi.PerformDeviceCreationResponse{}
+ if err := userAPI.PerformDeviceCreation(processCtx.Context(), &uapi.PerformDeviceCreationRequest{
+ Localpart: localpart,
+ ServerName: serverName,
+ AccessToken: accessToken,
+ NoDeviceListUpdate: true,
+ }, devRes); err != nil {
+ t.Fatal(err)
+ }
+ if !devRes.DeviceCreated {
+ t.Fatalf("failed to create device")
+ }
+ secondDeviceID := devRes.Device.ID
+
+ // initiate UIA for the second device
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/devices/"+secondDeviceID, strings.NewReader(""))
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected HTTP 401, got %d: %s", rec.Code, rec.Body.String())
+ }
+ // get the session ID
+ sessionID := gjson.GetBytes(rec.Body.Bytes(), "session").Str
+
+ // prepare UIA request body
+ reqBody := bytes.Buffer{}
+ if err := json.NewEncoder(&reqBody).Encode(map[string]interface{}{
+ "auth": map[string]string{
+ "session": sessionID,
+ "type": authtypes.LoginTypePassword,
+ "user": alice.ID,
+ "password": accessTokens[alice].password,
+ },
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // copy the request body, so we can use it again for the successful delete
+ reqBody2 := reqBody
+
+ // do the same request again, this time with our UIA, but for a different device ID, this should fail
+ rec = httptest.NewRecorder()
+
+ req = httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/devices/"+accessTokens[alice].deviceID, &reqBody)
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusForbidden {
+ t.Fatalf("expected HTTP 403, got %d: %s", rec.Code, rec.Body.String())
+ }
+
+ // do the same request again, this time with our UIA, but for the correct device ID, this should be fine
+ rec = httptest.NewRecorder()
+ req = httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/devices/"+secondDeviceID, &reqBody2)
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
+ }
+
+ // verify devices are deleted
+ rec = httptest.NewRecorder()
+ req = httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/devices", strings.NewReader(""))
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
+ }
+ for _, device := range gjson.GetBytes(rec.Body.Bytes(), "devices.#.device_id").Array() {
+ if device.Str == secondDeviceID {
+ t.Fatalf("expected device %s to be deleted, but wasn't", secondDeviceID)
+ }
+ }
+ })
+}
+
+// Deleting devices requires the UIA dance, so do this in a different test
+func TestDeleteDevices(t *testing.T) {
+ alice := test.NewUser(t)
+ localpart, serverName, _ := gomatrixserverlib.SplitID('@', alice.ID)
+
+ test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
+ cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
+ defer closeDB()
+
+ natsInstance := jetstream.NATSInstance{}
+ routers := httputil.NewRouters()
+ cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
+ caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
+ 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: {},
+ }
+
+ // create the account and an initial device
+ createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
+
+ // create some more devices
+ var devices []string
+ for i := 0; i < 10; i++ {
+ accessToken := util.RandomString(8)
+ devRes := &uapi.PerformDeviceCreationResponse{}
+ if err := userAPI.PerformDeviceCreation(processCtx.Context(), &uapi.PerformDeviceCreationRequest{
+ Localpart: localpart,
+ ServerName: serverName,
+ AccessToken: accessToken,
+ NoDeviceListUpdate: true,
+ }, devRes); err != nil {
+ t.Fatal(err)
+ }
+ if !devRes.DeviceCreated {
+ t.Fatalf("failed to create device")
+ }
+ devices = append(devices, devRes.Device.ID)
+ }
+
+ // initiate UIA
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/delete_devices", strings.NewReader(""))
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusUnauthorized {
+ t.Fatalf("expected HTTP 401, got %d: %s", rec.Code, rec.Body.String())
+ }
+ // get the session ID
+ sessionID := gjson.GetBytes(rec.Body.Bytes(), "session").Str
+
+ // prepare UIA request body
+ reqBody := bytes.Buffer{}
+ if err := json.NewEncoder(&reqBody).Encode(map[string]interface{}{
+ "auth": map[string]string{
+ "session": sessionID,
+ "type": authtypes.LoginTypePassword,
+ "user": alice.ID,
+ "password": accessTokens[alice].password,
+ },
+ "devices": devices[5:],
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // do the same request again, this time with our UIA,
+ rec = httptest.NewRecorder()
+ req = httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/delete_devices", &reqBody)
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
+ }
+
+ // verify devices are deleted
+ rec = httptest.NewRecorder()
+ req = httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/devices", strings.NewReader(""))
+ req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
+ }
+ for _, device := range gjson.GetBytes(rec.Body.Bytes(), "devices.#.device_id").Array() {
+ for _, deletedDevice := range devices[5:] {
+ if device.Str == deletedDevice {
+ t.Fatalf("expected device %s to be deleted, but wasn't", deletedDevice)
+ }
+ }
+ }
+ })
+}
+
+func createAccessTokens(t *testing.T, accessTokens map[*test.User]userDevice, userAPI uapi.UserInternalAPI, ctx context.Context, routers httputil.Routers) {
+ t.Helper()
+ for u := range accessTokens {
+ localpart, serverName, _ := gomatrixserverlib.SplitID('@', u.ID)
+ userRes := &uapi.PerformAccountCreationResponse{}
+ password := util.RandomString(8)
+ if err := userAPI.PerformAccountCreation(ctx, &uapi.PerformAccountCreationRequest{
+ AccountType: u.AccountType,
+ Localpart: localpart,
+ ServerName: serverName,
+ Password: password,
+ }, userRes); err != nil {
+ t.Errorf("failed to create account: %s", err)
+ }
+ req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
+ "type": authtypes.LoginTypePassword,
+ "identifier": map[string]interface{}{
+ "type": "m.id.user",
+ "user": u.ID,
+ },
+ "password": password,
+ }))
+ rec := httptest.NewRecorder()
+ routers.Client.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("failed to login: %s", rec.Body.String())
+ }
+ accessTokens[u] = userDevice{
+ accessToken: gjson.GetBytes(rec.Body.Bytes(), "access_token").String(),
+ deviceID: gjson.GetBytes(rec.Body.Bytes(), "device_id").String(),
+ password: password,
+ }
+ }
+}
diff --git a/clientapi/routing/deactivate.go b/clientapi/routing/deactivate.go
index f213db7f..3f4f539f 100644
--- a/clientapi/routing/deactivate.go
+++ b/clientapi/routing/deactivate.go
@@ -33,7 +33,7 @@ func Deactivate(
return *errRes
}
- localpart, _, err := gomatrixserverlib.SplitID('@', login.Username())
+ localpart, serverName, err := gomatrixserverlib.SplitID('@', login.Username())
if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
return jsonerror.InternalServerError()
@@ -41,7 +41,8 @@ func Deactivate(
var res api.PerformAccountDeactivationResponse
err = accountAPI.PerformAccountDeactivation(ctx, &api.PerformAccountDeactivationRequest{
- Localpart: localpart,
+ Localpart: localpart,
+ ServerName: serverName,
}, &res)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformAccountDeactivation failed")
diff --git a/clientapi/routing/device.go b/clientapi/routing/device.go
index e3a02661..331bacc3 100644
--- a/clientapi/routing/device.go
+++ b/clientapi/routing/device.go
@@ -15,6 +15,7 @@
package routing
import (
+ "encoding/json"
"io"
"net"
"net/http"
@@ -146,12 +147,6 @@ func UpdateDeviceByID(
JSON: jsonerror.Forbidden("device does not exist"),
}
}
- if performRes.Forbidden {
- return util.JSONResponse{
- Code: http.StatusForbidden,
- JSON: jsonerror.Forbidden("device not owned by current user"),
- }
- }
return util.JSONResponse{
Code: http.StatusOK,
@@ -189,7 +184,7 @@ func DeleteDeviceById(
if dev != deviceID {
return util.JSONResponse{
Code: http.StatusForbidden,
- JSON: jsonerror.Forbidden("session & device mismatch"),
+ JSON: jsonerror.Forbidden("session and device mismatch"),
}
}
}
@@ -242,16 +237,37 @@ func DeleteDeviceById(
// DeleteDevices handles POST requests to /delete_devices
func DeleteDevices(
- req *http.Request, userAPI api.ClientUserAPI, device *api.Device,
+ req *http.Request, userInteractiveAuth *auth.UserInteractive, userAPI api.ClientUserAPI, device *api.Device,
) util.JSONResponse {
ctx := req.Context()
- payload := devicesDeleteJSON{}
- if resErr := httputil.UnmarshalJSONRequest(req, &payload); resErr != nil {
- return *resErr
+ bodyBytes, err := io.ReadAll(req.Body)
+ if err != nil {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
+ }
}
+ defer req.Body.Close() // nolint:errcheck
- defer req.Body.Close() // nolint: errcheck
+ // initiate UIA
+ login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device)
+ if errRes != nil {
+ return *errRes
+ }
+
+ if login.Username() != device.UserID {
+ return util.JSONResponse{
+ Code: http.StatusForbidden,
+ JSON: jsonerror.Forbidden("unable to delete devices for other user"),
+ }
+ }
+
+ payload := devicesDeleteJSON{}
+ if err = json.Unmarshal(bodyBytes, &payload); err != nil {
+ util.GetLogger(ctx).WithError(err).Error("unable to unmarshal device deletion request")
+ return jsonerror.InternalServerError()
+ }
var res api.PerformDeviceDeletionResponse
if err := userAPI.PerformDeviceDeletion(ctx, &api.PerformDeviceDeletionRequest{
diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go
index e261cb3a..6c8035d4 100644
--- a/clientapi/routing/routing.go
+++ b/clientapi/routing/routing.go
@@ -1115,7 +1115,7 @@ func Setup(
v3mux.Handle("/delete_devices",
httputil.MakeAuthAPI("delete_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
- return DeleteDevices(req, userAPI, device)
+ return DeleteDevices(req, userInteractiveAuth, userAPI, device)
}),
).Methods(http.MethodPost, http.MethodOptions)