mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-16 18:43:10 -06:00
Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/morecstests
This commit is contained in:
commit
56839f4dc3
1
.github/workflows/helm.yml
vendored
1
.github/workflows/helm.yml
vendored
|
|
@ -6,6 +6,7 @@ on:
|
|||
- main
|
||||
paths:
|
||||
- 'helm/**' # only execute if we have helm chart changes
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
|
@ -150,6 +148,10 @@ type ASLocationResponse struct {
|
|||
Fields json.RawMessage `json:"fields"`
|
||||
}
|
||||
|
||||
// ErrProfileNotExists is returned when trying to lookup a user's profile that
|
||||
// doesn't exist locally.
|
||||
var ErrProfileNotExists = errors.New("no known profile for given user ID")
|
||||
|
||||
// RetrieveUserProfile is a wrapper that queries both the local database and
|
||||
// application services for a given user's profile
|
||||
// TODO: Remove this, it's called from federationapi and clientapi but is a pure function
|
||||
|
|
@ -157,25 +159,11 @@ func RetrieveUserProfile(
|
|||
ctx context.Context,
|
||||
userID string,
|
||||
asAPI AppServiceInternalAPI,
|
||||
profileAPI userapi.ClientUserAPI,
|
||||
profileAPI userapi.ProfileAPI,
|
||||
) (*authtypes.Profile, error) {
|
||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try to query the user from the local database
|
||||
res := &userapi.QueryProfileResponse{}
|
||||
err = profileAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: userID}, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profile := &authtypes.Profile{
|
||||
Localpart: localpart,
|
||||
DisplayName: res.DisplayName,
|
||||
AvatarURL: res.AvatarURL,
|
||||
}
|
||||
if res.UserExists {
|
||||
profile, err := profileAPI.QueryProfile(ctx, userID)
|
||||
if err == nil {
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
|
|
@ -188,19 +176,15 @@ func RetrieveUserProfile(
|
|||
|
||||
// If no user exists, return
|
||||
if !userResp.UserIDExists {
|
||||
return nil, errors.New("no known profile for given user ID")
|
||||
return nil, ErrProfileNotExists
|
||||
}
|
||||
|
||||
// Try to query the user from the local database again
|
||||
err = profileAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: userID}, res)
|
||||
profile, err = profileAPI.QueryProfile(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// profile should not be nil at this point
|
||||
return &authtypes.Profile{
|
||||
Localpart: localpart,
|
||||
DisplayName: res.DisplayName,
|
||||
AvatarURL: res.AvatarURL,
|
||||
}, nil
|
||||
return profile, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -262,6 +261,25 @@ func TestAdminEvacuateRoom(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for the FS API to have consumed every message
|
||||
js, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
||||
timeout := time.After(time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("FS API didn't process all events in time")
|
||||
default:
|
||||
}
|
||||
info, err := js.ConsumerInfo(cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent), cfg.Global.JetStream.Durable("FederationAPIRoomServerConsumer")+"Pull")
|
||||
if err != nil {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
continue
|
||||
}
|
||||
if info.NumPending == 0 && info.NumAckPending == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -308,8 +326,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 +347,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 +408,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 +427,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 +439,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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,29 +5,901 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||
"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/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"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"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDisplayname(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
bob := test.NewUser(t)
|
||||
notLocalUser := &test.User{ID: "@charlie:localhost", Localpart: "charlie"}
|
||||
changeDisplayName := "my new display name"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
user *test.User
|
||||
wantOK bool
|
||||
changeReq io.Reader
|
||||
wantDisplayName string
|
||||
}{
|
||||
{
|
||||
name: "invalid user",
|
||||
user: &test.User{ID: "!notauser"},
|
||||
},
|
||||
{
|
||||
name: "non-existent user",
|
||||
user: &test.User{ID: "@doesnotexist:test"},
|
||||
},
|
||||
{
|
||||
name: "non-local user is not allowed",
|
||||
user: notLocalUser,
|
||||
},
|
||||
{
|
||||
name: "existing user is allowed to change own name",
|
||||
user: alice,
|
||||
wantOK: true,
|
||||
wantDisplayName: changeDisplayName,
|
||||
},
|
||||
{
|
||||
name: "existing user is not allowed to change own name if name is empty",
|
||||
user: bob,
|
||||
wantOK: false,
|
||||
wantDisplayName: "",
|
||||
},
|
||||
}
|
||||
|
||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
|
||||
defer closeDB()
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
natsInstance := &jetstream.NATSInstance{}
|
||||
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil)
|
||||
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
|
||||
|
||||
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, 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) {
|
||||
wantDisplayName := tc.user.Localpart
|
||||
if tc.changeReq == nil {
|
||||
tc.changeReq = strings.NewReader("")
|
||||
}
|
||||
|
||||
// check profile after initial account creation
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/profile/"+tc.user.ID, strings.NewReader(""))
|
||||
t.Logf("%s", req.URL.String())
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d", rec.Code)
|
||||
}
|
||||
|
||||
if gotDisplayName := gjson.GetBytes(rec.Body.Bytes(), "displayname").Str; tc.wantOK && gotDisplayName != wantDisplayName {
|
||||
t.Fatalf("expected displayname to be '%s', but got '%s'", wantDisplayName, gotDisplayName)
|
||||
}
|
||||
|
||||
// now set the new display name
|
||||
wantDisplayName = tc.wantDisplayName
|
||||
tc.changeReq = strings.NewReader(fmt.Sprintf(`{"displayname":"%s"}`, tc.wantDisplayName))
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPut, "/_matrix/client/v3/profile/"+tc.user.ID+"/displayname", tc.changeReq)
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.user].accessToken)
|
||||
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
// now only get the display name
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/profile/"+tc.user.ID+"/displayname", strings.NewReader(""))
|
||||
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
if gotDisplayName := gjson.GetBytes(rec.Body.Bytes(), "displayname").Str; tc.wantOK && gotDisplayName != wantDisplayName {
|
||||
t.Fatalf("expected displayname to be '%s', but got '%s'", wantDisplayName, gotDisplayName)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetAvatarURL(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
bob := test.NewUser(t)
|
||||
notLocalUser := &test.User{ID: "@charlie:localhost", Localpart: "charlie"}
|
||||
changeDisplayName := "mxc://newMXID"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
user *test.User
|
||||
wantOK bool
|
||||
changeReq io.Reader
|
||||
avatar_url string
|
||||
}{
|
||||
{
|
||||
name: "invalid user",
|
||||
user: &test.User{ID: "!notauser"},
|
||||
},
|
||||
{
|
||||
name: "non-existent user",
|
||||
user: &test.User{ID: "@doesnotexist:test"},
|
||||
},
|
||||
{
|
||||
name: "non-local user is not allowed",
|
||||
user: notLocalUser,
|
||||
},
|
||||
{
|
||||
name: "existing user is allowed to change own avatar",
|
||||
user: alice,
|
||||
wantOK: true,
|
||||
avatar_url: changeDisplayName,
|
||||
},
|
||||
{
|
||||
name: "existing user is not allowed to change own avatar if avatar is empty",
|
||||
user: bob,
|
||||
wantOK: false,
|
||||
avatar_url: "",
|
||||
},
|
||||
}
|
||||
|
||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
|
||||
defer closeDB()
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
natsInstance := &jetstream.NATSInstance{}
|
||||
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil)
|
||||
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
|
||||
|
||||
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, 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) {
|
||||
wantAvatarURL := ""
|
||||
if tc.changeReq == nil {
|
||||
tc.changeReq = strings.NewReader("")
|
||||
}
|
||||
|
||||
// check profile after initial account creation
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/profile/"+tc.user.ID, strings.NewReader(""))
|
||||
t.Logf("%s", req.URL.String())
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d", rec.Code)
|
||||
}
|
||||
|
||||
if gotDisplayName := gjson.GetBytes(rec.Body.Bytes(), "avatar_url").Str; tc.wantOK && gotDisplayName != wantAvatarURL {
|
||||
t.Fatalf("expected displayname to be '%s', but got '%s'", wantAvatarURL, gotDisplayName)
|
||||
}
|
||||
|
||||
// now set the new display name
|
||||
wantAvatarURL = tc.avatar_url
|
||||
tc.changeReq = strings.NewReader(fmt.Sprintf(`{"avatar_url":"%s"}`, tc.avatar_url))
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPut, "/_matrix/client/v3/profile/"+tc.user.ID+"/avatar_url", tc.changeReq)
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.user].accessToken)
|
||||
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
// now only get the display name
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/profile/"+tc.user.ID+"/avatar_url", strings.NewReader(""))
|
||||
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
if gotDisplayName := gjson.GetBytes(rec.Body.Bytes(), "avatar_url").Str; tc.wantOK && gotDisplayName != wantAvatarURL {
|
||||
t.Fatalf("expected displayname to be '%s', but got '%s'", wantAvatarURL, gotDisplayName)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTyping(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
room := test.NewRoom(t, alice)
|
||||
ctx := context.Background()
|
||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||
defer close()
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
// Needed to create accounts
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
||||
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]userDevice{
|
||||
alice: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||
|
||||
// Create the room
|
||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
typingForUser string
|
||||
roomID string
|
||||
requestBody io.Reader
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "can not set typing for different user",
|
||||
typingForUser: "@notourself:test",
|
||||
roomID: room.ID,
|
||||
requestBody: strings.NewReader(""),
|
||||
},
|
||||
{
|
||||
name: "invalid request body",
|
||||
typingForUser: alice.ID,
|
||||
roomID: room.ID,
|
||||
requestBody: strings.NewReader(""),
|
||||
},
|
||||
{
|
||||
name: "non-existent room",
|
||||
typingForUser: alice.ID,
|
||||
roomID: "!doesnotexist:test",
|
||||
},
|
||||
{
|
||||
name: "invalid room ID",
|
||||
typingForUser: alice.ID,
|
||||
roomID: "@notaroomid:test",
|
||||
},
|
||||
{
|
||||
name: "allowed to set own typing status",
|
||||
typingForUser: alice.ID,
|
||||
roomID: room.ID,
|
||||
requestBody: strings.NewReader(`{"typing":true}`),
|
||||
wantOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPut, "/_matrix/client/v3/rooms/"+tc.roomID+"/typing/"+tc.typingForUser, tc.requestBody)
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMembership(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
bob := test.NewUser(t)
|
||||
room := test.NewRoom(t, alice)
|
||||
ctx := context.Background()
|
||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||
cfg.ClientAPI.RateLimiting.Enabled = false
|
||||
defer close()
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
// Needed to create accounts
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
rsAPI.SetUserAPI(userAPI)
|
||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
||||
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]userDevice{
|
||||
alice: {},
|
||||
bob: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||
|
||||
// Create the room
|
||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalidBodyRequest := func(roomID, membershipType string) *http.Request {
|
||||
return httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", roomID, membershipType), strings.NewReader(""))
|
||||
}
|
||||
|
||||
missingUserIDRequest := func(roomID, membershipType string) *http.Request {
|
||||
return httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", roomID, membershipType), strings.NewReader("{}"))
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
roomID string
|
||||
request *http.Request
|
||||
wantOK bool
|
||||
asUser *test.User
|
||||
}{
|
||||
{
|
||||
name: "ban - invalid request body",
|
||||
request: invalidBodyRequest(room.ID, "ban"),
|
||||
},
|
||||
{
|
||||
name: "kick - invalid request body",
|
||||
request: invalidBodyRequest(room.ID, "kick"),
|
||||
},
|
||||
{
|
||||
name: "unban - invalid request body",
|
||||
request: invalidBodyRequest(room.ID, "unban"),
|
||||
},
|
||||
{
|
||||
name: "invite - invalid request body",
|
||||
request: invalidBodyRequest(room.ID, "invite"),
|
||||
},
|
||||
{
|
||||
name: "ban - missing user_id body",
|
||||
request: missingUserIDRequest(room.ID, "ban"),
|
||||
},
|
||||
{
|
||||
name: "kick - missing user_id body",
|
||||
request: missingUserIDRequest(room.ID, "kick"),
|
||||
},
|
||||
{
|
||||
name: "unban - missing user_id body",
|
||||
request: missingUserIDRequest(room.ID, "unban"),
|
||||
},
|
||||
{
|
||||
name: "invite - missing user_id body",
|
||||
request: missingUserIDRequest(room.ID, "invite"),
|
||||
},
|
||||
{
|
||||
name: "Bob forgets invalid room",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", "!doesnotexist", "forget"), strings.NewReader("")),
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Alice can not ban Bob in non-existent room", // fails because "not joined"
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", "!doesnotexist:test", "ban"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
},
|
||||
{
|
||||
name: "Alice can not kick Bob in non-existent room", // fails because "not joined"
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", "!doesnotexist:test", "kick"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
},
|
||||
// the following must run in sequence, as they build up on each other
|
||||
{
|
||||
name: "Alice invites Bob",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "invite"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "Bob accepts invite",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "join"), strings.NewReader("")),
|
||||
wantOK: true,
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Alice verifies that Bob is joined", // returns an error if no membership event can be found
|
||||
request: httptest.NewRequest(http.MethodGet, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s/m.room.member/%s", room.ID, "state", bob.ID), strings.NewReader("")),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "Bob forgets the room but is still a member",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "forget"), strings.NewReader("")),
|
||||
wantOK: false, // user is still in the room
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Bob can not kick Alice",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "kick"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, alice.ID))),
|
||||
wantOK: false, // powerlevel too low
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Bob can not ban Alice",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "ban"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, alice.ID))),
|
||||
wantOK: false, // powerlevel too low
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Alice can kick Bob",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "kick"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alice can ban Bob",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "ban"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alice can not kick Bob again",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "kick"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: false, // can not kick banned/left user
|
||||
},
|
||||
{
|
||||
name: "Bob can not unban himself", // mostly because of not being a member of the room
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "unban"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Alice can not invite Bob again",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "invite"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: false, // user still banned
|
||||
},
|
||||
{
|
||||
name: "Alice can unban Bob",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "unban"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alice can not unban Bob again",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "unban"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: false,
|
||||
},
|
||||
{
|
||||
name: "Alice can invite Bob again",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "invite"), strings.NewReader(fmt.Sprintf(`{"user_id":"%s"}`, bob.ID))),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "Bob can reject the invite by leaving",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "leave"), strings.NewReader("")),
|
||||
wantOK: true,
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Bob can forget the room",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "forget"), strings.NewReader("")),
|
||||
wantOK: true,
|
||||
asUser: bob,
|
||||
},
|
||||
{
|
||||
name: "Bob can forget the room again",
|
||||
request: httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/%s", room.ID, "forget"), strings.NewReader("")),
|
||||
wantOK: true,
|
||||
asUser: bob,
|
||||
},
|
||||
// END must run in sequence
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.asUser == nil {
|
||||
tc.asUser = alice
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
tc.request.Header.Set("Authorization", "Bearer "+accessTokens[tc.asUser].accessToken)
|
||||
routers.Client.ServeHTTP(rec, tc.request)
|
||||
if tc.wantOK && rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if !tc.wantOK && rec.Code == http.StatusOK {
|
||||
t.Fatalf("expected request to fail, but didn't: %s", rec.Body.String())
|
||||
}
|
||||
t.Logf("%s", rec.Body.String())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCapabilities(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
ctx := context.Background()
|
||||
|
|
@ -74,8 +946,8 @@ func TestCapabilities(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{
|
||||
alice: "",
|
||||
accessTokens := map[*test.User]userDevice{
|
||||
alice: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||
|
||||
|
|
@ -92,7 +964,7 @@ func TestCapabilities(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
tc.request.Header.Set("Authorization", "Bearer "+accessTokens[alice])
|
||||
tc.request.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
||||
routers.Client.ServeHTTP(rec, tc.request)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.ObjectsAreEqual(expectedBuf.Bytes(), rec.Body.Bytes())
|
||||
|
|
@ -121,8 +993,8 @@ func TestTurnserver(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{
|
||||
alice: "",
|
||||
accessTokens := map[*test.User]userDevice{
|
||||
alice: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||
|
||||
|
|
@ -169,7 +1041,7 @@ func TestTurnserver(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/voip/turnServer", strings.NewReader(""))
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice])
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
||||
cfg.ClientAPI.TURN = tc.turnConfig
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
|
|
@ -217,8 +1089,8 @@ func Test3PID(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{
|
||||
alice: "",
|
||||
accessTokens := map[*test.User]userDevice{
|
||||
alice: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||
|
||||
|
|
@ -340,7 +1212,7 @@ func Test3PID(t *testing.T) {
|
|||
}
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
tc.request.Header.Set("Authorization", "Bearer "+accessTokens[alice])
|
||||
tc.request.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
||||
|
||||
routers.Client.ServeHTTP(rec, tc.request)
|
||||
t.Logf("Response: %s", rec.Body.String())
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
// initiate UIA
|
||||
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device)
|
||||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
|
||||
defer req.Body.Close() // nolint: errcheck
|
||||
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{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"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"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
|
@ -61,21 +62,19 @@ func JoinRoomByIDOrAlias(
|
|||
// Work out our localpart for the client profile request.
|
||||
|
||||
// Request our profile content to populate the request content with.
|
||||
res := &api.QueryProfileResponse{}
|
||||
err := profileAPI.QueryProfile(req.Context(), &api.QueryProfileRequest{UserID: device.UserID}, res)
|
||||
if err != nil || !res.UserExists {
|
||||
if !res.UserExists {
|
||||
util.GetLogger(req.Context()).Error("Unable to query user profile, no profile found.")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("Unable to query user profile, no profile found."),
|
||||
}
|
||||
}
|
||||
profile, err := profileAPI.QueryProfile(req.Context(), device.UserID)
|
||||
|
||||
util.GetLogger(req.Context()).WithError(err).Error("UserProfileAPI.QueryProfile failed")
|
||||
} else {
|
||||
joinReq.Content["displayname"] = res.DisplayName
|
||||
joinReq.Content["avatar_url"] = res.AvatarURL
|
||||
switch err {
|
||||
case nil:
|
||||
joinReq.Content["displayname"] = profile.DisplayName
|
||||
joinReq.Content["avatar_url"] = profile.AvatarURL
|
||||
case appserviceAPI.ErrProfileNotExists:
|
||||
util.GetLogger(req.Context()).Error("Unable to query user profile, no profile found.")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("Unable to query user profile, no profile found."),
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
// Ask the roomserver to perform the join.
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ package routing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
|
|
@ -31,76 +32,56 @@ import (
|
|||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
var errMissingUserID = errors.New("'user_id' must be supplied")
|
||||
|
||||
func SendBan(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI, device *userapi.Device,
|
||||
roomID string, cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
body, evTime, roomVer, reqErr := extractRequestData(req, roomID, rsAPI)
|
||||
body, evTime, reqErr := extractRequestData(req)
|
||||
if reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
|
||||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("missing user_id"),
|
||||
}
|
||||
}
|
||||
|
||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
||||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
|
||||
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
if plEvent == nil {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to ban this user, no power_levels event in this room."),
|
||||
}
|
||||
}
|
||||
pl, err := plEvent.PowerLevels()
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to ban this user, the power_levels event for this room is malformed so auth checks cannot be performed."),
|
||||
}
|
||||
pl, errRes := getPowerlevels(req, rsAPI, roomID)
|
||||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
allowedToBan := pl.UserLevel(device.UserID) >= pl.Ban
|
||||
if !allowedToBan {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to ban this user, power level too low."),
|
||||
}
|
||||
}
|
||||
|
||||
return sendMembership(req.Context(), profileAPI, device, roomID, "ban", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI)
|
||||
return sendMembership(req.Context(), profileAPI, device, roomID, gomatrixserverlib.Ban, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI)
|
||||
}
|
||||
|
||||
func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, device *userapi.Device,
|
||||
roomID, membership, reason string, cfg *config.ClientAPI, targetUserID string, evTime time.Time,
|
||||
roomVer gomatrixserverlib.RoomVersion,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI) util.JSONResponse {
|
||||
|
||||
event, err := buildMembershipEvent(
|
||||
ctx, targetUserID, reason, profileAPI, device, membership,
|
||||
roomID, false, cfg, evTime, rsAPI, asAPI,
|
||||
)
|
||||
if err == errMissingUserID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(err.Error()),
|
||||
}
|
||||
} else if err == eventutil.ErrRoomNoExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound(err.Error()),
|
||||
}
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
|
@ -109,7 +90,7 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic
|
|||
if err = roomserverAPI.SendEvents(
|
||||
ctx, rsAPI,
|
||||
roomserverAPI.KindNew,
|
||||
[]*gomatrixserverlib.HeaderedEvent{event.Event.Headered(roomVer)},
|
||||
[]*gomatrixserverlib.HeaderedEvent{event},
|
||||
device.UserDomain(),
|
||||
serverName,
|
||||
serverName,
|
||||
|
|
@ -131,13 +112,65 @@ func SendKick(
|
|||
roomID string, cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
body, evTime, roomVer, reqErr := extractRequestData(req, roomID, rsAPI)
|
||||
body, evTime, reqErr := extractRequestData(req)
|
||||
if reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("missing user_id"),
|
||||
}
|
||||
}
|
||||
|
||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
||||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
|
||||
pl, errRes := getPowerlevels(req, rsAPI, roomID)
|
||||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
allowedToKick := pl.UserLevel(device.UserID) >= pl.Kick
|
||||
if !allowedToKick {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to kick this user, power level too low."),
|
||||
}
|
||||
}
|
||||
|
||||
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
||||
err := rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: body.UserID,
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
// kick is only valid if the user is not currently banned or left (that is, they are joined or invited)
|
||||
if queryRes.Membership != gomatrixserverlib.Join && queryRes.Membership != gomatrixserverlib.Invite {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Unknown("cannot /kick banned or left users"),
|
||||
}
|
||||
}
|
||||
// TODO: should we be using SendLeave instead?
|
||||
return sendMembership(req.Context(), profileAPI, device, roomID, gomatrixserverlib.Leave, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI)
|
||||
}
|
||||
|
||||
func SendUnban(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI, device *userapi.Device,
|
||||
roomID string, cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
body, evTime, reqErr := extractRequestData(req)
|
||||
if reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("missing user_id"),
|
||||
}
|
||||
}
|
||||
|
|
@ -155,56 +188,16 @@ func SendKick(
|
|||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
// kick is only valid if the user is not currently banned or left (that is, they are joined or invited)
|
||||
if queryRes.Membership != "join" && queryRes.Membership != "invite" {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Unknown("cannot /kick banned or left users"),
|
||||
}
|
||||
}
|
||||
// TODO: should we be using SendLeave instead?
|
||||
return sendMembership(req.Context(), profileAPI, device, roomID, "leave", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI)
|
||||
}
|
||||
|
||||
func SendUnban(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI, device *userapi.Device,
|
||||
roomID string, cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
body, evTime, roomVer, reqErr := extractRequestData(req, roomID, rsAPI)
|
||||
if reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.BadJSON("missing user_id"),
|
||||
}
|
||||
}
|
||||
|
||||
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
||||
err := rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: body.UserID,
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
if !queryRes.RoomExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("room does not exist"),
|
||||
}
|
||||
}
|
||||
// unban is only valid if the user is currently banned
|
||||
if queryRes.Membership != "ban" {
|
||||
if queryRes.Membership != gomatrixserverlib.Ban {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown("can only /unban users that are banned"),
|
||||
}
|
||||
}
|
||||
// TODO: should we be using SendLeave instead?
|
||||
return sendMembership(req.Context(), profileAPI, device, roomID, "leave", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI)
|
||||
return sendMembership(req.Context(), profileAPI, device, roomID, gomatrixserverlib.Leave, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI)
|
||||
}
|
||||
|
||||
func SendInvite(
|
||||
|
|
@ -212,7 +205,7 @@ func SendInvite(
|
|||
roomID string, cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
body, evTime, _, reqErr := extractRequestData(req, roomID, rsAPI)
|
||||
body, evTime, reqErr := extractRequestData(req)
|
||||
if reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
|
|
@ -234,6 +227,18 @@ func SendInvite(
|
|||
}
|
||||
}
|
||||
|
||||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("missing user_id"),
|
||||
}
|
||||
}
|
||||
|
||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
||||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
|
||||
// We already received the return value, so no need to check for an error here.
|
||||
response, _ := sendInvite(req.Context(), profileAPI, device, roomID, body.UserID, body.Reason, cfg, rsAPI, asAPI, evTime)
|
||||
return response
|
||||
|
|
@ -250,20 +255,10 @@ func sendInvite(
|
|||
asAPI appserviceAPI.AppServiceInternalAPI, evTime time.Time,
|
||||
) (util.JSONResponse, error) {
|
||||
event, err := buildMembershipEvent(
|
||||
ctx, userID, reason, profileAPI, device, "invite",
|
||||
ctx, userID, reason, profileAPI, device, gomatrixserverlib.Invite,
|
||||
roomID, false, cfg, evTime, rsAPI, asAPI,
|
||||
)
|
||||
if err == errMissingUserID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(err.Error()),
|
||||
}, err
|
||||
} else if err == eventutil.ErrRoomNoExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound(err.Error()),
|
||||
}, err
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||
return jsonerror.InternalServerError(), err
|
||||
}
|
||||
|
|
@ -357,19 +352,7 @@ func loadProfile(
|
|||
return profile, err
|
||||
}
|
||||
|
||||
func extractRequestData(req *http.Request, roomID string, rsAPI roomserverAPI.ClientRoomserverAPI) (
|
||||
body *threepid.MembershipRequest, evTime time.Time, roomVer gomatrixserverlib.RoomVersion, resErr *util.JSONResponse,
|
||||
) {
|
||||
verReq := roomserverAPI.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||
verRes := roomserverAPI.QueryRoomVersionForRoomResponse{}
|
||||
if err := rsAPI.QueryRoomVersionForRoom(req.Context(), &verReq, &verRes); err != nil {
|
||||
resErr = &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UnsupportedRoomVersion(err.Error()),
|
||||
}
|
||||
return
|
||||
}
|
||||
roomVer = verRes.RoomVersion
|
||||
func extractRequestData(req *http.Request) (body *threepid.MembershipRequest, evTime time.Time, resErr *util.JSONResponse) {
|
||||
|
||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||
resErr = reqErr
|
||||
|
|
@ -432,34 +415,17 @@ func checkAndProcessThreepid(
|
|||
}
|
||||
|
||||
func checkMemberInRoom(ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, userID, roomID string) *util.JSONResponse {
|
||||
tuple := gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomMember,
|
||||
StateKey: userID,
|
||||
}
|
||||
var membershipRes roomserverAPI.QueryCurrentStateResponse
|
||||
err := rsAPI.QueryCurrentState(ctx, &roomserverAPI.QueryCurrentStateRequest{
|
||||
RoomID: roomID,
|
||||
StateTuples: []gomatrixserverlib.StateKeyTuple{tuple},
|
||||
var membershipRes roomserverAPI.QueryMembershipForUserResponse
|
||||
err := rsAPI.QueryMembershipForUser(ctx, &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: userID,
|
||||
}, &membershipRes)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("QueryCurrentState: could not query membership for user")
|
||||
util.GetLogger(ctx).WithError(err).Error("QueryMembershipForUser: could not query membership for user")
|
||||
e := jsonerror.InternalServerError()
|
||||
return &e
|
||||
}
|
||||
ev := membershipRes.StateEvents[tuple]
|
||||
if ev == nil {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("user does not belong to room"),
|
||||
}
|
||||
}
|
||||
membership, err := ev.Membership()
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("Member event isn't valid")
|
||||
e := jsonerror.InternalServerError()
|
||||
return &e
|
||||
}
|
||||
if membership != gomatrixserverlib.Join {
|
||||
if !membershipRes.IsInRoom {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("user does not belong to room"),
|
||||
|
|
@ -511,3 +477,24 @@ func SendForget(
|
|||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, roomID string) (*gomatrixserverlib.PowerLevelContent, *util.JSONResponse) {
|
||||
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: gomatrixserverlib.MRoomPowerLevels,
|
||||
StateKey: "",
|
||||
})
|
||||
if plEvent == nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to perform this action, no power_levels event in this room."),
|
||||
}
|
||||
}
|
||||
pl, err := plEvent.PowerLevels()
|
||||
if err != nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to perform this action, the power_levels event for this room is malformed so auth checks cannot be performed."),
|
||||
}
|
||||
}
|
||||
return pl, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ import (
|
|||
|
||||
// GetProfile implements GET /profile/{userID}
|
||||
func GetProfile(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI, cfg *config.ClientAPI,
|
||||
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
) util.JSONResponse {
|
||||
profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
|
||||
if err != nil {
|
||||
if err == eventutil.ErrProfileNoExists {
|
||||
if err == appserviceAPI.ErrProfileNotExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
|
||||
|
|
@ -56,7 +56,7 @@ func GetProfile(
|
|||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: eventutil.ProfileResponse{
|
||||
JSON: eventutil.UserProfile{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
},
|
||||
|
|
@ -65,34 +65,28 @@ func GetProfile(
|
|||
|
||||
// GetAvatarURL implements GET /profile/{userID}/avatar_url
|
||||
func GetAvatarURL(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI, cfg *config.ClientAPI,
|
||||
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
) util.JSONResponse {
|
||||
profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
|
||||
if err != nil {
|
||||
if err == eventutil.ErrProfileNoExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
|
||||
}
|
||||
}
|
||||
|
||||
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
||||
return jsonerror.InternalServerError()
|
||||
profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation)
|
||||
p, ok := profile.JSON.(eventutil.UserProfile)
|
||||
// not a profile response, so most likely an error, return that
|
||||
if !ok {
|
||||
return profile
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: eventutil.AvatarURL{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
JSON: eventutil.UserProfile{
|
||||
AvatarURL: p.AvatarURL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetAvatarURL implements PUT /profile/{userID}/avatar_url
|
||||
func SetAvatarURL(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI,
|
||||
req *http.Request, profileAPI userapi.ProfileAPI,
|
||||
device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.ClientRoomserverAPI,
|
||||
) util.JSONResponse {
|
||||
if userID != device.UserID {
|
||||
|
|
@ -102,7 +96,7 @@ func SetAvatarURL(
|
|||
}
|
||||
}
|
||||
|
||||
var r eventutil.AvatarURL
|
||||
var r eventutil.UserProfile
|
||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
|
@ -134,24 +128,20 @@ func SetAvatarURL(
|
|||
}
|
||||
}
|
||||
|
||||
setRes := &userapi.PerformSetAvatarURLResponse{}
|
||||
if err = profileAPI.SetAvatarURL(req.Context(), &userapi.PerformSetAvatarURLRequest{
|
||||
Localpart: localpart,
|
||||
ServerName: domain,
|
||||
AvatarURL: r.AvatarURL,
|
||||
}, setRes); err != nil {
|
||||
profile, changed, err := profileAPI.SetAvatarURL(req.Context(), localpart, domain, r.AvatarURL)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
// No need to build new membership events, since nothing changed
|
||||
if !setRes.Changed {
|
||||
if !changed {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
response, err := updateProfile(req.Context(), rsAPI, device, setRes.Profile, userID, cfg, evTime)
|
||||
response, err := updateProfile(req.Context(), rsAPI, device, profile, userID, cfg, evTime)
|
||||
if err != nil {
|
||||
return response
|
||||
}
|
||||
|
|
@ -164,34 +154,28 @@ func SetAvatarURL(
|
|||
|
||||
// GetDisplayName implements GET /profile/{userID}/displayname
|
||||
func GetDisplayName(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI, cfg *config.ClientAPI,
|
||||
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
) util.JSONResponse {
|
||||
profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
|
||||
if err != nil {
|
||||
if err == eventutil.ErrProfileNoExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
|
||||
}
|
||||
}
|
||||
|
||||
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
||||
return jsonerror.InternalServerError()
|
||||
profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation)
|
||||
p, ok := profile.JSON.(eventutil.UserProfile)
|
||||
// not a profile response, so most likely an error, return that
|
||||
if !ok {
|
||||
return profile
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: eventutil.DisplayName{
|
||||
DisplayName: profile.DisplayName,
|
||||
JSON: eventutil.UserProfile{
|
||||
DisplayName: p.DisplayName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetDisplayName implements PUT /profile/{userID}/displayname
|
||||
func SetDisplayName(
|
||||
req *http.Request, profileAPI userapi.ClientUserAPI,
|
||||
req *http.Request, profileAPI userapi.ProfileAPI,
|
||||
device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.ClientRoomserverAPI,
|
||||
) util.JSONResponse {
|
||||
if userID != device.UserID {
|
||||
|
|
@ -201,7 +185,7 @@ func SetDisplayName(
|
|||
}
|
||||
}
|
||||
|
||||
var r eventutil.DisplayName
|
||||
var r eventutil.UserProfile
|
||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
|
@ -233,25 +217,20 @@ func SetDisplayName(
|
|||
}
|
||||
}
|
||||
|
||||
profileRes := &userapi.PerformUpdateDisplayNameResponse{}
|
||||
err = profileAPI.SetDisplayName(req.Context(), &userapi.PerformUpdateDisplayNameRequest{
|
||||
Localpart: localpart,
|
||||
ServerName: domain,
|
||||
DisplayName: r.DisplayName,
|
||||
}, profileRes)
|
||||
profile, changed, err := profileAPI.SetDisplayName(req.Context(), localpart, domain, r.DisplayName)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
// No need to build new membership events, since nothing changed
|
||||
if !profileRes.Changed {
|
||||
if !changed {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
response, err := updateProfile(req.Context(), rsAPI, device, profileRes.Profile, userID, cfg, evTime)
|
||||
response, err := updateProfile(req.Context(), rsAPI, device, profile, userID, cfg, evTime)
|
||||
if err != nil {
|
||||
return response
|
||||
}
|
||||
|
|
@ -308,9 +287,9 @@ func updateProfile(
|
|||
// getProfile gets the full profile of a user by querying the database or a
|
||||
// remote homeserver.
|
||||
// Returns an error when something goes wrong or specifically
|
||||
// eventutil.ErrProfileNoExists when the profile doesn't exist.
|
||||
// eventutil.ErrProfileNotExists when the profile doesn't exist.
|
||||
func getProfile(
|
||||
ctx context.Context, profileAPI userapi.ClientUserAPI, cfg *config.ClientAPI,
|
||||
ctx context.Context, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *gomatrixserverlib.FederationClient,
|
||||
|
|
@ -325,7 +304,7 @@ func getProfile(
|
|||
if fedErr != nil {
|
||||
if x, ok := fedErr.(gomatrix.HTTPError); ok {
|
||||
if x.Code == http.StatusNotFound {
|
||||
return nil, eventutil.ErrProfileNoExists
|
||||
return nil, appserviceAPI.ErrProfileNotExists
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -888,13 +888,7 @@ func completeRegistration(
|
|||
}
|
||||
|
||||
if displayName != "" {
|
||||
nameReq := userapi.PerformUpdateDisplayNameRequest{
|
||||
Localpart: username,
|
||||
ServerName: serverName,
|
||||
DisplayName: displayName,
|
||||
}
|
||||
var nameRes userapi.PerformUpdateDisplayNameResponse
|
||||
err = userAPI.SetDisplayName(ctx, &nameReq, &nameRes)
|
||||
_, _, err = userAPI.SetDisplayName(ctx, username, serverName, displayName)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
|
|
|
|||
|
|
@ -611,11 +611,9 @@ func TestRegisterUserWithDisplayName(t *testing.T) {
|
|||
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
|
||||
req := api.QueryProfileRequest{UserID: "@user:server"}
|
||||
var res api.QueryProfileResponse
|
||||
err := userAPI.QueryProfile(processCtx.Context(), &req, &res)
|
||||
profile, err := userAPI.QueryProfile(processCtx.Context(), "@user:server")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedDisplayName, res.DisplayName)
|
||||
assert.Equal(t, expectedDisplayName, profile.DisplayName)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -662,10 +660,8 @@ func TestRegisterAdminUsingSharedSecret(t *testing.T) {
|
|||
)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
|
||||
profilReq := api.QueryProfileRequest{UserID: "@alice:server"}
|
||||
var profileRes api.QueryProfileResponse
|
||||
err = userAPI.QueryProfile(processCtx.Context(), &profilReq, &profileRes)
|
||||
profile, err := userAPI.QueryProfile(processCtx.Context(), "@alice:server")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedDisplayName, profileRes.DisplayName)
|
||||
assert.Equal(t, expectedDisplayName, profile.DisplayName)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
|
|
@ -199,18 +198,13 @@ func Setup(
|
|||
// server notifications
|
||||
if cfg.Matrix.ServerNotices.Enabled {
|
||||
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
|
||||
var serverNotificationSender *userapi.Device
|
||||
var err error
|
||||
notificationSenderOnce := &sync.Once{}
|
||||
serverNotificationSender, err := getSenderDevice(context.Background(), rsAPI, userAPI, cfg)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
|
||||
}
|
||||
|
||||
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
|
||||
httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
notificationSenderOnce.Do(func() {
|
||||
serverNotificationSender, err = getSenderDevice(context.Background(), rsAPI, userAPI, cfg)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
|
||||
}
|
||||
})
|
||||
// not specced, but ensure we're rate limiting requests to this endpoint
|
||||
if r := rateLimits.Limit(req, device); r != nil {
|
||||
return *r
|
||||
|
|
@ -232,12 +226,6 @@ func Setup(
|
|||
|
||||
synapseAdminRouter.Handle("/admin/v1/send_server_notice",
|
||||
httputil.MakeAuthAPI("send_server_notice", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
notificationSenderOnce.Do(func() {
|
||||
serverNotificationSender, err = getSenderDevice(context.Background(), rsAPI, userAPI, cfg)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
|
||||
}
|
||||
})
|
||||
// not specced, but ensure we're rate limiting requests to this endpoint
|
||||
if r := rateLimits.Limit(req, device); r != nil {
|
||||
return *r
|
||||
|
|
@ -1118,7 +1106,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)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type typingContentJSON struct {
|
||||
|
|
|
|||
|
|
@ -295,30 +295,28 @@ func getSenderDevice(
|
|||
}
|
||||
|
||||
// Set the avatarurl for the user
|
||||
avatarRes := &userapi.PerformSetAvatarURLResponse{}
|
||||
if err = userAPI.SetAvatarURL(ctx, &userapi.PerformSetAvatarURLRequest{
|
||||
Localpart: cfg.Matrix.ServerNotices.LocalPart,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
AvatarURL: cfg.Matrix.ServerNotices.AvatarURL,
|
||||
}, avatarRes); err != nil {
|
||||
profile, avatarChanged, err := userAPI.SetAvatarURL(ctx,
|
||||
cfg.Matrix.ServerNotices.LocalPart,
|
||||
cfg.Matrix.ServerName,
|
||||
cfg.Matrix.ServerNotices.AvatarURL,
|
||||
)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("userAPI.SetAvatarURL failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile := avatarRes.Profile
|
||||
|
||||
// Set the displayname for the user
|
||||
displayNameRes := &userapi.PerformUpdateDisplayNameResponse{}
|
||||
if err = userAPI.SetDisplayName(ctx, &userapi.PerformUpdateDisplayNameRequest{
|
||||
Localpart: cfg.Matrix.ServerNotices.LocalPart,
|
||||
ServerName: cfg.Matrix.ServerName,
|
||||
DisplayName: cfg.Matrix.ServerNotices.DisplayName,
|
||||
}, displayNameRes); err != nil {
|
||||
_, displayNameChanged, err := userAPI.SetDisplayName(ctx,
|
||||
cfg.Matrix.ServerNotices.LocalPart,
|
||||
cfg.Matrix.ServerName,
|
||||
cfg.Matrix.ServerNotices.DisplayName,
|
||||
)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("userAPI.SetDisplayName failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if displayNameRes.Changed {
|
||||
if displayNameChanged {
|
||||
profile.DisplayName = cfg.Matrix.ServerNotices.DisplayName
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +332,7 @@ func getSenderDevice(
|
|||
// We've got an existing account, return the first device of it
|
||||
if len(deviceRes.Devices) > 0 {
|
||||
// If there were changes to the profile, create a new membership event
|
||||
if displayNameRes.Changed || avatarRes.Changed {
|
||||
if displayNameChanged || avatarChanged {
|
||||
_, err = updateProfile(ctx, rsAPI, &deviceRes.Devices[0], profile, accRes.Account.UserID, cfg, time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -209,24 +209,17 @@ func queryIDServerStoreInvite(
|
|||
body *MembershipRequest, roomID string,
|
||||
) (*idServerStoreInviteResponse, error) {
|
||||
// Retrieve the sender's profile to get their display name
|
||||
localpart, serverName, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
_, serverName, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var profile *authtypes.Profile
|
||||
if cfg.Matrix.IsLocalServerName(serverName) {
|
||||
res := &userapi.QueryProfileResponse{}
|
||||
err = userAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: device.UserID}, res)
|
||||
profile, err = userAPI.QueryProfile(ctx, device.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profile = &authtypes.Profile{
|
||||
Localpart: localpart,
|
||||
DisplayName: res.DisplayName,
|
||||
AvatarURL: res.AvatarURL,
|
||||
}
|
||||
|
||||
} else {
|
||||
profile = &authtypes.Profile{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Mostly, although there are still bugs and missing features. If you are a confide
|
|||
|
||||
## Is Dendrite feature-complete?
|
||||
|
||||
No, although a good portion of the Matrix specification has been implemented. Mostly missing are client features - see the [readme](../README.md) at the root of the repository for more information.
|
||||
No, although a good portion of the Matrix specification has been implemented. Mostly missing are client features - see the [readme](https://github.com/matrix-org/dendrite/blob/main/README.md) at the root of the repository for more information.
|
||||
|
||||
## Why doesn't Dendrite have "x" yet?
|
||||
|
||||
|
|
|
|||
|
|
@ -50,10 +50,7 @@ func GetProfile(
|
|||
}
|
||||
}
|
||||
|
||||
var profileRes userapi.QueryProfileResponse
|
||||
err = userAPI.QueryProfile(httpReq.Context(), &userapi.QueryProfileRequest{
|
||||
UserID: userID,
|
||||
}, &profileRes)
|
||||
profile, err := userAPI.QueryProfile(httpReq.Context(), userID)
|
||||
if err != nil {
|
||||
util.GetLogger(httpReq.Context()).WithError(err).Error("userAPI.QueryProfile failed")
|
||||
return jsonerror.InternalServerError()
|
||||
|
|
@ -65,21 +62,21 @@ func GetProfile(
|
|||
if field != "" {
|
||||
switch field {
|
||||
case "displayname":
|
||||
res = eventutil.DisplayName{
|
||||
DisplayName: profileRes.DisplayName,
|
||||
res = eventutil.UserProfile{
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
case "avatar_url":
|
||||
res = eventutil.AvatarURL{
|
||||
AvatarURL: profileRes.AvatarURL,
|
||||
res = eventutil.UserProfile{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
}
|
||||
default:
|
||||
code = http.StatusBadRequest
|
||||
res = jsonerror.InvalidArgumentValue("The request body did not contain an allowed value of argument 'field'. Allowed values are either: 'avatar_url', 'displayname'.")
|
||||
}
|
||||
} else {
|
||||
res = eventutil.ProfileResponse{
|
||||
AvatarURL: profileRes.AvatarURL,
|
||||
DisplayName: profileRes.DisplayName,
|
||||
res = eventutil.UserProfile{
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
||||
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
|
||||
|
|
@ -43,8 +44,8 @@ type fakeUserAPI struct {
|
|||
userAPI.FederationUserAPI
|
||||
}
|
||||
|
||||
func (u *fakeUserAPI) QueryProfile(ctx context.Context, req *userAPI.QueryProfileRequest, res *userAPI.QueryProfileResponse) error {
|
||||
return nil
|
||||
func (u *fakeUserAPI) QueryProfile(ctx context.Context, userID string) (*authtypes.Profile, error) {
|
||||
return &authtypes.Profile{}, nil
|
||||
}
|
||||
|
||||
func TestHandleQueryProfile(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -256,17 +256,14 @@ func createInviteFrom3PIDInvite(
|
|||
StateKey: &inv.MXID,
|
||||
}
|
||||
|
||||
var res userapi.QueryProfileResponse
|
||||
err = userAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{
|
||||
UserID: inv.MXID,
|
||||
}, &res)
|
||||
profile, err := userAPI.QueryProfile(ctx, inv.MXID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := gomatrixserverlib.MemberContent{
|
||||
AvatarURL: res.AvatarURL,
|
||||
DisplayName: res.DisplayName,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
DisplayName: profile.DisplayName,
|
||||
Membership: gomatrixserverlib.Invite,
|
||||
ThirdPartyInvite: &gomatrixserverlib.MemberThirdPartyInvite{
|
||||
Signed: inv.Signed,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
{{ define "chart.monitoringSection" }}
|
||||
## Monitoring
|
||||
|
||||
[](https://grafana.com/grafana/dashboards/13916-dendrite/)
|
||||

|
||||
|
||||
* Works well with [Prometheus Operator](https://prometheus-operator.dev/) ([Helmchart](https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack)) and their setup of [Grafana](https://grafana.com/grafana/), by enabling the following values:
|
||||
```yaml
|
||||
dendrite_config:
|
||||
global:
|
||||
metrics:
|
||||
enabled: true
|
||||
|
||||
prometheus:
|
||||
servicemonitor:
|
||||
enabled: true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: dendrite
|
||||
version: "0.12.1"
|
||||
version: "0.12.2"
|
||||
appVersion: "0.12.0"
|
||||
description: Dendrite Matrix Homeserver
|
||||
type: application
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
# dendrite
|
||||
|
||||
  
|
||||
  
|
||||
Dendrite Matrix Homeserver
|
||||
|
||||
Status: **NOT PRODUCTION READY**
|
||||
|
|
@ -54,6 +55,11 @@ Create a folder `appservices` and place your configurations in there. The confi
|
|||
| persistence.media.capacity | string | `"1Gi"` | PVC Storage Request for the media volume |
|
||||
| persistence.search.existingClaim | string | `""` | Use an existing volume claim for the fulltext search index |
|
||||
| persistence.search.capacity | string | `"1Gi"` | PVC Storage Request for the search volume |
|
||||
| extraVolumes | list | `[]` | Add additional volumes to the Dendrite Pod |
|
||||
| extraVolumeMounts | list | `[]` | Configure additional mount points volumes in the Dendrite Pod |
|
||||
| strategy.type | string | `"RollingUpdate"` | Strategy to use for rolling updates (e.g. Recreate, RollingUpdate) If you are using ReadWriteOnce volumes, you should probably use Recreate |
|
||||
| strategy.rollingUpdate.maxUnavailable | string | `"25%"` | Maximum number of pods that can be unavailable during the update process |
|
||||
| strategy.rollingUpdate.maxSurge | string | `"25%"` | Maximum number of pods that can be scheduled above the desired number of pods |
|
||||
| dendrite_config.version | int | `2` | |
|
||||
| dendrite_config.global.server_name | string | `""` | **REQUIRED** Servername for this Dendrite deployment. |
|
||||
| dendrite_config.global.private_key | string | `"/etc/dendrite/secrets/signing.key"` | The private key to use. (**NOTE**: This is overriden in Helm) |
|
||||
|
|
@ -157,10 +163,15 @@ Create a folder `appservices` and place your configurations in there. The confi
|
|||
|
||||
## Monitoring
|
||||
|
||||
[](https://grafana.com/grafana/dashboards/13916-dendrite/)
|
||||

|
||||
|
||||
* Works well with [Prometheus Operator](https://prometheus-operator.dev/) ([Helmchart](https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack)) and their setup of [Grafana](https://grafana.com/grafana/), by enabling the following values:
|
||||
```yaml
|
||||
dendrite_config:
|
||||
global:
|
||||
metrics:
|
||||
enabled: true
|
||||
|
||||
prometheus:
|
||||
servicemonitor:
|
||||
enabled: true
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
479
helm/dendrite/grafana_dashboards/dendrite-rev2.json
Normal file
479
helm/dendrite/grafana_dashboards/dendrite-rev2.json
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Dendrite dashboard from https://github.com/matrix-org/dendrite/",
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"gnetId": 13916,
|
||||
"graphTooltip": 0,
|
||||
"id": 60,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"panels": [],
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Overview",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "Total number of registered users",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 7,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 20,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "9.3.6",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(dendrite_clientapi_reg_users_total{namespace=~\"$namespace\",service=~\"$service\"}) by (namespace,service)",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{namespace}}: {{service}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Registerd Users",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "The number of sync requests that are active right now and are waiting to be woken by a notifier",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 2,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "ops"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 17,
|
||||
"x": 7,
|
||||
"y": 1
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"mean",
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "9.3.6",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(dendrite_syncapi_active_sync_requests{namespace=~\"$namespace\",service=~\"$service\"}[$__rate_interval]))by (namspace,service)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "active: {{namspace}} - {{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(dendrite_syncapi_waiting_sync_requests{namespace=~\"$namespace\",service=~\"$service\"}[$__rate_interval]))by (namespace,service)",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "waiting: {{namspace}} - {{service}}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Sync API",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 8,
|
||||
"panels": [],
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Federation",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "Collection of queues for sending transactions to other matrix servers",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"mean",
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "9.3.6",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "dendrite_federationapi_destination_queues_running{namespace=~\"$namespace\",service=~\"$service\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "Queue Running: {{namespace}}-{{service}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "dendrite_federationapi_destination_queues_total{namespace=~\"$namespace\",service=~\"$service\"}",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Queue Total: {{namespace}}-{{service}}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "dendrite_federationapi_destination_queues_backing_off{namespace=~\"$namespace\",service=~\"$service\"}",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Backing Off: {{namespace}}-{{service}}",
|
||||
"range": true,
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "Federation Sender Destination",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "10s",
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"matrix",
|
||||
"dendrite"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "Prometheus",
|
||||
"value": "Prometheus"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "datasource",
|
||||
"multi": false,
|
||||
"name": "DS_PROMETHEUS",
|
||||
"options": [],
|
||||
"query": "prometheus",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": true,
|
||||
"text": [
|
||||
"All"
|
||||
],
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(dendrite_syncapi_active_sync_requests, namespace)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "namespace",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(dendrite_syncapi_active_sync_requests, namespace)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(dendrite_syncapi_active_sync_requests{namespace=~\"$namespace\"}, service)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "service",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(dendrite_syncapi_active_sync_requests{namespace=~\"$namespace\"}, service)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-3h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Dendrite",
|
||||
"uid": "RoRt1jEGz",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
BIN
helm/dendrite/grafana_dashboards/dendrite-rev2.png
Normal file
BIN
helm/dendrite/grafana_dashboards/dendrite-rev2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
|
|
@ -12,6 +12,13 @@ spec:
|
|||
matchLabels:
|
||||
{{- include "dendrite.selectorLabels" . | nindent 6 }}
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: {{ $.Values.strategy.type }}
|
||||
{{- if eq $.Values.strategy.type "RollingUpdate" }}
|
||||
rollingUpdate:
|
||||
maxSurge: {{ $.Values.strategy.rollingUpdate.maxSurge }}
|
||||
maxUnavailable: {{ $.Values.strategy.rollingUpdate.maxUnavailable }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
|
@ -40,6 +47,9 @@ spec:
|
|||
- name: {{ include "dendrite.fullname" . }}-search
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (print ( include "dendrite.fullname" . ) "-search-pvc") $.Values.persistence.search.existingClaim | quote }}
|
||||
{{- with .Values.extraVolumes }}
|
||||
{{ . | toYaml | nindent 6 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
{{- include "image.name" . | nindent 8 }}
|
||||
|
|
@ -73,6 +83,9 @@ spec:
|
|||
name: {{ include "dendrite.fullname" . }}-jetstream
|
||||
- mountPath: {{ .Values.dendrite_config.sync_api.search.index_path }}
|
||||
name: {{ include "dendrite.fullname" . }}-search
|
||||
{{- with .Values.extraVolumeMounts }}
|
||||
{{ . | toYaml | nindent 8 }}
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
|
|
|
|||
|
|
@ -43,6 +43,30 @@ persistence:
|
|||
# -- PVC Storage Request for the search volume
|
||||
capacity: "1Gi"
|
||||
|
||||
# -- Add additional volumes to the Dendrite Pod
|
||||
extraVolumes: []
|
||||
# ex.
|
||||
# - name: extra-config
|
||||
# secret:
|
||||
# secretName: extra-config
|
||||
|
||||
|
||||
# -- Configure additional mount points volumes in the Dendrite Pod
|
||||
extraVolumeMounts: []
|
||||
# ex.
|
||||
# - mountPath: /etc/dendrite/extra-config
|
||||
# name: extra-config
|
||||
|
||||
strategy:
|
||||
# -- Strategy to use for rolling updates (e.g. Recreate, RollingUpdate)
|
||||
# If you are using ReadWriteOnce volumes, you should probably use Recreate
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
# -- Maximum number of pods that can be unavailable during the update process
|
||||
maxUnavailable: 25%
|
||||
# -- Maximum number of pods that can be scheduled above the desired number of pods
|
||||
maxSurge: 25%
|
||||
|
||||
dendrite_config:
|
||||
version: 2
|
||||
global:
|
||||
|
|
|
|||
|
|
@ -15,16 +15,11 @@
|
|||
package eventutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/matrix-org/dendrite/syncapi/types"
|
||||
)
|
||||
|
||||
// ErrProfileNoExists is returned when trying to lookup a user's profile that
|
||||
// doesn't exist locally.
|
||||
var ErrProfileNoExists = errors.New("no known profile for given user ID")
|
||||
|
||||
// AccountData represents account data sent from the client API server to the
|
||||
// sync API server
|
||||
type AccountData struct {
|
||||
|
|
@ -56,20 +51,10 @@ type NotificationData struct {
|
|||
UnreadNotificationCount int `json:"unread_notification_count"`
|
||||
}
|
||||
|
||||
// ProfileResponse is a struct containing all known user profile data
|
||||
type ProfileResponse struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
DisplayName string `json:"displayname"`
|
||||
}
|
||||
|
||||
// AvatarURL is a struct containing only the URL to a user's avatar
|
||||
type AvatarURL struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
// DisplayName is a struct containing only a user's display name
|
||||
type DisplayName struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
// UserProfile is a struct containing all known user profile data
|
||||
type UserProfile struct {
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
DisplayName string `json:"displayname,omitempty"`
|
||||
}
|
||||
|
||||
// WeakBoolean is a type that will Unmarshal to true or false even if the encoded
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package test
|
|||
import (
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ var (
|
|||
|
||||
type User struct {
|
||||
ID string
|
||||
Localpart string
|
||||
AccountType api.AccountType
|
||||
// key ID and private key of the server who has this user, if known.
|
||||
keyID gomatrixserverlib.KeyID
|
||||
|
|
@ -81,6 +83,7 @@ func NewUser(t *testing.T, opts ...UserOpt) *User {
|
|||
WithSigningServer(serverName, keyID, privateKey)(&u)
|
||||
}
|
||||
u.ID = fmt.Sprintf("@%d:%s", counter, u.srvName)
|
||||
u.Localpart = strconv.Itoa(int(counter))
|
||||
t.Logf("NewUser: created user %s", u.ID)
|
||||
return &u
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ type MediaUserAPI interface {
|
|||
type FederationUserAPI interface {
|
||||
UploadDeviceKeysAPI
|
||||
QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error
|
||||
QueryProfile(ctx context.Context, req *QueryProfileRequest, res *QueryProfileResponse) error
|
||||
QueryProfile(ctx context.Context, userID string) (*authtypes.Profile, error)
|
||||
QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error
|
||||
QueryKeys(ctx context.Context, req *QueryKeysRequest, res *QueryKeysResponse) error
|
||||
QuerySignatures(ctx context.Context, req *QuerySignaturesRequest, res *QuerySignaturesResponse) error
|
||||
|
|
@ -83,9 +83,9 @@ type ClientUserAPI interface {
|
|||
LoginTokenInternalAPI
|
||||
UserLoginAPI
|
||||
ClientKeyAPI
|
||||
ProfileAPI
|
||||
QueryNumericLocalpart(ctx context.Context, req *QueryNumericLocalpartRequest, res *QueryNumericLocalpartResponse) error
|
||||
QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error
|
||||
QueryProfile(ctx context.Context, req *QueryProfileRequest, res *QueryProfileResponse) error
|
||||
QueryAccountData(ctx context.Context, req *QueryAccountDataRequest, res *QueryAccountDataResponse) error
|
||||
QueryPushers(ctx context.Context, req *QueryPushersRequest, res *QueryPushersResponse) error
|
||||
QueryPushRules(ctx context.Context, req *QueryPushRulesRequest, res *QueryPushRulesResponse) error
|
||||
|
|
@ -100,8 +100,6 @@ type ClientUserAPI interface {
|
|||
PerformPushRulesPut(ctx context.Context, req *PerformPushRulesPutRequest, res *struct{}) error
|
||||
PerformAccountDeactivation(ctx context.Context, req *PerformAccountDeactivationRequest, res *PerformAccountDeactivationResponse) error
|
||||
PerformOpenIDTokenCreation(ctx context.Context, req *PerformOpenIDTokenCreationRequest, res *PerformOpenIDTokenCreationResponse) error
|
||||
SetAvatarURL(ctx context.Context, req *PerformSetAvatarURLRequest, res *PerformSetAvatarURLResponse) error
|
||||
SetDisplayName(ctx context.Context, req *PerformUpdateDisplayNameRequest, res *PerformUpdateDisplayNameResponse) 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
|
||||
|
|
@ -113,6 +111,12 @@ type ClientUserAPI interface {
|
|||
PerformSaveThreePIDAssociation(ctx context.Context, req *PerformSaveThreePIDAssociationRequest, res *struct{}) error
|
||||
}
|
||||
|
||||
type ProfileAPI interface {
|
||||
QueryProfile(ctx context.Context, userID string) (*authtypes.Profile, error)
|
||||
SetAvatarURL(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName, avatarURL string) (*authtypes.Profile, bool, error)
|
||||
SetDisplayName(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName, displayName string) (*authtypes.Profile, bool, error)
|
||||
}
|
||||
|
||||
// custom api functions required by pinecone / p2p demos
|
||||
type QuerySearchProfilesAPI interface {
|
||||
QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error
|
||||
|
|
@ -224,7 +228,6 @@ type PerformDeviceUpdateRequest struct {
|
|||
}
|
||||
type PerformDeviceUpdateResponse struct {
|
||||
DeviceExists bool
|
||||
Forbidden bool
|
||||
}
|
||||
|
||||
type PerformDeviceDeletionRequest struct {
|
||||
|
|
@ -291,22 +294,6 @@ type QueryDevicesResponse struct {
|
|||
Devices []Device
|
||||
}
|
||||
|
||||
// QueryProfileRequest is the request for QueryProfile
|
||||
type QueryProfileRequest struct {
|
||||
// The user ID to query
|
||||
UserID string
|
||||
}
|
||||
|
||||
// QueryProfileResponse is the response for QueryProfile
|
||||
type QueryProfileResponse struct {
|
||||
// True if the user exists. Querying for a profile does not create them.
|
||||
UserExists bool
|
||||
// The current display name if set.
|
||||
DisplayName string
|
||||
// The current avatar URL if set.
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
// QuerySearchProfilesRequest is the request for QueryProfile
|
||||
type QuerySearchProfilesRequest struct {
|
||||
// The search string to match
|
||||
|
|
@ -601,16 +588,6 @@ type Notification struct {
|
|||
TS gomatrixserverlib.Timestamp `json:"ts"` // Required.
|
||||
}
|
||||
|
||||
type PerformSetAvatarURLRequest struct {
|
||||
Localpart string
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
AvatarURL string
|
||||
}
|
||||
type PerformSetAvatarURLResponse struct {
|
||||
Profile *authtypes.Profile `json:"profile"`
|
||||
Changed bool `json:"changed"`
|
||||
}
|
||||
|
||||
type QueryNumericLocalpartRequest struct {
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
}
|
||||
|
|
@ -639,17 +616,6 @@ type QueryAccountByPasswordResponse struct {
|
|||
Exists bool
|
||||
}
|
||||
|
||||
type PerformUpdateDisplayNameRequest struct {
|
||||
Localpart string
|
||||
ServerName gomatrixserverlib.ServerName
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
type PerformUpdateDisplayNameResponse struct {
|
||||
Profile *authtypes.Profile `json:"profile"`
|
||||
Changed bool `json:"changed"`
|
||||
}
|
||||
|
||||
type QueryLocalpartForThreePIDRequest struct {
|
||||
ThreePID, Medium string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
fedsenderapi "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
|
@ -386,11 +388,6 @@ func (a *UserInternalAPI) PerformDeviceUpdate(ctx context.Context, req *api.Perf
|
|||
}
|
||||
res.DeviceExists = true
|
||||
|
||||
if dev.UserID != req.RequestingUserID {
|
||||
res.Forbidden = true
|
||||
return nil
|
||||
}
|
||||
|
||||
err = a.DB.UpdateDevice(ctx, localpart, domain, req.DeviceID, req.DisplayName)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("deviceDB.UpdateDevice failed")
|
||||
|
|
@ -423,25 +420,26 @@ func (a *UserInternalAPI) PerformDeviceUpdate(ctx context.Context, req *api.Perf
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *UserInternalAPI) QueryProfile(ctx context.Context, req *api.QueryProfileRequest, res *api.QueryProfileResponse) error {
|
||||
local, domain, err := gomatrixserverlib.SplitID('@', req.UserID)
|
||||
var (
|
||||
ErrIsRemoteServer = errors.New("cannot query profile of remote users")
|
||||
)
|
||||
|
||||
func (a *UserInternalAPI) QueryProfile(ctx context.Context, userID string) (*authtypes.Profile, error) {
|
||||
local, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if !a.Config.Matrix.IsLocalServerName(domain) {
|
||||
return fmt.Errorf("cannot query profile of remote users (server name %s)", domain)
|
||||
return nil, ErrIsRemoteServer
|
||||
}
|
||||
prof, err := a.DB.GetProfileByLocalpart(ctx, local, domain)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil
|
||||
return nil, appserviceAPI.ErrProfileNotExists
|
||||
}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
res.UserExists = true
|
||||
res.AvatarURL = prof.AvatarURL
|
||||
res.DisplayName = prof.DisplayName
|
||||
return nil
|
||||
return prof, nil
|
||||
}
|
||||
|
||||
func (a *UserInternalAPI) QuerySearchProfiles(ctx context.Context, req *api.QuerySearchProfilesRequest, res *api.QuerySearchProfilesResponse) error {
|
||||
|
|
@ -906,11 +904,8 @@ func (a *UserInternalAPI) QueryPushRules(ctx context.Context, req *api.QueryPush
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *UserInternalAPI) SetAvatarURL(ctx context.Context, req *api.PerformSetAvatarURLRequest, res *api.PerformSetAvatarURLResponse) error {
|
||||
profile, changed, err := a.DB.SetAvatarURL(ctx, req.Localpart, req.ServerName, req.AvatarURL)
|
||||
res.Profile = profile
|
||||
res.Changed = changed
|
||||
return err
|
||||
func (a *UserInternalAPI) SetAvatarURL(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName, avatarURL string) (*authtypes.Profile, bool, error) {
|
||||
return a.DB.SetAvatarURL(ctx, localpart, serverName, avatarURL)
|
||||
}
|
||||
|
||||
func (a *UserInternalAPI) QueryNumericLocalpart(ctx context.Context, req *api.QueryNumericLocalpartRequest, res *api.QueryNumericLocalpartResponse) error {
|
||||
|
|
@ -944,11 +939,8 @@ func (a *UserInternalAPI) QueryAccountByPassword(ctx context.Context, req *api.Q
|
|||
}
|
||||
}
|
||||
|
||||
func (a *UserInternalAPI) SetDisplayName(ctx context.Context, req *api.PerformUpdateDisplayNameRequest, res *api.PerformUpdateDisplayNameResponse) error {
|
||||
profile, changed, err := a.DB.SetDisplayName(ctx, req.Localpart, req.ServerName, req.DisplayName)
|
||||
res.Profile = profile
|
||||
res.Changed = changed
|
||||
return err
|
||||
func (a *UserInternalAPI) SetDisplayName(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName, displayName string) (*authtypes.Profile, bool, error) {
|
||||
return a.DB.SetDisplayName(ctx, localpart, serverName, displayName)
|
||||
}
|
||||
|
||||
func (a *UserInternalAPI) QueryLocalpartForThreePID(ctx context.Context, req *api.QueryLocalpartForThreePIDRequest, res *api.QueryLocalpartForThreePIDResponse) error {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
api2 "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/userapi/producers"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
|
@ -111,33 +113,26 @@ func TestQueryProfile(t *testing.T) {
|
|||
aliceDisplayName := "Alice"
|
||||
|
||||
testCases := []struct {
|
||||
req api.QueryProfileRequest
|
||||
wantRes api.QueryProfileResponse
|
||||
userID string
|
||||
wantRes *authtypes.Profile
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
req: api.QueryProfileRequest{
|
||||
UserID: fmt.Sprintf("@alice:%s", serverName),
|
||||
},
|
||||
wantRes: api.QueryProfileResponse{
|
||||
UserExists: true,
|
||||
AvatarURL: aliceAvatarURL,
|
||||
userID: fmt.Sprintf("@alice:%s", serverName),
|
||||
wantRes: &authtypes.Profile{
|
||||
Localpart: "alice",
|
||||
DisplayName: aliceDisplayName,
|
||||
AvatarURL: aliceAvatarURL,
|
||||
ServerName: string(serverName),
|
||||
},
|
||||
},
|
||||
{
|
||||
req: api.QueryProfileRequest{
|
||||
UserID: fmt.Sprintf("@bob:%s", serverName),
|
||||
},
|
||||
wantRes: api.QueryProfileResponse{
|
||||
UserExists: false,
|
||||
},
|
||||
userID: fmt.Sprintf("@bob:%s", serverName),
|
||||
wantErr: api2.ErrProfileNotExists,
|
||||
},
|
||||
{
|
||||
req: api.QueryProfileRequest{
|
||||
UserID: "@alice:wrongdomain.com",
|
||||
},
|
||||
wantErr: fmt.Errorf("wrong domain"),
|
||||
userID: "@alice:wrongdomain.com",
|
||||
wantErr: api2.ErrProfileNotExists,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -147,14 +142,14 @@ func TestQueryProfile(t *testing.T) {
|
|||
mode = "HTTP"
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var gotRes api.QueryProfileResponse
|
||||
gotErr := testAPI.QueryProfile(context.TODO(), &tc.req, &gotRes)
|
||||
|
||||
profile, gotErr := testAPI.QueryProfile(context.TODO(), tc.userID)
|
||||
if tc.wantErr == nil && gotErr != nil || tc.wantErr != nil && gotErr == nil {
|
||||
t.Errorf("QueryProfile %s error, got %s want %s", mode, gotErr, tc.wantErr)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(tc.wantRes, gotRes) {
|
||||
t.Errorf("QueryProfile %s response got %+v want %+v", mode, gotRes, tc.wantRes)
|
||||
if !reflect.DeepEqual(tc.wantRes, profile) {
|
||||
t.Errorf("QueryProfile %s response got %+v want %+v", mode, profile, tc.wantRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue