mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-10 15:43:09 -06:00
Merge branch 'main' into feature/assorted-chart-fixes
This commit is contained in:
commit
4c35b65410
1
.github/workflows/helm.yml
vendored
1
.github/workflows/helm.yml
vendored
|
|
@ -38,3 +38,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
config: helm/cr.yaml
|
config: helm/cr.yaml
|
||||||
charts_dir: helm/
|
charts_dir: helm/
|
||||||
|
mark_as_latest: false
|
||||||
|
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -74,4 +74,7 @@ complement/
|
||||||
docs/_site
|
docs/_site
|
||||||
|
|
||||||
media_store/
|
media_store/
|
||||||
build
|
build
|
||||||
|
|
||||||
|
# golang workspaces
|
||||||
|
go.work*
|
||||||
10
README.md
10
README.md
|
|
@ -13,7 +13,7 @@ It intends to provide an **efficient**, **reliable** and **scalable** alternativ
|
||||||
|
|
||||||
Dendrite is **beta** software, which means:
|
Dendrite is **beta** software, which means:
|
||||||
|
|
||||||
- Dendrite is ready for early adopters. We recommend running in Monolith mode with a PostgreSQL database.
|
- Dendrite is ready for early adopters. We recommend running Dendrite with a PostgreSQL database.
|
||||||
- Dendrite has periodic releases. We intend to release new versions as we fix bugs and land significant features.
|
- Dendrite has periodic releases. We intend to release new versions as we fix bugs and land significant features.
|
||||||
- Dendrite supports database schema upgrades between releases. This means you should never lose your messages when upgrading Dendrite.
|
- Dendrite supports database schema upgrades between releases. This means you should never lose your messages when upgrading Dendrite.
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ This does not mean:
|
||||||
|
|
||||||
- Dendrite is bug-free. It has not yet been battle-tested in the real world and so will be error prone initially.
|
- Dendrite is bug-free. It has not yet been battle-tested in the real world and so will be error prone initially.
|
||||||
- Dendrite is feature-complete. There may be client or federation APIs that are not implemented.
|
- Dendrite is feature-complete. There may be client or federation APIs that are not implemented.
|
||||||
- Dendrite is ready for massive homeserver deployments. There is no sharding of microservices (although it is possible to run them on separate machines) and there is no high-availability/clustering support.
|
- Dendrite is ready for massive homeserver deployments. There is no high-availability/clustering support.
|
||||||
|
|
||||||
Currently, we expect Dendrite to function well for small (10s/100s of users) homeserver deployments as well as P2P Matrix nodes in-browser or on mobile devices.
|
Currently, we expect Dendrite to function well for small (10s/100s of users) homeserver deployments as well as P2P Matrix nodes in-browser or on mobile devices.
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ For a usable federating Dendrite deployment, you will also need:
|
||||||
Also recommended are:
|
Also recommended are:
|
||||||
|
|
||||||
- A PostgreSQL database engine, which will perform better than SQLite with many users and/or larger rooms
|
- A PostgreSQL database engine, which will perform better than SQLite with many users and/or larger rooms
|
||||||
- A reverse proxy server, such as nginx, configured [like this sample](https://github.com/matrix-org/dendrite/blob/master/docs/nginx/monolith-sample.conf)
|
- A reverse proxy server, such as nginx, configured [like this sample](https://github.com/matrix-org/dendrite/blob/main/docs/nginx/dendrite-sample.conf)
|
||||||
|
|
||||||
The [Federation Tester](https://federationtester.matrix.org) can be used to verify your deployment.
|
The [Federation Tester](https://federationtester.matrix.org) can be used to verify your deployment.
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ The following instructions are enough to get Dendrite started as a non-federatin
|
||||||
```bash
|
```bash
|
||||||
$ git clone https://github.com/matrix-org/dendrite
|
$ git clone https://github.com/matrix-org/dendrite
|
||||||
$ cd dendrite
|
$ cd dendrite
|
||||||
$ ./build.sh
|
$ go build -o bin/ ./cmd/...
|
||||||
|
|
||||||
# Generate a Matrix signing key for federation (required)
|
# Generate a Matrix signing key for federation (required)
|
||||||
$ ./bin/generate-keys --private-key matrix_key.pem
|
$ ./bin/generate-keys --private-key matrix_key.pem
|
||||||
|
|
@ -85,7 +85,7 @@ Then point your favourite Matrix client at `http://localhost:8008` or `https://l
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
We use a script called Are We Synapse Yet which checks Sytest compliance rates. Sytest is a black-box homeserver
|
We use a script called "Are We Synapse Yet" which checks Sytest compliance rates. Sytest is a black-box homeserver
|
||||||
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
||||||
updates with CI. As of January 2023, we have 100% server-server parity with Synapse, and the client-server parity is at 93% , though check
|
updates with CI. As of January 2023, we have 100% server-server parity with Synapse, and the client-server parity is at 93% , though check
|
||||||
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,9 @@ func (s *OutputRoomEventConsumer) sendEvents(
|
||||||
// Create the transaction body.
|
// Create the transaction body.
|
||||||
transaction, err := json.Marshal(
|
transaction, err := json.Marshal(
|
||||||
ApplicationServiceTransaction{
|
ApplicationServiceTransaction{
|
||||||
Events: synctypes.ToClientEvents(gomatrixserverlib.ToPDUs(events), synctypes.FormatAll),
|
Events: synctypes.ToClientEvents(gomatrixserverlib.ToPDUs(events), synctypes.FormatAll, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
|
return s.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -233,10 +235,20 @@ func (s *appserviceState) backoffAndPause(err error) error {
|
||||||
//
|
//
|
||||||
// TODO: This should be cached, see https://github.com/matrix-org/dendrite/issues/1682
|
// TODO: This should be cached, see https://github.com/matrix-org/dendrite/issues/1682
|
||||||
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event *types.HeaderedEvent, appservice *config.ApplicationService) bool {
|
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event *types.HeaderedEvent, appservice *config.ApplicationService) bool {
|
||||||
|
user := ""
|
||||||
|
validRoomID, err := spec.NewRoomID(event.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
userID, err := s.rsAPI.QueryUserIDForSender(ctx, *validRoomID, event.SenderID())
|
||||||
|
if err == nil {
|
||||||
|
user = userID.String()
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case appservice.URL == "":
|
case appservice.URL == "":
|
||||||
return false
|
return false
|
||||||
case appservice.IsInterestedInUserID(event.Sender()):
|
case appservice.IsInterestedInUserID(user):
|
||||||
return true
|
return true
|
||||||
case appservice.IsInterestedInRoomID(event.RoomID()):
|
case appservice.IsInterestedInRoomID(event.RoomID()):
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
51
build.cmd
51
build.cmd
|
|
@ -1,51 +0,0 @@
|
||||||
@echo off
|
|
||||||
|
|
||||||
:ENTRY_POINT
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
|
|
||||||
REM script base dir
|
|
||||||
set SCRIPTDIR=%~dp0
|
|
||||||
set PROJDIR=%SCRIPTDIR:~0,-1%
|
|
||||||
|
|
||||||
REM Put installed packages into ./bin
|
|
||||||
set GOBIN=%PROJDIR%\bin
|
|
||||||
|
|
||||||
set FLAGS=
|
|
||||||
|
|
||||||
REM Check if sources are under Git control
|
|
||||||
if not exist ".git" goto :CHECK_BIN
|
|
||||||
|
|
||||||
REM set BUILD=`git rev-parse --short HEAD \\ ""`
|
|
||||||
FOR /F "tokens=*" %%X IN ('git rev-parse --short HEAD') DO (
|
|
||||||
set BUILD=%%X
|
|
||||||
)
|
|
||||||
|
|
||||||
REM set BRANCH=`(git symbolic-ref --short HEAD \ tr -d \/ ) \\ ""`
|
|
||||||
FOR /F "tokens=*" %%X IN ('git symbolic-ref --short HEAD') DO (
|
|
||||||
set BRANCHRAW=%%X
|
|
||||||
set BRANCH=!BRANCHRAW:/=!
|
|
||||||
)
|
|
||||||
if "%BRANCH%" == "main" set BRANCH=
|
|
||||||
|
|
||||||
set FLAGS=-X github.com/matrix-org/dendrite/internal.branch=%BRANCH% -X github.com/matrix-org/dendrite/internal.build=%BUILD%
|
|
||||||
|
|
||||||
:CHECK_BIN
|
|
||||||
if exist "bin" goto :ALL_SET
|
|
||||||
mkdir "bin"
|
|
||||||
|
|
||||||
:ALL_SET
|
|
||||||
set CGO_ENABLED=1
|
|
||||||
for /D %%P in (cmd\*) do (
|
|
||||||
go build -trimpath -ldflags "%FLAGS%" -v -o ".\bin" ".\%%P"
|
|
||||||
)
|
|
||||||
|
|
||||||
set CGO_ENABLED=0
|
|
||||||
set GOOS=js
|
|
||||||
set GOARCH=wasm
|
|
||||||
go build -trimpath -ldflags "%FLAGS%" -o bin\main.wasm .\cmd\dendritejs-pinecone
|
|
||||||
|
|
||||||
goto :DONE
|
|
||||||
|
|
||||||
:DONE
|
|
||||||
echo Done
|
|
||||||
endlocal
|
|
||||||
24
build.sh
24
build.sh
|
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/sh -eu
|
|
||||||
|
|
||||||
# Put installed packages into ./bin
|
|
||||||
export GOBIN=$PWD/`dirname $0`/bin
|
|
||||||
|
|
||||||
if [ -d ".git" ]
|
|
||||||
then
|
|
||||||
export BUILD=`git rev-parse --short HEAD || ""`
|
|
||||||
export BRANCH=`(git symbolic-ref --short HEAD | tr -d \/ ) || ""`
|
|
||||||
if [ "$BRANCH" = main ]
|
|
||||||
then
|
|
||||||
export BRANCH=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
export FLAGS="-X github.com/matrix-org/dendrite/internal.branch=$BRANCH -X github.com/matrix-org/dendrite/internal.build=$BUILD"
|
|
||||||
else
|
|
||||||
export FLAGS=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p bin
|
|
||||||
|
|
||||||
CGO_ENABLED=1 go build -trimpath -ldflags "$FLAGS" -v -o "bin/" ./cmd/...
|
|
||||||
|
|
||||||
# CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs-pinecone
|
|
||||||
|
|
@ -6,23 +6,20 @@ They can be found on Docker Hub:
|
||||||
|
|
||||||
- [matrixdotorg/dendrite-monolith](https://hub.docker.com/r/matrixdotorg/dendrite-monolith) for monolith deployments
|
- [matrixdotorg/dendrite-monolith](https://hub.docker.com/r/matrixdotorg/dendrite-monolith) for monolith deployments
|
||||||
|
|
||||||
## Dockerfiles
|
## Dockerfile
|
||||||
|
|
||||||
The `Dockerfile` is a multistage file which can build all four Dendrite
|
The `Dockerfile` is a multistage file which can build Dendrite. From the root of the Dendrite
|
||||||
images depending on the supplied `--target`. From the root of the Dendrite
|
|
||||||
repository, run:
|
repository, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker build . --target monolith -t matrixdotorg/dendrite-monolith
|
docker build . -t matrixdotorg/dendrite-monolith
|
||||||
docker build . --target demo-pinecone -t matrixdotorg/dendrite-demo-pinecone
|
|
||||||
docker build . --target demo-yggdrasil -t matrixdotorg/dendrite-demo-yggdrasil
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compose files
|
## Compose file
|
||||||
|
|
||||||
There are two sample `docker-compose` files:
|
There is one sample `docker-compose` files:
|
||||||
|
|
||||||
- `docker-compose.monolith.yml` which runs a monolith Dendrite deployment
|
- `docker-compose.yml` which runs a Dendrite deployment with Postgres
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|
@ -55,7 +52,7 @@ Create your config based on the [`dendrite-sample.yaml`](https://github.com/matr
|
||||||
Then start the deployment:
|
Then start the deployment:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose -f docker-compose.monolith.yml up
|
docker-compose -f docker-compose.yml up
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building the images
|
## Building the images
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
version: "3.4"
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
hostname: postgres
|
|
||||||
image: postgres:14
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- ./postgres/create_db.sh:/docker-entrypoint-initdb.d/20-create_db.sh
|
|
||||||
# To persist your PostgreSQL databases outside of the Docker image,
|
|
||||||
# to prevent data loss, modify the following ./path_to path:
|
|
||||||
- ./path_to/postgresql:/var/lib/postgresql/data
|
|
||||||
environment:
|
|
||||||
POSTGRES_PASSWORD: itsasecret
|
|
||||||
POSTGRES_USER: dendrite
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U dendrite"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
|
|
||||||
monolith:
|
|
||||||
hostname: monolith
|
|
||||||
image: matrixdotorg/dendrite-monolith:latest
|
|
||||||
command: [
|
|
||||||
"--tls-cert=server.crt",
|
|
||||||
"--tls-key=server.key"
|
|
||||||
]
|
|
||||||
ports:
|
|
||||||
- 8008:8008
|
|
||||||
- 8448:8448
|
|
||||||
volumes:
|
|
||||||
- ./config:/etc/dendrite
|
|
||||||
- ./media:/var/dendrite/media
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
networks:
|
|
||||||
internal:
|
|
||||||
attachable: true
|
|
||||||
52
build/docker/docker-compose.yml
Normal file
52
build/docker/docker-compose.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
hostname: postgres
|
||||||
|
image: postgres:15-alpine
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
# This will create a docker volume to persist the database files in.
|
||||||
|
# If you prefer those files to be outside of docker, you'll need to change this.
|
||||||
|
- dendrite_postgres_data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: itsasecret
|
||||||
|
POSTGRES_USER: dendrite
|
||||||
|
POSTGRES_DATABASE: dendrite
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U dendrite"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
|
||||||
|
monolith:
|
||||||
|
hostname: monolith
|
||||||
|
image: matrixdotorg/dendrite-monolith:latest
|
||||||
|
ports:
|
||||||
|
- 8008:8008
|
||||||
|
- 8448:8448
|
||||||
|
volumes:
|
||||||
|
- ./config:/etc/dendrite
|
||||||
|
# The following volumes use docker volumes, change this
|
||||||
|
# if you prefer to have those files outside of docker.
|
||||||
|
- dendrite_media:/var/dendrite/media
|
||||||
|
- dendrite_jetstream:/var/dendrite/jetstream
|
||||||
|
- dendrite_search_index:/var/dendrite/searchindex
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal:
|
||||||
|
attachable: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
dendrite_postgres_data:
|
||||||
|
dendrite_media:
|
||||||
|
dendrite_jetstream:
|
||||||
|
dendrite_search_index:
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
for db in userapi_accounts mediaapi syncapi roomserver keyserver federationapi appservice mscs; do
|
|
||||||
createdb -U dendrite -O dendrite dendrite_$db
|
|
||||||
done
|
|
||||||
|
|
@ -22,7 +22,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMonolithStarts(t *testing.T) {
|
func TestMonolithStarts(t *testing.T) {
|
||||||
monolith := DendriteMonolith{}
|
monolith := DendriteMonolith{
|
||||||
|
StorageDirectory: t.TempDir(),
|
||||||
|
CacheDirectory: t.TempDir(),
|
||||||
|
}
|
||||||
monolith.Start()
|
monolith.Start()
|
||||||
monolith.PublicKey()
|
monolith.PublicKey()
|
||||||
monolith.Stop()
|
monolith.Stop()
|
||||||
|
|
@ -60,7 +63,10 @@ func TestMonolithSetRelayServers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
monolith := DendriteMonolith{}
|
monolith := DendriteMonolith{
|
||||||
|
StorageDirectory: t.TempDir(),
|
||||||
|
CacheDirectory: t.TempDir(),
|
||||||
|
}
|
||||||
monolith.Start()
|
monolith.Start()
|
||||||
|
|
||||||
inputRelays := tc.relays
|
inputRelays := tc.relays
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package clientapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -23,12 +24,649 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
|
capi "github.com/matrix-org/dendrite/clientapi/api"
|
||||||
"github.com/matrix-org/dendrite/test"
|
"github.com/matrix-org/dendrite/test"
|
||||||
"github.com/matrix-org/dendrite/test/testrig"
|
"github.com/matrix-org/dendrite/test/testrig"
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAdminCreateToken(t *testing.T) {
|
||||||
|
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||||
|
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||||
|
ctx := context.Background()
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
|
cfg.ClientAPI.RegistrationRequiresToken = true
|
||||||
|
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)
|
||||||
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
accessTokens := map[*test.User]userDevice{
|
||||||
|
aliceAdmin: {},
|
||||||
|
bob: {},
|
||||||
|
}
|
||||||
|
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
requestingUser *test.User
|
||||||
|
requestOpt test.HTTPRequestOpt
|
||||||
|
wantOK bool
|
||||||
|
withHeader bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Missing auth",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bob is denied access",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token2",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can create a token without specifyiing any information",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can to create a token specifying a name",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token3",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice cannot to create a token that already exists",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token3",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can create a token specifying valid params",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token4",
|
||||||
|
"uses_allowed": 5,
|
||||||
|
"expiry_time": time.Now().Add(5*24*time.Hour).UnixNano() / int64(time.Millisecond),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice cannot create a token specifying invalid name",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token@",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice cannot create a token specifying invalid uses_allowed",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token5",
|
||||||
|
"uses_allowed": -1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice cannot create a token specifying invalid expiry_time",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"token": "token6",
|
||||||
|
"expiry_time": time.Now().Add(-1*5*24*time.Hour).UnixNano() / int64(time.Millisecond),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice cannot to create a token specifying invalid length",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"length": 80,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/registrationTokens/new")
|
||||||
|
if tc.requestOpt != nil {
|
||||||
|
req = test.NewRequest(t, http.MethodPost, "/_dendrite/admin/registrationTokens/new", tc.requestOpt)
|
||||||
|
}
|
||||||
|
if tc.withHeader {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
routers.DendriteAdmin.ServeHTTP(rec, req)
|
||||||
|
t.Logf("%s", rec.Body.String())
|
||||||
|
if tc.wantOK && rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected http status %d, got %d: %s", http.StatusOK, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminListRegistrationTokens(t *testing.T) {
|
||||||
|
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||||
|
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||||
|
ctx := context.Background()
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
|
cfg.ClientAPI.RegistrationRequiresToken = true
|
||||||
|
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)
|
||||||
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
accessTokens := map[*test.User]userDevice{
|
||||||
|
aliceAdmin: {},
|
||||||
|
bob: {},
|
||||||
|
}
|
||||||
|
tokens := []capi.RegistrationToken{
|
||||||
|
{
|
||||||
|
Token: getPointer("valid"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: getPointer("invalid"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(-1*5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tkn := range tokens {
|
||||||
|
tkn := tkn
|
||||||
|
userAPI.PerformAdminCreateRegistrationToken(ctx, &tkn)
|
||||||
|
}
|
||||||
|
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
requestingUser *test.User
|
||||||
|
valid string
|
||||||
|
isValidSpecified bool
|
||||||
|
wantOK bool
|
||||||
|
withHeader bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Missing auth",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
isValidSpecified: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bob is denied access",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
isValidSpecified: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can list all tokens",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can list all valid tokens",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
valid: "true",
|
||||||
|
isValidSpecified: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can list all invalid tokens",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
valid: "false",
|
||||||
|
isValidSpecified: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No response when valid has a bad value",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
valid: "trueee",
|
||||||
|
isValidSpecified: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
if tc.isValidSpecified {
|
||||||
|
path = fmt.Sprintf("/_dendrite/admin/registrationTokens?valid=%v", tc.valid)
|
||||||
|
} else {
|
||||||
|
path = "/_dendrite/admin/registrationTokens"
|
||||||
|
}
|
||||||
|
req := test.NewRequest(t, http.MethodGet, path)
|
||||||
|
if tc.withHeader {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
routers.DendriteAdmin.ServeHTTP(rec, req)
|
||||||
|
t.Logf("%s", rec.Body.String())
|
||||||
|
if tc.wantOK && rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected http status %d, got %d: %s", http.StatusOK, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminGetRegistrationToken(t *testing.T) {
|
||||||
|
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||||
|
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||||
|
ctx := context.Background()
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
|
cfg.ClientAPI.RegistrationRequiresToken = true
|
||||||
|
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)
|
||||||
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
accessTokens := map[*test.User]userDevice{
|
||||||
|
aliceAdmin: {},
|
||||||
|
bob: {},
|
||||||
|
}
|
||||||
|
tokens := []capi.RegistrationToken{
|
||||||
|
{
|
||||||
|
Token: getPointer("alice_token1"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: getPointer("alice_token2"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(-1*5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tkn := range tokens {
|
||||||
|
tkn := tkn
|
||||||
|
userAPI.PerformAdminCreateRegistrationToken(ctx, &tkn)
|
||||||
|
}
|
||||||
|
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
requestingUser *test.User
|
||||||
|
token string
|
||||||
|
wantOK bool
|
||||||
|
withHeader bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Missing auth",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bob is denied access",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can GET alice_token1",
|
||||||
|
token: "alice_token1",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can GET alice_token2",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice cannot GET a token that does not exists",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
path := fmt.Sprintf("/_dendrite/admin/registrationTokens/%s", tc.token)
|
||||||
|
req := test.NewRequest(t, http.MethodGet, path)
|
||||||
|
if tc.withHeader {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
routers.DendriteAdmin.ServeHTTP(rec, req)
|
||||||
|
t.Logf("%s", rec.Body.String())
|
||||||
|
if tc.wantOK && rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected http status %d, got %d: %s", http.StatusOK, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminDeleteRegistrationToken(t *testing.T) {
|
||||||
|
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||||
|
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||||
|
ctx := context.Background()
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
|
cfg.ClientAPI.RegistrationRequiresToken = true
|
||||||
|
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)
|
||||||
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
accessTokens := map[*test.User]userDevice{
|
||||||
|
aliceAdmin: {},
|
||||||
|
bob: {},
|
||||||
|
}
|
||||||
|
tokens := []capi.RegistrationToken{
|
||||||
|
{
|
||||||
|
Token: getPointer("alice_token1"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: getPointer("alice_token2"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(-1*5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tkn := range tokens {
|
||||||
|
tkn := tkn
|
||||||
|
userAPI.PerformAdminCreateRegistrationToken(ctx, &tkn)
|
||||||
|
}
|
||||||
|
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
requestingUser *test.User
|
||||||
|
token string
|
||||||
|
wantOK bool
|
||||||
|
withHeader bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Missing auth",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bob is denied access",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can DELETE alice_token1",
|
||||||
|
token: "alice_token1",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can DELETE alice_token2",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
path := fmt.Sprintf("/_dendrite/admin/registrationTokens/%s", tc.token)
|
||||||
|
req := test.NewRequest(t, http.MethodDelete, path)
|
||||||
|
if tc.withHeader {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
routers.DendriteAdmin.ServeHTTP(rec, req)
|
||||||
|
t.Logf("%s", rec.Body.String())
|
||||||
|
if tc.wantOK && rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected http status %d, got %d: %s", http.StatusOK, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminUpdateRegistrationToken(t *testing.T) {
|
||||||
|
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||||
|
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||||
|
ctx := context.Background()
|
||||||
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||||
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
|
cfg.ClientAPI.RegistrationRequiresToken = true
|
||||||
|
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)
|
||||||
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
accessTokens := map[*test.User]userDevice{
|
||||||
|
aliceAdmin: {},
|
||||||
|
bob: {},
|
||||||
|
}
|
||||||
|
createAccessTokens(t, accessTokens, userAPI, ctx, routers)
|
||||||
|
tokens := []capi.RegistrationToken{
|
||||||
|
{
|
||||||
|
Token: getPointer("alice_token1"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Token: getPointer("alice_token2"),
|
||||||
|
UsesAllowed: getPointer(int32(10)),
|
||||||
|
ExpiryTime: getPointer(time.Now().Add(-1*5*24*time.Hour).UnixNano() / int64(time.Millisecond)),
|
||||||
|
Pending: getPointer(int32(0)),
|
||||||
|
Completed: getPointer(int32(0)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tkn := range tokens {
|
||||||
|
tkn := tkn
|
||||||
|
userAPI.PerformAdminCreateRegistrationToken(ctx, &tkn)
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
requestingUser *test.User
|
||||||
|
method string
|
||||||
|
token string
|
||||||
|
requestOpt test.HTTPRequestOpt
|
||||||
|
wantOK bool
|
||||||
|
withHeader bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Missing auth",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
token: "alice_token1",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": 10,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bob is denied access",
|
||||||
|
requestingUser: bob,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token1",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": 10,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can UPDATE a token's uses_allowed property",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token1",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": 10,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can UPDATE a token's expiry_time property",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: true,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token2",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"expiry_time": time.Now().Add(5*24*time.Hour).UnixNano() / int64(time.Millisecond),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can UPDATE a token's uses_allowed and expiry_time property",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token1",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": 20,
|
||||||
|
"expiry_time": time.Now().Add(10*24*time.Hour).UnixNano() / int64(time.Millisecond),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice CANNOT update a token with invalid properties",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token2",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": -5,
|
||||||
|
"expiry_time": time.Now().Add(-1*5*24*time.Hour).UnixNano() / int64(time.Millisecond),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice CANNOT UPDATE a token that does not exist",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token9",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": 100,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can UPDATE token specifying uses_allowed as null - Valid for infinite uses",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token1",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"uses_allowed": nil,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Alice can UPDATE token specifying expiry_time AS null - Valid for infinite time",
|
||||||
|
requestingUser: aliceAdmin,
|
||||||
|
wantOK: false,
|
||||||
|
withHeader: true,
|
||||||
|
token: "alice_token1",
|
||||||
|
requestOpt: test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
"expiry_time": nil,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
path := fmt.Sprintf("/_dendrite/admin/registrationTokens/%s", tc.token)
|
||||||
|
req := test.NewRequest(t, http.MethodPut, path)
|
||||||
|
if tc.requestOpt != nil {
|
||||||
|
req = test.NewRequest(t, http.MethodPut, path, tc.requestOpt)
|
||||||
|
}
|
||||||
|
if tc.withHeader {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
routers.DendriteAdmin.ServeHTTP(rec, req)
|
||||||
|
t.Logf("%s", rec.Body.String())
|
||||||
|
if tc.wantOK && rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected http status %d, got %d: %s", http.StatusOK, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPointer[T any](s T) *T {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdminResetPassword(t *testing.T) {
|
func TestAdminResetPassword(t *testing.T) {
|
||||||
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||||
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||||
|
|
@ -133,7 +771,11 @@ func TestPurgeRoom(t *testing.T) {
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
natsInstance := jetstream.NATSInstance{}
|
natsInstance := jetstream.NATSInstance{}
|
||||||
defer close()
|
defer func() {
|
||||||
|
// give components the time to process purge requests
|
||||||
|
time.Sleep(time.Millisecond * 50)
|
||||||
|
close()
|
||||||
|
}()
|
||||||
|
|
||||||
routers := httputil.NewRouters()
|
routers := httputil.NewRouters()
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,11 @@ type ExtraPublicRoomsProvider interface {
|
||||||
// Rooms returns the extra rooms. This is called on-demand by clients, so cache appropriately.
|
// Rooms returns the extra rooms. This is called on-demand by clients, so cache appropriately.
|
||||||
Rooms() []fclient.PublicRoom
|
Rooms() []fclient.PublicRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegistrationToken struct {
|
||||||
|
Token *string `json:"token"`
|
||||||
|
UsesAllowed *int32 `json:"uses_allowed"`
|
||||||
|
Pending *int32 `json:"pending"`
|
||||||
|
Completed *int32 `json:"completed"`
|
||||||
|
ExpiryTime *int64 `json:"expiry_time"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ func VerifyUserFromRequest(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.MissingToken(err.Error()),
|
JSON: spec.MissingToken(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var res api.QueryAccessTokenResponse
|
var res api.QueryAccessTokenResponse
|
||||||
|
|
@ -68,21 +68,23 @@ func VerifyUserFromRequest(
|
||||||
}, &res)
|
}, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryAccessToken failed")
|
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryAccessToken failed")
|
||||||
jsonErr := jsonerror.InternalServerError()
|
return nil, &util.JSONResponse{
|
||||||
return nil, &jsonErr
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if res.Err != "" {
|
if res.Err != "" {
|
||||||
if strings.HasPrefix(strings.ToLower(res.Err), "forbidden:") { // TODO: use actual error and no string comparison
|
if strings.HasPrefix(strings.ToLower(res.Err), "forbidden:") { // TODO: use actual error and no string comparison
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(res.Err),
|
JSON: spec.Forbidden(res.Err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if res.Device == nil {
|
if res.Device == nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.UnknownToken("Unknown token"),
|
JSON: spec.UnknownToken("Unknown token"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.Device, nil
|
return res.Device, nil
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.U
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := &util.JSONResponse{
|
err := &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Reading request body failed: " + err.Error()),
|
JSON: spec.BadJSON("Reading request body failed: " + err.Error()),
|
||||||
}
|
}
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.U
|
||||||
if err := json.Unmarshal(reqBytes, &header); err != nil {
|
if err := json.Unmarshal(reqBytes, &header); err != nil {
|
||||||
err := &util.JSONResponse{
|
err := &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Reading request body failed: " + err.Error()),
|
JSON: spec.BadJSON("Reading request body failed: " + err.Error()),
|
||||||
}
|
}
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.U
|
||||||
default:
|
default:
|
||||||
err := util.JSONResponse{
|
err := util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("unhandled login type: " + header.Type),
|
JSON: spec.InvalidParam("unhandled login type: " + header.Type),
|
||||||
}
|
}
|
||||||
return nil, nil, &err
|
return nil, nil, &err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -107,13 +107,13 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
Name string
|
Name string
|
||||||
Body string
|
Body string
|
||||||
|
|
||||||
WantErrCode string
|
WantErrCode spec.MatrixErrorCode
|
||||||
}{
|
}{
|
||||||
{Name: "empty", WantErrCode: "M_BAD_JSON"},
|
{Name: "empty", WantErrCode: spec.ErrorBadJSON},
|
||||||
{
|
{
|
||||||
Name: "badUnmarshal",
|
Name: "badUnmarshal",
|
||||||
Body: `badsyntaxJSON`,
|
Body: `badsyntaxJSON`,
|
||||||
WantErrCode: "M_BAD_JSON",
|
WantErrCode: spec.ErrorBadJSON,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "badPassword",
|
Name: "badPassword",
|
||||||
|
|
@ -123,7 +123,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
"password": "invalidpassword",
|
"password": "invalidpassword",
|
||||||
"device_id": "adevice"
|
"device_id": "adevice"
|
||||||
}`,
|
}`,
|
||||||
WantErrCode: "M_FORBIDDEN",
|
WantErrCode: spec.ErrorForbidden,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "badToken",
|
Name: "badToken",
|
||||||
|
|
@ -132,7 +132,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
"token": "invalidtoken",
|
"token": "invalidtoken",
|
||||||
"device_id": "adevice"
|
"device_id": "adevice"
|
||||||
}`,
|
}`,
|
||||||
WantErrCode: "M_FORBIDDEN",
|
WantErrCode: spec.ErrorForbidden,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "badType",
|
Name: "badType",
|
||||||
|
|
@ -140,7 +140,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
"type": "m.login.invalid",
|
"type": "m.login.invalid",
|
||||||
"device_id": "adevice"
|
"device_id": "adevice"
|
||||||
}`,
|
}`,
|
||||||
WantErrCode: "M_INVALID_ARGUMENT_VALUE",
|
WantErrCode: spec.ErrorInvalidParam,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tst := range tsts {
|
for _, tst := range tsts {
|
||||||
|
|
@ -157,7 +157,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
if errRes == nil {
|
if errRes == nil {
|
||||||
cleanup(ctx, nil)
|
cleanup(ctx, nil)
|
||||||
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
|
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
|
||||||
} else if merr, ok := errRes.JSON.(*jsonerror.MatrixError); ok && merr.ErrCode != tst.WantErrCode {
|
} else if merr, ok := errRes.JSON.(spec.MatrixError); ok && merr.ErrCode != tst.WantErrCode {
|
||||||
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
|
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -48,13 +48,15 @@ func (t *LoginTypeToken) LoginFromJSON(ctx context.Context, reqBytes []byte) (*L
|
||||||
var res uapi.QueryLoginTokenResponse
|
var res uapi.QueryLoginTokenResponse
|
||||||
if err := t.UserAPI.QueryLoginToken(ctx, &uapi.QueryLoginTokenRequest{Token: r.Token}, &res); err != nil {
|
if err := t.UserAPI.QueryLoginToken(ctx, &uapi.QueryLoginTokenRequest{Token: r.Token}, &res); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("UserAPI.QueryLoginToken failed")
|
util.GetLogger(ctx).WithError(err).Error("UserAPI.QueryLoginToken failed")
|
||||||
jsonErr := jsonerror.InternalServerError()
|
return nil, nil, &util.JSONResponse{
|
||||||
return nil, nil, &jsonErr
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if res.Data == nil {
|
if res.Data == nil {
|
||||||
return nil, nil, &util.JSONResponse{
|
return nil, nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("invalid login token"),
|
JSON: spec.Forbidden("invalid login token"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -65,26 +65,26 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.BadJSON("A username must be supplied."),
|
JSON: spec.BadJSON("A username must be supplied."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r.Password) == 0 {
|
if len(r.Password) == 0 {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.BadJSON("A password must be supplied."),
|
JSON: spec.BadJSON("A password must be supplied."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localpart, domain, err := userutil.ParseUsernameParam(username, t.Config.Matrix)
|
localpart, domain, err := userutil.ParseUsernameParam(username, t.Config.Matrix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.InvalidUsername(err.Error()),
|
JSON: spec.InvalidUsername(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !t.Config.Matrix.IsLocalServerName(domain) {
|
if !t.Config.Matrix.IsLocalServerName(domain) {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.InvalidUsername("The server name is not known."),
|
JSON: spec.InvalidUsername("The server name is not known."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Squash username to all lowercase letters
|
// Squash username to all lowercase letters
|
||||||
|
|
@ -97,7 +97,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("Unable to fetch account by password."),
|
JSON: spec.Unknown("Unable to fetch account by password."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("Unable to fetch account by password."),
|
JSON: spec.Unknown("Unable to fetch account by password."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows
|
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows
|
||||||
|
|
@ -120,7 +120,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
if !res.Exists {
|
if !res.Exists {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("The username or password was incorrect or the account does not exist."),
|
JSON: spec.Forbidden("The username or password was incorrect or the account does not exist."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
@ -178,8 +178,10 @@ func (u *UserInteractive) NewSession() *util.JSONResponse {
|
||||||
sessionID, err := GenerateAccessToken()
|
sessionID, err := GenerateAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Error("failed to generate session ID")
|
logrus.WithError(err).Error("failed to generate session ID")
|
||||||
res := jsonerror.InternalServerError()
|
return &util.JSONResponse{
|
||||||
return &res
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
u.Lock()
|
u.Lock()
|
||||||
u.Sessions[sessionID] = []string{}
|
u.Sessions[sessionID] = []string{}
|
||||||
|
|
@ -193,15 +195,19 @@ func (u *UserInteractive) ResponseWithChallenge(sessionID string, response inter
|
||||||
mixedObjects := make(map[string]interface{})
|
mixedObjects := make(map[string]interface{})
|
||||||
b, err := json.Marshal(response)
|
b, err := json.Marshal(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ise := jsonerror.InternalServerError()
|
return &util.JSONResponse{
|
||||||
return &ise
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ = json.Unmarshal(b, &mixedObjects)
|
_ = json.Unmarshal(b, &mixedObjects)
|
||||||
challenge := u.challenge(sessionID)
|
challenge := u.challenge(sessionID)
|
||||||
b, err = json.Marshal(challenge.JSON)
|
b, err = json.Marshal(challenge.JSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ise := jsonerror.InternalServerError()
|
return &util.JSONResponse{
|
||||||
return &ise
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ = json.Unmarshal(b, &mixedObjects)
|
_ = json.Unmarshal(b, &mixedObjects)
|
||||||
|
|
||||||
|
|
@ -234,7 +240,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Unknown auth.type: " + authType),
|
JSON: spec.BadJSON("Unknown auth.type: " + authType),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,7 +256,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *
|
||||||
if !u.IsSingleStageFlow(authType) {
|
if !u.IsSingleStageFlow(authType) {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown("The auth.session is missing or unknown."),
|
JSON: spec.Unknown("The auth.session is missing or unknown."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import (
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
@ -1105,7 +1106,7 @@ func Test3PID(t *testing.T) {
|
||||||
resp := threepid.GetValidatedResponse{}
|
resp := threepid.GetValidatedResponse{}
|
||||||
switch r.URL.Query().Get("client_secret") {
|
switch r.URL.Query().Get("client_secret") {
|
||||||
case "fail":
|
case "fail":
|
||||||
resp.ErrCode = "M_SESSION_NOT_VALIDATED"
|
resp.ErrCode = string(spec.ErrorSessionNotValidated)
|
||||||
case "fail2":
|
case "fail2":
|
||||||
resp.ErrCode = "some other error"
|
resp.ErrCode = "some other error"
|
||||||
case "fail3":
|
case "fail3":
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -32,8 +32,10 @@ func UnmarshalJSONRequest(req *http.Request, iface interface{}) *util.JSONRespon
|
||||||
body, err := io.ReadAll(req.Body)
|
body, err := io.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("io.ReadAll failed")
|
util.GetLogger(req.Context()).WithError(err).Error("io.ReadAll failed")
|
||||||
resp := jsonerror.InternalServerError()
|
return &util.JSONResponse{
|
||||||
return &resp
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return UnmarshalJSON(body, iface)
|
return UnmarshalJSON(body, iface)
|
||||||
|
|
@ -43,7 +45,7 @@ func UnmarshalJSON(body []byte, iface interface{}) *util.JSONResponse {
|
||||||
if !utf8.Valid(body) {
|
if !utf8.Valid(body) {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.NotJSON("Body contains invalid UTF-8"),
|
JSON: spec.NotJSON("Body contains invalid UTF-8"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +55,7 @@ func UnmarshalJSON(body []byte, iface interface{}) *util.JSONResponse {
|
||||||
// valid JSON with incorrect types for values.
|
// valid JSON with incorrect types for values.
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
JSON: spec.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -1,246 +0,0 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package jsonerror
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MatrixError represents the "standard error response" in Matrix.
|
|
||||||
// http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
|
|
||||||
type MatrixError struct {
|
|
||||||
ErrCode string `json:"errcode"`
|
|
||||||
Err string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e MatrixError) Error() string {
|
|
||||||
return fmt.Sprintf("%s: %s", e.ErrCode, e.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalServerError returns a 500 Internal Server Error in a matrix-compliant
|
|
||||||
// format.
|
|
||||||
func InternalServerError() util.JSONResponse {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: Unknown("Internal Server Error"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown is an unexpected error
|
|
||||||
func Unknown(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_UNKNOWN", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forbidden is an error when the client tries to access a resource
|
|
||||||
// they are not allowed to access.
|
|
||||||
func Forbidden(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_FORBIDDEN", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BadJSON is an error when the client supplies malformed JSON.
|
|
||||||
func BadJSON(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_BAD_JSON", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BadAlias is an error when the client supplies a bad alias.
|
|
||||||
func BadAlias(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_BAD_ALIAS", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotJSON is an error when the client supplies something that is not JSON
|
|
||||||
// to a JSON endpoint.
|
|
||||||
func NotJSON(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_NOT_JSON", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotFound is an error when the client tries to access an unknown resource.
|
|
||||||
func NotFound(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_NOT_FOUND", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MissingArgument is an error when the client tries to access a resource
|
|
||||||
// without providing an argument that is required.
|
|
||||||
func MissingArgument(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_MISSING_ARGUMENT", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidArgumentValue is an error when the client tries to provide an
|
|
||||||
// invalid value for a valid argument
|
|
||||||
func InvalidArgumentValue(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_INVALID_ARGUMENT_VALUE", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MissingToken is an error when the client tries to access a resource which
|
|
||||||
// requires authentication without supplying credentials.
|
|
||||||
func MissingToken(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_MISSING_TOKEN", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnknownToken is an error when the client tries to access a resource which
|
|
||||||
// requires authentication and supplies an unrecognised token
|
|
||||||
func UnknownToken(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_UNKNOWN_TOKEN", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WeakPassword is an error which is returned when the client tries to register
|
|
||||||
// using a weak password. http://matrix.org/docs/spec/client_server/r0.2.0.html#password-based
|
|
||||||
func WeakPassword(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_WEAK_PASSWORD", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidUsername is an error returned when the client tries to register an
|
|
||||||
// invalid username
|
|
||||||
func InvalidUsername(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_INVALID_USERNAME", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserInUse is an error returned when the client tries to register an
|
|
||||||
// username that already exists
|
|
||||||
func UserInUse(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_USER_IN_USE", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoomInUse is an error returned when the client tries to make a room
|
|
||||||
// that already exists
|
|
||||||
func RoomInUse(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_ROOM_IN_USE", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASExclusive is an error returned when an application service tries to
|
|
||||||
// register an username that is outside of its registered namespace, or if a
|
|
||||||
// user attempts to register a username or room alias within an exclusive
|
|
||||||
// namespace.
|
|
||||||
func ASExclusive(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_EXCLUSIVE", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GuestAccessForbidden is an error which is returned when the client is
|
|
||||||
// forbidden from accessing a resource as a guest.
|
|
||||||
func GuestAccessForbidden(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_GUEST_ACCESS_FORBIDDEN", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidSignature is an error which is returned when the client tries
|
|
||||||
// to upload invalid signatures.
|
|
||||||
func InvalidSignature(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_INVALID_SIGNATURE", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidParam is an error that is returned when a parameter was invalid,
|
|
||||||
// traditionally with cross-signing.
|
|
||||||
func InvalidParam(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_INVALID_PARAM", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MissingParam is an error that is returned when a parameter was incorrect,
|
|
||||||
// traditionally with cross-signing.
|
|
||||||
func MissingParam(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_MISSING_PARAM", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnableToAuthoriseJoin is an error that is returned when a server can't
|
|
||||||
// determine whether to allow a restricted join or not.
|
|
||||||
func UnableToAuthoriseJoin(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_UNABLE_TO_AUTHORISE_JOIN", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeaveServerNoticeError is an error returned when trying to reject an invite
|
|
||||||
// for a server notice room.
|
|
||||||
func LeaveServerNoticeError() *MatrixError {
|
|
||||||
return &MatrixError{
|
|
||||||
ErrCode: "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM",
|
|
||||||
Err: "You cannot reject this invite",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrRoomKeysVersion is an error returned by `PUT /room_keys/keys`
|
|
||||||
type ErrRoomKeysVersion struct {
|
|
||||||
MatrixError
|
|
||||||
CurrentVersion string `json:"current_version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrongBackupVersionError is an error returned by `PUT /room_keys/keys`
|
|
||||||
func WrongBackupVersionError(currentVersion string) *ErrRoomKeysVersion {
|
|
||||||
return &ErrRoomKeysVersion{
|
|
||||||
MatrixError: MatrixError{
|
|
||||||
ErrCode: "M_WRONG_ROOM_KEYS_VERSION",
|
|
||||||
Err: "Wrong backup version.",
|
|
||||||
},
|
|
||||||
CurrentVersion: currentVersion,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type IncompatibleRoomVersionError struct {
|
|
||||||
RoomVersion string `json:"room_version"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
Code string `json:"errcode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncompatibleRoomVersion is an error which is returned when the client
|
|
||||||
// requests a room with a version that is unsupported.
|
|
||||||
func IncompatibleRoomVersion(roomVersion gomatrixserverlib.RoomVersion) *IncompatibleRoomVersionError {
|
|
||||||
return &IncompatibleRoomVersionError{
|
|
||||||
Code: "M_INCOMPATIBLE_ROOM_VERSION",
|
|
||||||
RoomVersion: string(roomVersion),
|
|
||||||
Error: "Your homeserver does not support the features required to join this room",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsupportedRoomVersion is an error which is returned when the client
|
|
||||||
// requests a room with a version that is unsupported.
|
|
||||||
func UnsupportedRoomVersion(msg string) *MatrixError {
|
|
||||||
return &MatrixError{"M_UNSUPPORTED_ROOM_VERSION", msg}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LimitExceededError is a rate-limiting error.
|
|
||||||
type LimitExceededError struct {
|
|
||||||
MatrixError
|
|
||||||
RetryAfterMS int64 `json:"retry_after_ms,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LimitExceeded is an error when the client tries to send events too quickly.
|
|
||||||
func LimitExceeded(msg string, retryAfterMS int64) *LimitExceededError {
|
|
||||||
return &LimitExceededError{
|
|
||||||
MatrixError: MatrixError{"M_LIMIT_EXCEEDED", msg},
|
|
||||||
RetryAfterMS: retryAfterMS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotTrusted is an error which is returned when the client asks the server to
|
|
||||||
// proxy a request (e.g. 3PID association) to a server that isn't trusted
|
|
||||||
func NotTrusted(serverName string) *MatrixError {
|
|
||||||
return &MatrixError{
|
|
||||||
ErrCode: "M_SERVER_NOT_TRUSTED",
|
|
||||||
Err: fmt.Sprintf("Untrusted server '%s'", serverName),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalAPIError is returned when Dendrite failed to reach an internal API.
|
|
||||||
func InternalAPIError(ctx context.Context, err error) util.JSONResponse {
|
|
||||||
logrus.WithContext(ctx).WithError(err).Error("Error reaching an internal API")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: &MatrixError{
|
|
||||||
ErrCode: "M_INTERNAL_SERVER_ERROR",
|
|
||||||
Err: "Dendrite encountered an error reaching an internal API.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package jsonerror
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLimitExceeded(t *testing.T) {
|
|
||||||
e := LimitExceeded("too fast", 5000)
|
|
||||||
jsonBytes, err := json.Marshal(&e)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("TestLimitExceeded: Failed to marshal LimitExceeded error. %s", err.Error())
|
|
||||||
}
|
|
||||||
want := `{"errcode":"M_LIMIT_EXCEEDED","error":"too fast","retry_after_ms":5000}`
|
|
||||||
if string(jsonBytes) != want {
|
|
||||||
t.Errorf("TestLimitExceeded: want %s, got %s", want, string(jsonBytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForbidden(t *testing.T) {
|
|
||||||
e := Forbidden("you shall not pass")
|
|
||||||
jsonBytes, err := json.Marshal(&e)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("TestForbidden: Failed to marshal Forbidden error. %s", err.Error())
|
|
||||||
}
|
|
||||||
want := `{"errcode":"M_FORBIDDEN","error":"you shall not pass"}`
|
|
||||||
if string(jsonBytes) != want {
|
|
||||||
t.Errorf("TestForbidden: want %s, got %s", want, string(jsonBytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,11 +21,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -38,7 +38,7 @@ func GetAccountData(
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
JSON: spec.Forbidden("userID does not match the current user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ func GetAccountData(
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("data not found"),
|
JSON: spec.NotFound("data not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ func SaveAccountData(
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
JSON: spec.Forbidden("userID does not match the current user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,27 +90,30 @@ func SaveAccountData(
|
||||||
if req.Body == http.NoBody {
|
if req.Body == http.NoBody {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.NotJSON("Content not JSON"),
|
JSON: spec.NotJSON("Content not JSON"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dataType == "m.fully_read" || dataType == "m.push_rules" {
|
if dataType == "m.fully_read" || dataType == "m.push_rules" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(fmt.Sprintf("Unable to modify %q using this API", dataType)),
|
JSON: spec.Forbidden(fmt.Sprintf("Unable to modify %q using this API", dataType)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(req.Body)
|
body, err := io.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("io.ReadAll failed")
|
util.GetLogger(req.Context()).WithError(err).Error("io.ReadAll failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !json.Valid(body) {
|
if !json.Valid(body) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Bad JSON content"),
|
JSON: spec.BadJSON("Bad JSON content"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,8 +145,16 @@ func SaveReadMarker(
|
||||||
userAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
userAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
syncProducer *producers.SyncAPIProducer, device *api.Device, roomID string,
|
syncProducer *producers.SyncAPIProducer, device *api.Device, roomID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("userID for this device is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the user is a member of this room
|
// Verify that the user is a member of this room
|
||||||
resErr := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +168,10 @@ func SaveReadMarker(
|
||||||
if r.FullyRead != "" {
|
if r.FullyRead != "" {
|
||||||
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
|
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dataReq := api.InputAccountDataRequest{
|
dataReq := api.InputAccountDataRequest{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
@ -16,15 +18,254 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
clientapi "github.com/matrix-org/dendrite/clientapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var validRegistrationTokenRegex = regexp.MustCompile("^[[:ascii:][:digit:]_]*$")
|
||||||
|
|
||||||
|
func AdminCreateNewRegistrationToken(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
||||||
|
if !cfg.RegistrationRequiresToken {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("Registration via tokens is not enabled on this homeserver"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request := struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
UsesAllowed *int32 `json:"uses_allowed,omitempty"`
|
||||||
|
ExpiryTime *int64 `json:"expiry_time,omitempty"`
|
||||||
|
Length int32 `json:"length"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON(fmt.Sprintf("Failed to decode request body: %s", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token := request.Token
|
||||||
|
usesAllowed := request.UsesAllowed
|
||||||
|
expiryTime := request.ExpiryTime
|
||||||
|
length := request.Length
|
||||||
|
|
||||||
|
if len(token) == 0 {
|
||||||
|
if length == 0 {
|
||||||
|
// length not provided in request. Assign default value of 16.
|
||||||
|
length = 16
|
||||||
|
}
|
||||||
|
// token not present in request body. Hence, generate a random token.
|
||||||
|
if length <= 0 || length > 64 {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("length must be greater than zero and not greater than 64"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token = util.RandomString(int(length))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(token) > 64 {
|
||||||
|
//Token present in request body, but is too long.
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("token must not be longer than 64"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isTokenValid := validRegistrationTokenRegex.Match([]byte(token))
|
||||||
|
if !isTokenValid {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("token must consist only of characters matched by the regex [A-Za-z0-9-_]"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// At this point, we have a valid token, either through request body or through random generation.
|
||||||
|
if usesAllowed != nil && *usesAllowed < 0 {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("uses_allowed must be a non-negative integer or null"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expiryTime != nil && spec.Timestamp(*expiryTime).Time().Before(time.Now()) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("expiry_time must not be in the past"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pending := int32(0)
|
||||||
|
completed := int32(0)
|
||||||
|
// If usesAllowed or expiryTime is 0, it means they are not present in the request. NULL (indicating unlimited uses / no expiration will be persisted in DB)
|
||||||
|
registrationToken := &clientapi.RegistrationToken{
|
||||||
|
Token: &token,
|
||||||
|
UsesAllowed: usesAllowed,
|
||||||
|
Pending: &pending,
|
||||||
|
Completed: &completed,
|
||||||
|
ExpiryTime: expiryTime,
|
||||||
|
}
|
||||||
|
created, err := userAPI.PerformAdminCreateRegistrationToken(req.Context(), registrationToken)
|
||||||
|
if !created {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusConflict,
|
||||||
|
JSON: map[string]string{
|
||||||
|
"error": fmt.Sprintf("token: %s already exists", token),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: map[string]interface{}{
|
||||||
|
"token": token,
|
||||||
|
"uses_allowed": getReturnValue(usesAllowed),
|
||||||
|
"pending": pending,
|
||||||
|
"completed": completed,
|
||||||
|
"expiry_time": getReturnValue(expiryTime),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReturnValue[t constraints.Integer](in *t) any {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return *in
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminListRegistrationTokens(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
||||||
|
queryParams := req.URL.Query()
|
||||||
|
returnAll := true
|
||||||
|
valid := true
|
||||||
|
validQuery, ok := queryParams["valid"]
|
||||||
|
if ok {
|
||||||
|
returnAll = false
|
||||||
|
validValue, err := strconv.ParseBool(validQuery[0])
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("invalid 'valid' query parameter"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid = validValue
|
||||||
|
}
|
||||||
|
tokens, err := userAPI.PerformAdminListRegistrationTokens(req.Context(), returnAll, valid)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.ErrorUnknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: map[string]interface{}{
|
||||||
|
"registration_tokens": tokens,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminGetRegistrationToken(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
tokenText := vars["token"]
|
||||||
|
token, err := userAPI.PerformAdminGetRegistrationToken(req.Context(), tokenText)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound(fmt.Sprintf("token: %s not found", tokenText)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminDeleteRegistrationToken(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
tokenText := vars["token"]
|
||||||
|
err = userAPI.PerformAdminDeleteRegistrationToken(req.Context(), tokenText)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminUpdateRegistrationToken(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
tokenText := vars["token"]
|
||||||
|
request := make(map[string]*int64)
|
||||||
|
if err = json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON(fmt.Sprintf("Failed to decode request body: %s", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newAttributes := make(map[string]interface{})
|
||||||
|
usesAllowed, ok := request["uses_allowed"]
|
||||||
|
if ok {
|
||||||
|
// Only add usesAllowed to newAtrributes if it is present and valid
|
||||||
|
if usesAllowed != nil && *usesAllowed < 0 {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("uses_allowed must be a non-negative integer or null"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newAttributes["usesAllowed"] = usesAllowed
|
||||||
|
}
|
||||||
|
expiryTime, ok := request["expiry_time"]
|
||||||
|
if ok {
|
||||||
|
// Only add expiryTime to newAtrributes if it is present and valid
|
||||||
|
if expiryTime != nil && spec.Timestamp(*expiryTime).Time().Before(time.Now()) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("expiry_time must not be in the past"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newAttributes["expiryTime"] = expiryTime
|
||||||
|
}
|
||||||
|
if len(newAttributes) == 0 {
|
||||||
|
// No attributes to update. Return existing token
|
||||||
|
return AdminGetRegistrationToken(req, cfg, userAPI)
|
||||||
|
}
|
||||||
|
updatedToken, err := userAPI.PerformAdminUpdateRegistrationToken(req.Context(), tokenText, newAttributes)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound(fmt.Sprintf("token: %s not found", tokenText)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: *updatedToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AdminEvacuateRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
func AdminEvacuateRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -32,12 +273,12 @@ func AdminEvacuateRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAP
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := rsAPI.PerformAdminEvacuateRoom(req.Context(), vars["roomID"])
|
affected, err := rsAPI.PerformAdminEvacuateRoom(req.Context(), vars["roomID"])
|
||||||
switch err {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
case eventutil.ErrRoomNoExists:
|
case eventutil.ErrRoomNoExists:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound(err.Error()),
|
JSON: spec.NotFound(err.Error()),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logrus.WithError(err).WithField("roomID", vars["roomID"]).Error("Failed to evacuate room")
|
logrus.WithError(err).WithField("roomID", vars["roomID"]).Error("Failed to evacuate room")
|
||||||
|
|
@ -91,7 +332,7 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
||||||
if req.Body == nil {
|
if req.Body == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown("Missing request body"),
|
JSON: spec.Unknown("Missing request body"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -104,7 +345,7 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accAvailableResp := &api.QueryAccountAvailabilityResponse{}
|
accAvailableResp := &api.QueryAccountAvailabilityResponse{}
|
||||||
|
|
@ -114,28 +355,29 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
||||||
}, accAvailableResp); err != nil {
|
}, accAvailableResp); err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.InternalAPIError(req.Context(), err),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if accAvailableResp.Available {
|
if accAvailableResp.Available {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.Unknown("User does not exist"),
|
JSON: spec.Unknown("User does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request := struct {
|
request := struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
LogoutDevices bool `json:"logout_devices"`
|
||||||
}{}
|
}{}
|
||||||
if err = json.NewDecoder(req.Body).Decode(&request); err != nil {
|
if err = json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown("Failed to decode request body: " + err.Error()),
|
JSON: spec.Unknown("Failed to decode request body: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if request.Password == "" {
|
if request.Password == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("Expecting non-empty password."),
|
JSON: spec.MissingParam("Expecting non-empty password."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,13 +389,13 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
||||||
Localpart: localpart,
|
Localpart: localpart,
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
Password: request.Password,
|
Password: request.Password,
|
||||||
LogoutDevices: true,
|
LogoutDevices: request.LogoutDevices,
|
||||||
}
|
}
|
||||||
updateRes := &api.PerformPasswordUpdateResponse{}
|
updateRes := &api.PerformPasswordUpdateResponse{}
|
||||||
if err := userAPI.PerformPasswordUpdate(req.Context(), updateReq, updateRes); err != nil {
|
if err := userAPI.PerformPasswordUpdate(req.Context(), updateReq, updateRes); err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown("Failed to perform password update: " + err.Error()),
|
JSON: spec.Unknown("Failed to perform password update: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -170,7 +412,10 @@ func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *api.Device,
|
||||||
_, err := natsClient.RequestMsg(nats.NewMsg(cfg.Matrix.JetStream.Prefixed(jetstream.InputFulltextReindex)), time.Second*10)
|
_, err := natsClient.RequestMsg(nats.NewMsg(cfg.Matrix.JetStream.Prefixed(jetstream.InputFulltextReindex)), time.Second*10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Error("failed to publish nats message")
|
logrus.WithError(err).Error("failed to publish nats message")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -192,7 +437,7 @@ func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.Clien
|
||||||
if cfg.Matrix.IsLocalServerName(domain) {
|
if cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam("Can not mark local device list as stale"),
|
JSON: spec.InvalidParam("Can not mark local device list as stale"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,7 +448,7 @@ func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.Clien
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown(fmt.Sprintf("Failed to mark device list as stale: %s", err)),
|
JSON: spec.Unknown(fmt.Sprintf("Failed to mark device list as stale: %s", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -221,21 +466,21 @@ func AdminDownloadState(req *http.Request, device *api.Device, rsAPI roomserverA
|
||||||
if !ok {
|
if !ok {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("Expecting room ID."),
|
JSON: spec.MissingParam("Expecting room ID."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverName, ok := vars["serverName"]
|
serverName, ok := vars["serverName"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("Expecting remote server name."),
|
JSON: spec.MissingParam("Expecting remote server name."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = rsAPI.PerformAdminDownloadState(req.Context(), roomID, device.UserID, spec.ServerName(serverName)); err != nil {
|
if err = rsAPI.PerformAdminDownloadState(req.Context(), roomID, device.UserID, spec.ServerName(serverName)); err != nil {
|
||||||
if errors.Is(err, eventutil.ErrRoomNoExists) {
|
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
JSON: jsonerror.NotFound(eventutil.ErrRoomNoExists.Error()),
|
JSON: spec.NotFound(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -51,7 +51,7 @@ func GetAdminWhois(
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
JSON: spec.Forbidden("userID does not match the current user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +61,10 @@ func GetAdminWhois(
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("GetAdminWhois failed to query user devices")
|
util.GetLogger(req.Context()).WithError(err).Error("GetAdminWhois failed to query user devices")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
devices := make(map[string]deviceInfo)
|
devices := make(map[string]deviceInfo)
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -57,19 +55,29 @@ func GetAliases(
|
||||||
visibility = content.HistoryVisibility
|
visibility = content.HistoryVisibility
|
||||||
}
|
}
|
||||||
if visibility != spec.WorldReadable {
|
if visibility != spec.WorldReadable {
|
||||||
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("userID doesn't have power level to change visibility"),
|
||||||
|
}
|
||||||
|
}
|
||||||
queryReq := api.QueryMembershipForUserRequest{
|
queryReq := api.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: device.UserID,
|
UserID: *deviceUserID,
|
||||||
}
|
}
|
||||||
var queryRes api.QueryMembershipForUserResponse
|
var queryRes api.QueryMembershipForUserResponse
|
||||||
if err := rsAPI.QueryMembershipForUser(req.Context(), &queryReq, &queryRes); err != nil {
|
if err := rsAPI.QueryMembershipForUser(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.QueryMembershipsForRoom failed")
|
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.QueryMembershipsForRoom failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !queryRes.IsInRoom {
|
if !queryRes.IsInRoom {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("You aren't a member of this room."),
|
JSON: spec.Forbidden("You aren't a member of this room."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
|
||||||
roomserverVersion "github.com/matrix-org/dendrite/roomserver/version"
|
roomserverVersion "github.com/matrix-org/dendrite/roomserver/version"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -42,32 +37,19 @@ import (
|
||||||
|
|
||||||
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||||
type createRoomRequest struct {
|
type createRoomRequest struct {
|
||||||
Invite []string `json:"invite"`
|
Invite []string `json:"invite"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visibility string `json:"visibility"`
|
Visibility string `json:"visibility"`
|
||||||
Topic string `json:"topic"`
|
Topic string `json:"topic"`
|
||||||
Preset string `json:"preset"`
|
Preset string `json:"preset"`
|
||||||
CreationContent json.RawMessage `json:"creation_content"`
|
CreationContent json.RawMessage `json:"creation_content"`
|
||||||
InitialState []fledglingEvent `json:"initial_state"`
|
InitialState []gomatrixserverlib.FledglingEvent `json:"initial_state"`
|
||||||
RoomAliasName string `json:"room_alias_name"`
|
RoomAliasName string `json:"room_alias_name"`
|
||||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||||
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
||||||
IsDirect bool `json:"is_direct"`
|
IsDirect bool `json:"is_direct"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
presetPrivateChat = "private_chat"
|
|
||||||
presetTrustedPrivateChat = "trusted_private_chat"
|
|
||||||
presetPublicChat = "public_chat"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
historyVisibilityShared = "shared"
|
|
||||||
// TODO: These should be implemented once history visibility is implemented
|
|
||||||
// historyVisibilityWorldReadable = "world_readable"
|
|
||||||
// historyVisibilityInvited = "invited"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r createRoomRequest) Validate() *util.JSONResponse {
|
func (r createRoomRequest) Validate() *util.JSONResponse {
|
||||||
whitespace := "\t\n\x0b\x0c\r " // https://docs.python.org/2/library/string.html#string.whitespace
|
whitespace := "\t\n\x0b\x0c\r " // https://docs.python.org/2/library/string.html#string.whitespace
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L81
|
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L81
|
||||||
|
|
@ -75,28 +57,23 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
||||||
if strings.ContainsAny(r.RoomAliasName, whitespace+":") {
|
if strings.ContainsAny(r.RoomAliasName, whitespace+":") {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("room_alias_name cannot contain whitespace or ':'"),
|
JSON: spec.BadJSON("room_alias_name cannot contain whitespace or ':'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, userID := range r.Invite {
|
for _, userID := range r.Invite {
|
||||||
// TODO: We should put user ID parsing code into gomatrixserverlib and use that instead
|
if _, err := spec.NewUserID(userID, true); err != nil {
|
||||||
// (see https://github.com/matrix-org/gomatrixserverlib/blob/3394e7c7003312043208aa73727d2256eea3d1f6/eventcontent.go#L347 )
|
|
||||||
// It should be a struct (with pointers into a single string to avoid copying) and
|
|
||||||
// we should update all refs to use UserID types rather than strings.
|
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/types.py#L92
|
|
||||||
if _, _, err := gomatrixserverlib.SplitID('@', userID); err != nil {
|
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("user id must be in the form @localpart:domain"),
|
JSON: spec.BadJSON("user id must be in the form @localpart:domain"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch r.Preset {
|
switch r.Preset {
|
||||||
case presetPrivateChat, presetTrustedPrivateChat, presetPublicChat, "":
|
case spec.PresetPrivateChat, spec.PresetTrustedPrivateChat, spec.PresetPublicChat, "":
|
||||||
default:
|
default:
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("preset must be any of 'private_chat', 'trusted_private_chat', 'public_chat'"),
|
JSON: spec.BadJSON("preset must be any of 'private_chat', 'trusted_private_chat', 'public_chat'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,7 +85,7 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("malformed creation_content"),
|
JSON: spec.BadJSON("malformed creation_content"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,7 +94,7 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("malformed creation_content"),
|
JSON: spec.BadJSON("malformed creation_content"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,13 +107,6 @@ type createRoomResponse struct {
|
||||||
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
||||||
}
|
}
|
||||||
|
|
||||||
// fledglingEvent is a helper representation of an event used when creating many events in succession.
|
|
||||||
type fledglingEvent struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
StateKey string `json:"state_key"`
|
|
||||||
Content interface{} `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRoom implements /createRoom
|
// CreateRoom implements /createRoom
|
||||||
func CreateRoom(
|
func CreateRoom(
|
||||||
req *http.Request, device *api.Device,
|
req *http.Request, device *api.Device,
|
||||||
|
|
@ -144,478 +114,124 @@ func CreateRoom(
|
||||||
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var r createRoomRequest
|
var createRequest createRoomRequest
|
||||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
resErr := httputil.UnmarshalJSONRequest(req, &createRequest)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
if resErr = r.Validate(); resErr != nil {
|
if resErr = createRequest.Validate(); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
evTime, err := httputil.ParseTSParam(req)
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createRoom(req.Context(), r, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
return createRoom(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRoom implements /createRoom
|
// createRoom implements /createRoom
|
||||||
// nolint: gocyclo
|
|
||||||
func createRoom(
|
func createRoom(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r createRoomRequest, device *api.Device,
|
createRequest createRoomRequest, device *api.Device,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
_, userDomain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(ctx).WithError(err).Error("invalid userID")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !cfg.Matrix.IsLocalServerName(userDomain) {
|
if !cfg.Matrix.IsLocalServerName(userID.Domain()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(fmt.Sprintf("User domain %q not configured locally", userDomain)),
|
JSON: spec.Forbidden(fmt.Sprintf("User domain %q not configured locally", userID.Domain())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
|
||||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
|
||||||
roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), userDomain)
|
|
||||||
|
|
||||||
logger := util.GetLogger(ctx)
|
logger := util.GetLogger(ctx)
|
||||||
userID := device.UserID
|
|
||||||
|
// TODO: Check room ID doesn't clash with an existing one, and we
|
||||||
|
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||||
|
roomID, err := spec.NewRoomID(fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()))
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("invalid roomID")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clobber keys: creator, room_version
|
// Clobber keys: creator, room_version
|
||||||
|
|
||||||
roomVersion := roomserverVersion.DefaultRoomVersion()
|
roomVersion := roomserverVersion.DefaultRoomVersion()
|
||||||
if r.RoomVersion != "" {
|
if createRequest.RoomVersion != "" {
|
||||||
candidateVersion := gomatrixserverlib.RoomVersion(r.RoomVersion)
|
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
||||||
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
||||||
if roomVersionError != nil {
|
if roomVersionError != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UnsupportedRoomVersion(roomVersionError.Error()),
|
JSON: spec.UnsupportedRoomVersion(roomVersionError.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomVersion = candidateVersion
|
roomVersion = candidateVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: visibility/presets/raw initial state
|
|
||||||
// TODO: Create room alias association
|
|
||||||
// Make sure this doesn't fall into an application service's namespace though!
|
|
||||||
|
|
||||||
logger.WithFields(log.Fields{
|
logger.WithFields(log.Fields{
|
||||||
"userID": userID,
|
"userID": userID.String(),
|
||||||
"roomID": roomID,
|
"roomID": roomID.String(),
|
||||||
"roomVersion": roomVersion,
|
"roomVersion": roomVersion,
|
||||||
}).Info("Creating new room")
|
}).Info("Creating new room")
|
||||||
|
|
||||||
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
|
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
createContent := map[string]interface{}{}
|
|
||||||
if len(r.CreationContent) > 0 {
|
|
||||||
if err = json.Unmarshal(r.CreationContent, &createContent); err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.BadJSON("invalid create content"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createContent["creator"] = userID
|
|
||||||
createContent["room_version"] = roomVersion
|
|
||||||
powerLevelContent := eventutil.InitialPowerLevelsContent(userID)
|
|
||||||
joinRuleContent := gomatrixserverlib.JoinRuleContent{
|
|
||||||
JoinRule: spec.Invite,
|
|
||||||
}
|
|
||||||
historyVisibilityContent := gomatrixserverlib.HistoryVisibilityContent{
|
|
||||||
HistoryVisibility: historyVisibilityShared,
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.PowerLevelContentOverride != nil {
|
|
||||||
// Merge powerLevelContentOverride fields by unmarshalling it atop the defaults
|
|
||||||
err = json.Unmarshal(r.PowerLevelContentOverride, &powerLevelContent)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for power_level_content_override failed")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.BadJSON("malformed power_level_content_override"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var guestsCanJoin bool
|
|
||||||
switch r.Preset {
|
|
||||||
case presetPrivateChat:
|
|
||||||
joinRuleContent.JoinRule = spec.Invite
|
|
||||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
|
||||||
guestsCanJoin = true
|
|
||||||
case presetTrustedPrivateChat:
|
|
||||||
joinRuleContent.JoinRule = spec.Invite
|
|
||||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
|
||||||
for _, invitee := range r.Invite {
|
|
||||||
powerLevelContent.Users[invitee] = 100
|
|
||||||
}
|
|
||||||
guestsCanJoin = true
|
|
||||||
case presetPublicChat:
|
|
||||||
joinRuleContent.JoinRule = spec.Public
|
|
||||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent := fledglingEvent{
|
|
||||||
Type: spec.MRoomCreate,
|
|
||||||
Content: createContent,
|
|
||||||
}
|
|
||||||
powerLevelEvent := fledglingEvent{
|
|
||||||
Type: spec.MRoomPowerLevels,
|
|
||||||
Content: powerLevelContent,
|
|
||||||
}
|
|
||||||
joinRuleEvent := fledglingEvent{
|
|
||||||
Type: spec.MRoomJoinRules,
|
|
||||||
Content: joinRuleContent,
|
|
||||||
}
|
|
||||||
historyVisibilityEvent := fledglingEvent{
|
|
||||||
Type: spec.MRoomHistoryVisibility,
|
|
||||||
Content: historyVisibilityContent,
|
|
||||||
}
|
|
||||||
membershipEvent := fledglingEvent{
|
|
||||||
Type: spec.MRoomMember,
|
|
||||||
StateKey: userID,
|
|
||||||
Content: gomatrixserverlib.MemberContent{
|
|
||||||
Membership: spec.Join,
|
|
||||||
DisplayName: profile.DisplayName,
|
|
||||||
AvatarURL: profile.AvatarURL,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var nameEvent *fledglingEvent
|
|
||||||
var topicEvent *fledglingEvent
|
|
||||||
var guestAccessEvent *fledglingEvent
|
|
||||||
var aliasEvent *fledglingEvent
|
|
||||||
|
|
||||||
if r.Name != "" {
|
|
||||||
nameEvent = &fledglingEvent{
|
|
||||||
Type: spec.MRoomName,
|
|
||||||
Content: eventutil.NameContent{
|
|
||||||
Name: r.Name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Topic != "" {
|
|
||||||
topicEvent = &fledglingEvent{
|
|
||||||
Type: spec.MRoomTopic,
|
|
||||||
Content: eventutil.TopicContent{
|
|
||||||
Topic: r.Topic,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if guestsCanJoin {
|
|
||||||
guestAccessEvent = &fledglingEvent{
|
|
||||||
Type: spec.MRoomGuestAccess,
|
|
||||||
Content: eventutil.GuestAccessContent{
|
|
||||||
GuestAccess: "can_join",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomAlias string
|
|
||||||
if r.RoomAliasName != "" {
|
|
||||||
roomAlias = fmt.Sprintf("#%s:%s", r.RoomAliasName, userDomain)
|
|
||||||
// check it's free TODO: This races but is better than nothing
|
|
||||||
hasAliasReq := roomserverAPI.GetRoomIDForAliasRequest{
|
|
||||||
Alias: roomAlias,
|
|
||||||
IncludeAppservices: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
var aliasResp roomserverAPI.GetRoomIDForAliasResponse
|
|
||||||
err = rsAPI.GetRoomIDForAlias(ctx, &hasAliasReq, &aliasResp)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.GetRoomIDForAlias failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
if aliasResp.RoomID != "" {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.RoomInUse("Room ID already exists."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aliasEvent = &fledglingEvent{
|
|
||||||
Type: spec.MRoomCanonicalAlias,
|
|
||||||
Content: eventutil.CanonicalAlias{
|
|
||||||
Alias: roomAlias,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var initialStateEvents []fledglingEvent
|
|
||||||
for i := range r.InitialState {
|
|
||||||
if r.InitialState[i].StateKey != "" {
|
|
||||||
initialStateEvents = append(initialStateEvents, r.InitialState[i])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.InitialState[i].Type {
|
|
||||||
case spec.MRoomCreate:
|
|
||||||
continue
|
|
||||||
|
|
||||||
case spec.MRoomPowerLevels:
|
|
||||||
powerLevelEvent = r.InitialState[i]
|
|
||||||
|
|
||||||
case spec.MRoomJoinRules:
|
|
||||||
joinRuleEvent = r.InitialState[i]
|
|
||||||
|
|
||||||
case spec.MRoomHistoryVisibility:
|
|
||||||
historyVisibilityEvent = r.InitialState[i]
|
|
||||||
|
|
||||||
case spec.MRoomGuestAccess:
|
|
||||||
guestAccessEvent = &r.InitialState[i]
|
|
||||||
|
|
||||||
case spec.MRoomName:
|
|
||||||
nameEvent = &r.InitialState[i]
|
|
||||||
|
|
||||||
case spec.MRoomTopic:
|
|
||||||
topicEvent = &r.InitialState[i]
|
|
||||||
|
|
||||||
default:
|
|
||||||
initialStateEvents = append(initialStateEvents, r.InitialState[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send events into the room in order of:
|
|
||||||
// 1- m.room.create
|
|
||||||
// 2- room creator join member
|
|
||||||
// 3- m.room.power_levels
|
|
||||||
// 4- m.room.join_rules
|
|
||||||
// 5- m.room.history_visibility
|
|
||||||
// 6- m.room.canonical_alias (opt)
|
|
||||||
// 7- m.room.guest_access (opt)
|
|
||||||
// 8- other initial state items
|
|
||||||
// 9- m.room.name (opt)
|
|
||||||
// 10- m.room.topic (opt)
|
|
||||||
// 11- invite events (opt) - with is_direct flag if applicable TODO
|
|
||||||
// 12- 3pid invite events (opt) TODO
|
|
||||||
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
|
||||||
// depending on if those events were in "initial_state" or not. This made it
|
|
||||||
// harder to reason about, hence sticking to a strict static ordering.
|
|
||||||
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
|
||||||
eventsToMake := []fledglingEvent{
|
|
||||||
createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
|
||||||
}
|
|
||||||
if guestAccessEvent != nil {
|
|
||||||
eventsToMake = append(eventsToMake, *guestAccessEvent)
|
|
||||||
}
|
|
||||||
eventsToMake = append(eventsToMake, initialStateEvents...)
|
|
||||||
if nameEvent != nil {
|
|
||||||
eventsToMake = append(eventsToMake, *nameEvent)
|
|
||||||
}
|
|
||||||
if topicEvent != nil {
|
|
||||||
eventsToMake = append(eventsToMake, *topicEvent)
|
|
||||||
}
|
|
||||||
if aliasEvent != nil {
|
|
||||||
// TODO: bit of a chicken and egg problem here as the alias doesn't exist and cannot until we have made the room.
|
|
||||||
// This means we might fail creating the alias but say the canonical alias is something that doesn't exist.
|
|
||||||
eventsToMake = append(eventsToMake, *aliasEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: invite events
|
|
||||||
// TODO: 3pid invite events
|
|
||||||
|
|
||||||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomVersion)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.BadJSON("unknown room version"),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var builtEvents []*types.HeaderedEvent
|
userDisplayName := profile.DisplayName
|
||||||
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
userAvatarURL := profile.AvatarURL
|
||||||
for i, e := range eventsToMake {
|
|
||||||
depth := i + 1 // depth starts at 1
|
|
||||||
|
|
||||||
builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{
|
keyID := cfg.Matrix.KeyID
|
||||||
Sender: userID,
|
privateKey := cfg.Matrix.PrivateKey
|
||||||
RoomID: roomID,
|
|
||||||
Type: e.Type,
|
|
||||||
StateKey: &e.StateKey,
|
|
||||||
Depth: int64(depth),
|
|
||||||
})
|
|
||||||
err = builder.SetContent(e.Content)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
|
||||||
}
|
|
||||||
var ev gomatrixserverlib.PDU
|
|
||||||
if err = builder.AddAuthEvents(&authEvents); err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
ev, err = builder.Build(evTime, userDomain, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = gomatrixserverlib.Allowed(ev, &authEvents); err != nil {
|
req := roomserverAPI.PerformCreateRoomRequest{
|
||||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
InvitedUsers: createRequest.Invite,
|
||||||
return jsonerror.InternalServerError()
|
RoomName: createRequest.Name,
|
||||||
}
|
Visibility: createRequest.Visibility,
|
||||||
|
Topic: createRequest.Topic,
|
||||||
|
StatePreset: createRequest.Preset,
|
||||||
|
CreationContent: createRequest.CreationContent,
|
||||||
|
InitialState: createRequest.InitialState,
|
||||||
|
RoomAliasName: createRequest.RoomAliasName,
|
||||||
|
RoomVersion: roomVersion,
|
||||||
|
PowerLevelContentOverride: createRequest.PowerLevelContentOverride,
|
||||||
|
IsDirect: createRequest.IsDirect,
|
||||||
|
|
||||||
// Add the event to the list of auth events
|
UserDisplayName: userDisplayName,
|
||||||
builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: ev})
|
UserAvatarURL: userAvatarURL,
|
||||||
err = authEvents.AddEvent(ev)
|
KeyID: keyID,
|
||||||
if err != nil {
|
PrivateKey: privateKey,
|
||||||
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
|
EventTime: evTime,
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]roomserverAPI.InputRoomEvent, 0, len(builtEvents))
|
roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req)
|
||||||
for _, event := range builtEvents {
|
if createRes != nil {
|
||||||
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
return *createRes
|
||||||
Kind: roomserverAPI.KindNew,
|
|
||||||
Event: event,
|
|
||||||
Origin: userDomain,
|
|
||||||
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err = roomserverAPI.SendInputRoomEvents(ctx, rsAPI, device.UserDomain(), inputs, false); err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(#269): Reserve room alias while we create the room. This stops us
|
|
||||||
// from creating the room but still failing due to the alias having already
|
|
||||||
// been taken.
|
|
||||||
if roomAlias != "" {
|
|
||||||
aliasReq := roomserverAPI.SetRoomAliasRequest{
|
|
||||||
Alias: roomAlias,
|
|
||||||
RoomID: roomID,
|
|
||||||
UserID: userID,
|
|
||||||
}
|
|
||||||
|
|
||||||
var aliasResp roomserverAPI.SetRoomAliasResponse
|
|
||||||
err = rsAPI.SetRoomAlias(ctx, &aliasReq, &aliasResp)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if aliasResp.AliasExists {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.RoomInUse("Room alias already exists."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a direct message then we should invite the participants.
|
|
||||||
if len(r.Invite) > 0 {
|
|
||||||
// Build some stripped state for the invite.
|
|
||||||
var globalStrippedState []fclient.InviteV2StrippedState
|
|
||||||
for _, event := range builtEvents {
|
|
||||||
// Chosen events from the spec:
|
|
||||||
// https://spec.matrix.org/v1.3/client-server-api/#stripped-state
|
|
||||||
switch event.Type() {
|
|
||||||
case spec.MRoomCreate:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomName:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomAvatar:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomTopic:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomCanonicalAlias:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomEncryption:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomMember:
|
|
||||||
fallthrough
|
|
||||||
case spec.MRoomJoinRules:
|
|
||||||
ev := event.PDU
|
|
||||||
globalStrippedState = append(
|
|
||||||
globalStrippedState,
|
|
||||||
fclient.NewInviteV2StrippedState(ev),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the invites.
|
|
||||||
var inviteEvent *types.HeaderedEvent
|
|
||||||
for _, invitee := range r.Invite {
|
|
||||||
// Build the invite event.
|
|
||||||
inviteEvent, err = buildMembershipEvent(
|
|
||||||
ctx, invitee, "", profileAPI, device, spec.Invite,
|
|
||||||
roomID, r.IsDirect, cfg, evTime, rsAPI, asAPI,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
inviteStrippedState := append(
|
|
||||||
globalStrippedState,
|
|
||||||
fclient.NewInviteV2StrippedState(inviteEvent.PDU),
|
|
||||||
)
|
|
||||||
// Send the invite event to the roomserver.
|
|
||||||
event := inviteEvent
|
|
||||||
err = rsAPI.PerformInvite(ctx, &roomserverAPI.PerformInviteRequest{
|
|
||||||
Event: event,
|
|
||||||
InviteRoomState: inviteStrippedState,
|
|
||||||
RoomVersion: event.Version(),
|
|
||||||
SendAsServer: string(userDomain),
|
|
||||||
})
|
|
||||||
switch e := err.(type) {
|
|
||||||
case roomserverAPI.ErrInvalidID:
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.Unknown(e.Error()),
|
|
||||||
}
|
|
||||||
case roomserverAPI.ErrNotAllowed:
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
JSON: jsonerror.Forbidden(e.Error()),
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
default:
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
|
||||||
sentry.CaptureException(err)
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: jsonerror.InternalServerError(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Visibility == spec.Public {
|
|
||||||
// expose this room in the published room list
|
|
||||||
if err = rsAPI.PerformPublish(ctx, &roomserverAPI.PerformPublishRequest{
|
|
||||||
RoomID: roomID,
|
|
||||||
Visibility: spec.Public,
|
|
||||||
}); err != nil {
|
|
||||||
util.GetLogger(ctx).WithError(err).Error("failed to publish room")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response := createRoomResponse{
|
response := createRoomResponse{
|
||||||
RoomID: roomID,
|
RoomID: roomID.String(),
|
||||||
RoomAlias: roomAlias,
|
RoomAlias: roomAlias,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ func Deactivate(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +36,10 @@ func Deactivate(
|
||||||
localpart, serverName, err := gomatrixserverlib.SplitID('@', login.Username())
|
localpart, serverName, err := gomatrixserverlib.SplitID('@', login.Username())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res api.PerformAccountDeactivationResponse
|
var res api.PerformAccountDeactivationResponse
|
||||||
|
|
@ -46,7 +49,10 @@ func Deactivate(
|
||||||
}, &res)
|
}, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformAccountDeactivation failed")
|
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformAccountDeactivation failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
@ -60,7 +60,10 @@ func GetDeviceByID(
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryDevices failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryDevices failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var targetDevice *api.Device
|
var targetDevice *api.Device
|
||||||
for _, device := range queryRes.Devices {
|
for _, device := range queryRes.Devices {
|
||||||
|
|
@ -72,7 +75,7 @@ func GetDeviceByID(
|
||||||
if targetDevice == nil {
|
if targetDevice == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("Unknown device"),
|
JSON: spec.NotFound("Unknown device"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +100,10 @@ func GetDevicesByLocalpart(
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryDevices failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryDevices failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := devicesJSON{}
|
res := devicesJSON{}
|
||||||
|
|
@ -139,12 +145,15 @@ func UpdateDeviceByID(
|
||||||
}, &performRes)
|
}, &performRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceUpdate failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceUpdate failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !performRes.DeviceExists {
|
if !performRes.DeviceExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.Forbidden("device does not exist"),
|
JSON: spec.Forbidden("device does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,7 +183,7 @@ func DeleteDeviceById(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +193,7 @@ func DeleteDeviceById(
|
||||||
if dev != deviceID {
|
if dev != deviceID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("session and device mismatch"),
|
JSON: spec.Forbidden("session and device mismatch"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +215,10 @@ func DeleteDeviceById(
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure that the access token being used matches the login creds used for user interactive auth, else
|
// make sure that the access token being used matches the login creds used for user interactive auth, else
|
||||||
|
|
@ -214,7 +226,7 @@ func DeleteDeviceById(
|
||||||
if login.Username() != localpart && login.Username() != device.UserID {
|
if login.Username() != localpart && login.Username() != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 403,
|
Code: 403,
|
||||||
JSON: jsonerror.Forbidden("Cannot delete another user's device"),
|
JSON: spec.Forbidden("Cannot delete another user's device"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +236,10 @@ func DeleteDeviceById(
|
||||||
DeviceIDs: []string{deviceID},
|
DeviceIDs: []string{deviceID},
|
||||||
}, &res); err != nil {
|
}, &res); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformDeviceDeletion failed")
|
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformDeviceDeletion failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOK = true
|
deleteOK = true
|
||||||
|
|
@ -245,7 +260,7 @@ func DeleteDevices(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer req.Body.Close() // nolint:errcheck
|
defer req.Body.Close() // nolint:errcheck
|
||||||
|
|
@ -259,14 +274,17 @@ func DeleteDevices(
|
||||||
if login.Username() != device.UserID {
|
if login.Username() != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("unable to delete devices for other user"),
|
JSON: spec.Forbidden("unable to delete devices for other user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := devicesDeleteJSON{}
|
payload := devicesDeleteJSON{}
|
||||||
if err = json.Unmarshal(bodyBytes, &payload); err != nil {
|
if err = json.Unmarshal(bodyBytes, &payload); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("unable to unmarshal device deletion request")
|
util.GetLogger(ctx).WithError(err).Error("unable to unmarshal device deletion request")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res api.PerformDeviceDeletionResponse
|
var res api.PerformDeviceDeletionResponse
|
||||||
|
|
@ -275,7 +293,10 @@ func DeleteDevices(
|
||||||
DeviceIDs: payload.Devices,
|
DeviceIDs: payload.Devices,
|
||||||
}, &res); err != nil {
|
}, &res); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformDeviceDeletion failed")
|
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformDeviceDeletion failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
|
@ -56,7 +55,7 @@ func DirectoryRoom(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +69,10 @@ func DirectoryRoom(
|
||||||
queryRes := &roomserverAPI.GetRoomIDForAliasResponse{}
|
queryRes := &roomserverAPI.GetRoomIDForAliasResponse{}
|
||||||
if err = rsAPI.GetRoomIDForAlias(req.Context(), queryReq, queryRes); err != nil {
|
if err = rsAPI.GetRoomIDForAlias(req.Context(), queryReq, queryRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.GetRoomIDForAlias failed")
|
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.GetRoomIDForAlias failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.RoomID = queryRes.RoomID
|
res.RoomID = queryRes.RoomID
|
||||||
|
|
@ -84,7 +86,10 @@ func DirectoryRoom(
|
||||||
// TODO: Return 502 if the remote server errored.
|
// TODO: Return 502 if the remote server errored.
|
||||||
// TODO: Return 504 if the remote server timed out.
|
// TODO: Return 504 if the remote server timed out.
|
||||||
util.GetLogger(req.Context()).WithError(fedErr).Error("federation.LookupRoomAlias failed")
|
util.GetLogger(req.Context()).WithError(fedErr).Error("federation.LookupRoomAlias failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.RoomID = fedRes.RoomID
|
res.RoomID = fedRes.RoomID
|
||||||
res.fillServers(fedRes.Servers)
|
res.fillServers(fedRes.Servers)
|
||||||
|
|
@ -93,7 +98,7 @@ func DirectoryRoom(
|
||||||
if res.RoomID == "" {
|
if res.RoomID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound(
|
JSON: spec.NotFound(
|
||||||
fmt.Sprintf("Room alias %s not found", roomAlias),
|
fmt.Sprintf("Room alias %s not found", roomAlias),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +108,10 @@ func DirectoryRoom(
|
||||||
var joinedHostsRes federationAPI.QueryJoinedHostServerNamesInRoomResponse
|
var joinedHostsRes federationAPI.QueryJoinedHostServerNamesInRoomResponse
|
||||||
if err = fedSenderAPI.QueryJoinedHostServerNamesInRoom(req.Context(), &joinedHostsReq, &joinedHostsRes); err != nil {
|
if err = fedSenderAPI.QueryJoinedHostServerNamesInRoom(req.Context(), &joinedHostsReq, &joinedHostsRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("fedSenderAPI.QueryJoinedHostServerNamesInRoom failed")
|
util.GetLogger(req.Context()).WithError(err).Error("fedSenderAPI.QueryJoinedHostServerNamesInRoom failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.fillServers(joinedHostsRes.ServerNames)
|
res.fillServers(joinedHostsRes.ServerNames)
|
||||||
}
|
}
|
||||||
|
|
@ -126,14 +134,14 @@ func SetLocalAlias(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Matrix.IsLocalServerName(domain) {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Alias must be on local homeserver"),
|
JSON: spec.Forbidden("Alias must be on local homeserver"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +154,7 @@ func SetLocalAlias(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("User ID must be in the form '@localpart:domain'"),
|
JSON: spec.BadJSON("User ID must be in the form '@localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, appservice := range cfg.Derived.ApplicationServices {
|
for _, appservice := range cfg.Derived.ApplicationServices {
|
||||||
|
|
@ -158,7 +166,7 @@ func SetLocalAlias(
|
||||||
if namespace.Exclusive && namespace.RegexpObject.MatchString(alias) {
|
if namespace.Exclusive && namespace.RegexpObject.MatchString(alias) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.ASExclusive("Alias is reserved by an application service"),
|
JSON: spec.ASExclusive("Alias is reserved by an application service"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,13 +189,16 @@ func SetLocalAlias(
|
||||||
var queryRes roomserverAPI.SetRoomAliasResponse
|
var queryRes roomserverAPI.SetRoomAliasResponse
|
||||||
if err := rsAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
if err := rsAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if queryRes.AliasExists {
|
if queryRes.AliasExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusConflict,
|
Code: http.StatusConflict,
|
||||||
JSON: jsonerror.Unknown("The alias " + alias + " already exists."),
|
JSON: spec.Unknown("The alias " + alias + " already exists."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,27 +215,63 @@ func RemoveLocalAlias(
|
||||||
alias string,
|
alias string,
|
||||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: "UserID for device is invalid"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roomIDReq := roomserverAPI.GetRoomIDForAliasRequest{Alias: alias}
|
||||||
|
roomIDRes := roomserverAPI.GetRoomIDForAliasResponse{}
|
||||||
|
err = rsAPI.GetRoomIDForAlias(req.Context(), &roomIDReq, &roomIDRes)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound("The alias does not exist."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validRoomID, err := spec.NewRoomID(roomIDRes.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound("The alias does not exist."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deviceSenderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *userID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound("The alias does not exist."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
queryReq := roomserverAPI.RemoveRoomAliasRequest{
|
queryReq := roomserverAPI.RemoveRoomAliasRequest{
|
||||||
Alias: alias,
|
Alias: alias,
|
||||||
UserID: device.UserID,
|
SenderID: deviceSenderID,
|
||||||
}
|
}
|
||||||
var queryRes roomserverAPI.RemoveRoomAliasResponse
|
var queryRes roomserverAPI.RemoveRoomAliasResponse
|
||||||
if err := rsAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
if err := rsAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !queryRes.Found {
|
if !queryRes.Found {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("The alias does not exist."),
|
JSON: spec.NotFound("The alias does not exist."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !queryRes.Removed {
|
if !queryRes.Removed {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("You do not have permission to remove this alias."),
|
JSON: spec.Forbidden("You do not have permission to remove this alias."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,7 +296,10 @@ func GetVisibility(
|
||||||
}, &res)
|
}, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryPublishedRooms failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryPublishedRooms failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var v roomVisibility
|
var v roomVisibility
|
||||||
|
|
@ -271,7 +321,30 @@ func SetVisibility(
|
||||||
req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, dev *userapi.Device,
|
req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, dev *userapi.Device,
|
||||||
roomID string,
|
roomID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
resErr := checkMemberInRoom(req.Context(), rsAPI, dev.UserID, roomID)
|
deviceUserID, err := spec.NewUserID(dev.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("userID for this device is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("roomID is invalid")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("RoomID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.Unknown("failed to find senderID for this user"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -284,18 +357,21 @@ func SetVisibility(
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
var queryEventsRes roomserverAPI.QueryLatestEventsAndStateResponse
|
var queryEventsRes roomserverAPI.QueryLatestEventsAndStateResponse
|
||||||
err := rsAPI.QueryLatestEventsAndState(req.Context(), &queryEventsReq, &queryEventsRes)
|
err = rsAPI.QueryLatestEventsAndState(req.Context(), &queryEventsReq, &queryEventsRes)
|
||||||
if err != nil || len(queryEventsRes.StateEvents) == 0 {
|
if err != nil || len(queryEventsRes.StateEvents) == 0 {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("could not query events from room")
|
util.GetLogger(req.Context()).WithError(err).Error("could not query events from room")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTSPEC: Check if the user's power is greater than power required to change m.room.canonical_alias event
|
// NOTSPEC: Check if the user's power is greater than power required to change m.room.canonical_alias event
|
||||||
power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU)
|
power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU)
|
||||||
if power.UserLevel(dev.UserID) < power.EventLevel(spec.MRoomCanonicalAlias, true) {
|
if power.UserLevel(senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID doesn't have power level to change visibility"),
|
JSON: spec.Forbidden("userID doesn't have power level to change visibility"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,7 +385,10 @@ func SetVisibility(
|
||||||
Visibility: v.Visibility,
|
Visibility: v.Visibility,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
|
util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -325,7 +404,7 @@ func SetVisibilityAS(
|
||||||
if dev.AccountType != userapi.AccountTypeAppService {
|
if dev.AccountType != userapi.AccountTypeAppService {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Only appservice may use this endpoint"),
|
JSON: spec.Forbidden("Only appservice may use this endpoint"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var v roomVisibility
|
var v roomVisibility
|
||||||
|
|
@ -345,7 +424,10 @@ func SetVisibilityAS(
|
||||||
AppserviceID: dev.AppserviceID,
|
AppserviceID: dev.AppserviceID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
|
util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/api"
|
"github.com/matrix-org/dendrite/clientapi/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
)
|
)
|
||||||
|
|
@ -68,7 +67,7 @@ func GetPostPublicRooms(
|
||||||
if request.IncludeAllNetworks && request.NetworkID != "" {
|
if request.IncludeAllNetworks && request.NetworkID != "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam("include_all_networks and third_party_instance_id can not be used together"),
|
JSON: spec.InvalidParam("include_all_networks and third_party_instance_id can not be used together"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +81,10 @@ func GetPostPublicRooms(
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("failed to get public rooms")
|
util.GetLogger(req.Context()).WithError(err).Error("failed to get public rooms")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -93,7 +95,10 @@ func GetPostPublicRooms(
|
||||||
response, err := publicRooms(req.Context(), request, rsAPI, extRoomsProvider)
|
response, err := publicRooms(req.Context(), request, rsAPI, extRoomsProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Errorf("failed to work out public rooms")
|
util.GetLogger(req.Context()).WithError(err).Errorf("failed to work out public rooms")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -173,7 +178,7 @@ func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSO
|
||||||
if httpReq.Method != "GET" && httpReq.Method != "POST" {
|
if httpReq.Method != "GET" && httpReq.Method != "POST" {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusMethodNotAllowed,
|
Code: http.StatusMethodNotAllowed,
|
||||||
JSON: jsonerror.NotFound("Bad method"),
|
JSON: spec.NotFound("Bad method"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if httpReq.Method == "GET" {
|
if httpReq.Method == "GET" {
|
||||||
|
|
@ -184,7 +189,7 @@ func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSO
|
||||||
util.GetLogger(httpReq.Context()).WithError(err).Error("strconv.Atoi failed")
|
util.GetLogger(httpReq.Context()).WithError(err).Error("strconv.Atoi failed")
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.BadJSON("limit param is not a number"),
|
JSON: spec.BadJSON("limit param is not a number"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.Limit = int64(limit)
|
request.Limit = int64(limit)
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type getJoinedRoomsResponse struct {
|
type getJoinedRoomsResponse struct {
|
||||||
|
|
@ -40,7 +40,10 @@ func GetJoinedRooms(
|
||||||
}, &res)
|
}, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if res.RoomIDs == nil {
|
if res.RoomIDs == nil {
|
||||||
res.RoomIDs = []string{}
|
res.RoomIDs = []string{}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,11 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
|
@ -75,7 +73,7 @@ func JoinRoomByIDOrAlias(
|
||||||
util.GetLogger(req.Context()).Error("Unable to query user profile, no profile found.")
|
util.GetLogger(req.Context()).Error("Unable to query user profile, no profile found.")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("Unable to query user profile, no profile found."),
|
JSON: spec.Unknown("Unable to query user profile, no profile found."),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
@ -99,12 +97,12 @@ func JoinRoomByIDOrAlias(
|
||||||
case roomserverAPI.ErrInvalidID:
|
case roomserverAPI.ErrInvalidID:
|
||||||
response = util.JSONResponse{
|
response = util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(e.Error()),
|
JSON: spec.Unknown(e.Error()),
|
||||||
}
|
}
|
||||||
case roomserverAPI.ErrNotAllowed:
|
case roomserverAPI.ErrNotAllowed:
|
||||||
jsonErr := jsonerror.Forbidden(e.Error())
|
jsonErr := spec.Forbidden(e.Error())
|
||||||
if device.AccountType == api.AccountTypeGuest {
|
if device.AccountType == api.AccountTypeGuest {
|
||||||
jsonErr = jsonerror.GuestAccessForbidden(e.Error())
|
jsonErr = spec.GuestAccessForbidden(e.Error())
|
||||||
}
|
}
|
||||||
response = util.JSONResponse{
|
response = util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
|
|
@ -115,16 +113,15 @@ func JoinRoomByIDOrAlias(
|
||||||
Code: e.Code,
|
Code: e.Code,
|
||||||
JSON: json.RawMessage(e.Message),
|
JSON: json.RawMessage(e.Message),
|
||||||
}
|
}
|
||||||
|
case eventutil.ErrRoomNoExists:
|
||||||
|
response = util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound(e.Error()),
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
response = util.JSONResponse{
|
response = util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.InternalServerError(),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
|
||||||
if errors.Is(err, eventutil.ErrRoomNoExists) {
|
|
||||||
response = util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound(e.Error()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done <- response
|
done <- response
|
||||||
|
|
@ -137,7 +134,7 @@ func JoinRoomByIDOrAlias(
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusAccepted,
|
Code: http.StatusAccepted,
|
||||||
JSON: jsonerror.Unknown("The room join will continue in the background."),
|
JSON: spec.Unknown("The room join will continue in the background."),
|
||||||
}
|
}
|
||||||
case result := <-done:
|
case result := <-done:
|
||||||
// Stop and drain the timer
|
// Stop and drain the timer
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"github.com/matrix-org/dendrite/appservice"
|
||||||
"github.com/matrix-org/dendrite/roomserver"
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
|
|
@ -63,7 +64,7 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
||||||
IsDirect: true,
|
IsDirect: true,
|
||||||
Topic: "testing",
|
Topic: "testing",
|
||||||
Visibility: "public",
|
Visibility: "public",
|
||||||
Preset: presetPublicChat,
|
Preset: spec.PresetPublicChat,
|
||||||
RoomAliasName: "alias",
|
RoomAliasName: "alias",
|
||||||
Invite: []string{bob.ID},
|
Invite: []string{bob.ID},
|
||||||
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
||||||
|
|
@ -78,7 +79,7 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
||||||
IsDirect: true,
|
IsDirect: true,
|
||||||
Topic: "testing",
|
Topic: "testing",
|
||||||
Visibility: "public",
|
Visibility: "public",
|
||||||
Preset: presetPublicChat,
|
Preset: spec.PresetPublicChat,
|
||||||
Invite: []string{charlie.ID},
|
Invite: []string{charlie.ID},
|
||||||
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
||||||
crRespWithGuestAccess, ok := resp.JSON.(createRoomResponse)
|
crRespWithGuestAccess, ok := resp.JSON.(createRoomResponse)
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ func CreateKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
|
||||||
if len(kb.AuthData) == 0 {
|
if len(kb.AuthData) == 0 {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing auth_data"),
|
JSON: spec.BadJSON("missing auth_data"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version, err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
version, err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||||
|
|
@ -98,7 +98,7 @@ func KeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *
|
||||||
if !queryResp.Exists {
|
if !queryResp.Exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("version not found"),
|
JSON: spec.NotFound("version not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -128,7 +128,7 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse
|
||||||
Algorithm: kb.Algorithm,
|
Algorithm: kb.Algorithm,
|
||||||
})
|
})
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case *jsonerror.ErrRoomKeysVersion:
|
case spec.ErrRoomKeysVersion:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: e,
|
JSON: e,
|
||||||
|
|
@ -141,7 +141,7 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse
|
||||||
if !performKeyBackupResp.Exists {
|
if !performKeyBackupResp.Exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("backup version not found"),
|
JSON: spec.NotFound("backup version not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -162,7 +162,7 @@ func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
|
||||||
if !exists {
|
if !exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("backup version not found"),
|
JSON: spec.NotFound("backup version not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -182,7 +182,7 @@ func UploadBackupKeys(
|
||||||
})
|
})
|
||||||
|
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case *jsonerror.ErrRoomKeysVersion:
|
case spec.ErrRoomKeysVersion:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: e,
|
JSON: e,
|
||||||
|
|
@ -194,7 +194,7 @@ func UploadBackupKeys(
|
||||||
if !performKeyBackupResp.Exists {
|
if !performKeyBackupResp.Exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("backup version not found"),
|
JSON: spec.NotFound("backup version not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -223,7 +223,7 @@ func GetBackupKeys(
|
||||||
if !queryResp.Exists {
|
if !queryResp.Exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("version not found"),
|
JSON: spec.NotFound("version not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sessionID != "" {
|
if sessionID != "" {
|
||||||
|
|
@ -274,6 +274,6 @@ func GetBackupKeys(
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
JSON: jsonerror.NotFound("keys not found"),
|
JSON: spec.NotFound("keys not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -71,31 +71,29 @@ func UploadCrossSigningDeviceKeys(
|
||||||
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
|
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
|
||||||
|
|
||||||
uploadReq.UserID = device.UserID
|
uploadReq.UserID = device.UserID
|
||||||
if err := keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes); err != nil {
|
keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes)
|
||||||
return jsonerror.InternalAPIError(req.Context(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := uploadRes.Error; err != nil {
|
if err := uploadRes.Error; err != nil {
|
||||||
switch {
|
switch {
|
||||||
case err.IsInvalidSignature:
|
case err.IsInvalidSignature:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidSignature(err.Error()),
|
JSON: spec.InvalidSignature(err.Error()),
|
||||||
}
|
}
|
||||||
case err.IsMissingParam:
|
case err.IsMissingParam:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingParam(err.Error()),
|
JSON: spec.MissingParam(err.Error()),
|
||||||
}
|
}
|
||||||
case err.IsInvalidParam:
|
case err.IsInvalidParam:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(err.Error()),
|
JSON: spec.Unknown(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -115,31 +113,29 @@ func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.Clie
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadReq.UserID = device.UserID
|
uploadReq.UserID = device.UserID
|
||||||
if err := keyserverAPI.PerformUploadDeviceSignatures(req.Context(), uploadReq, uploadRes); err != nil {
|
keyserverAPI.PerformUploadDeviceSignatures(req.Context(), uploadReq, uploadRes)
|
||||||
return jsonerror.InternalAPIError(req.Context(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := uploadRes.Error; err != nil {
|
if err := uploadRes.Error; err != nil {
|
||||||
switch {
|
switch {
|
||||||
case err.IsInvalidSignature:
|
case err.IsInvalidSignature:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidSignature(err.Error()),
|
JSON: spec.InvalidSignature(err.Error()),
|
||||||
}
|
}
|
||||||
case err.IsMissingParam:
|
case err.IsMissingParam:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingParam(err.Error()),
|
JSON: spec.MissingParam(err.Error()),
|
||||||
}
|
}
|
||||||
case err.IsInvalidParam:
|
case err.IsInvalidParam:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(err.Error()),
|
JSON: spec.Unknown(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type uploadKeysRequest struct {
|
type uploadKeysRequest struct {
|
||||||
|
|
@ -67,7 +67,10 @@ func UploadKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *api.Device)
|
||||||
}
|
}
|
||||||
if uploadRes.Error != nil {
|
if uploadRes.Error != nil {
|
||||||
util.GetLogger(req.Context()).WithError(uploadRes.Error).Error("Failed to PerformUploadKeys")
|
util.GetLogger(req.Context()).WithError(uploadRes.Error).Error("Failed to PerformUploadKeys")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(uploadRes.KeyErrors) > 0 {
|
if len(uploadRes.KeyErrors) > 0 {
|
||||||
util.GetLogger(req.Context()).WithField("key_errors", uploadRes.KeyErrors).Error("Failed to upload one or more keys")
|
util.GetLogger(req.Context()).WithField("key_errors", uploadRes.KeyErrors).Error("Failed to upload one or more keys")
|
||||||
|
|
@ -112,14 +115,12 @@ func QueryKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *api.Device) u
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
queryRes := api.QueryKeysResponse{}
|
queryRes := api.QueryKeysResponse{}
|
||||||
if err := keyAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{
|
keyAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{
|
||||||
UserID: device.UserID,
|
UserID: device.UserID,
|
||||||
UserToDevices: r.DeviceKeys,
|
UserToDevices: r.DeviceKeys,
|
||||||
Timeout: r.GetTimeout(),
|
Timeout: r.GetTimeout(),
|
||||||
// TODO: Token?
|
// TODO: Token?
|
||||||
}, &queryRes); err != nil {
|
}, &queryRes)
|
||||||
return util.ErrorResponse(err)
|
|
||||||
}
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
JSON: map[string]interface{}{
|
JSON: map[string]interface{}{
|
||||||
|
|
@ -152,15 +153,16 @@ func ClaimKeys(req *http.Request, keyAPI api.ClientKeyAPI) util.JSONResponse {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
claimRes := api.PerformClaimKeysResponse{}
|
claimRes := api.PerformClaimKeysResponse{}
|
||||||
if err := keyAPI.PerformClaimKeys(req.Context(), &api.PerformClaimKeysRequest{
|
keyAPI.PerformClaimKeys(req.Context(), &api.PerformClaimKeysRequest{
|
||||||
OneTimeKeys: r.OneTimeKeys,
|
OneTimeKeys: r.OneTimeKeys,
|
||||||
Timeout: r.GetTimeout(),
|
Timeout: r.GetTimeout(),
|
||||||
}, &claimRes); err != nil {
|
}, &claimRes)
|
||||||
return jsonerror.InternalAPIError(req.Context(), err)
|
|
||||||
}
|
|
||||||
if claimRes.Error != nil {
|
if claimRes.Error != nil {
|
||||||
util.GetLogger(req.Context()).WithError(claimRes.Error).Error("failed to PerformClaimKeys")
|
util.GetLogger(req.Context()).WithError(claimRes.Error).Error("failed to PerformClaimKeys")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,10 +29,18 @@ func LeaveRoomByID(
|
||||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
roomID string,
|
roomID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.Unknown("device userID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare to ask the roomserver to perform the room join.
|
// Prepare to ask the roomserver to perform the room join.
|
||||||
leaveReq := roomserverAPI.PerformLeaveRequest{
|
leaveReq := roomserverAPI.PerformLeaveRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: device.UserID,
|
Leaver: *userID,
|
||||||
}
|
}
|
||||||
leaveRes := roomserverAPI.PerformLeaveResponse{}
|
leaveRes := roomserverAPI.PerformLeaveResponse{}
|
||||||
|
|
||||||
|
|
@ -41,12 +49,12 @@ func LeaveRoomByID(
|
||||||
if leaveRes.Code != 0 {
|
if leaveRes.Code != 0 {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: leaveRes.Code,
|
Code: leaveRes.Code,
|
||||||
JSON: jsonerror.LeaveServerNoticeError(),
|
JSON: spec.LeaveServerNoticeError(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(err.Error()),
|
JSON: spec.Unknown(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ func Login(
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusMethodNotAllowed,
|
Code: http.StatusMethodNotAllowed,
|
||||||
JSON: jsonerror.NotFound("Bad method"),
|
JSON: spec.NotFound("Bad method"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,13 +83,19 @@ func completeAuth(
|
||||||
token, err := auth.GenerateAccessToken()
|
token, err := auth.GenerateAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("auth.GenerateAccessToken failed")
|
util.GetLogger(ctx).WithError(err).Error("auth.GenerateAccessToken failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localpart, serverName, err := userutil.ParseUsernameParam(login.Username(), cfg)
|
localpart, serverName, err := userutil.ParseUsernameParam(login.Username(), cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("auth.ParseUsernameParam failed")
|
util.GetLogger(ctx).WithError(err).Error("auth.ParseUsernameParam failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var performRes userapi.PerformDeviceCreationResponse
|
var performRes userapi.PerformDeviceCreationResponse
|
||||||
|
|
@ -105,7 +111,7 @@ func completeAuth(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
JSON: spec.Unknown("failed to create device: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,7 +33,10 @@ func Logout(
|
||||||
}, &performRes)
|
}, &performRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -53,7 +56,10 @@ func LogoutAll(
|
||||||
}, &performRes)
|
}, &performRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,15 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
@ -35,6 +32,9 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -52,11 +52,33 @@ func SendBan(
|
||||||
if body.UserID == "" {
|
if body.UserID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing user_id"),
|
JSON: spec.BadJSON("missing user_id"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to ban this user, bad userID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("RoomID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to ban this user, unknown senderID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errRes := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
|
|
@ -65,11 +87,11 @@ func SendBan(
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
allowedToBan := pl.UserLevel(device.UserID) >= pl.Ban
|
allowedToBan := pl.UserLevel(senderID) >= pl.Ban
|
||||||
if !allowedToBan {
|
if !allowedToBan {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("You don't have permission to ban this user, power level too low."),
|
JSON: spec.Forbidden("You don't have permission to ban this user, power level too low."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,7 +108,10 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName := device.UserDomain()
|
serverName := device.UserDomain()
|
||||||
|
|
@ -101,7 +126,10 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic
|
||||||
false,
|
false,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -122,11 +150,33 @@ func SendKick(
|
||||||
if body.UserID == "" {
|
if body.UserID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing user_id"),
|
JSON: spec.BadJSON("missing user_id"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to kick this user, bad userID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("RoomID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to kick this user, unknown senderID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errRes := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
|
|
@ -135,18 +185,25 @@ func SendKick(
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
allowedToKick := pl.UserLevel(device.UserID) >= pl.Kick
|
allowedToKick := pl.UserLevel(senderID) >= pl.Kick
|
||||||
if !allowedToKick {
|
if !allowedToKick {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("You don't have permission to kick this user, power level too low."),
|
JSON: spec.Forbidden("You don't have permission to kick this user, power level too low."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bodyUserID, err := spec.NewUserID(body.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("body userID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
||||||
err := rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: body.UserID,
|
UserID: *bodyUserID,
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
|
|
@ -155,7 +212,7 @@ func SendKick(
|
||||||
if queryRes.Membership != spec.Join && queryRes.Membership != spec.Invite {
|
if queryRes.Membership != spec.Join && queryRes.Membership != spec.Invite {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Unknown("cannot /kick banned or left users"),
|
JSON: spec.Unknown("cannot /kick banned or left users"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: should we be using SendLeave instead?
|
// TODO: should we be using SendLeave instead?
|
||||||
|
|
@ -174,19 +231,34 @@ func SendUnban(
|
||||||
if body.UserID == "" {
|
if body.UserID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing user_id"),
|
JSON: spec.BadJSON("missing user_id"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to kick this user, bad userID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errRes := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bodyUserID, err := spec.NewUserID(body.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("body userID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
||||||
err := rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: body.UserID,
|
UserID: *bodyUserID,
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
|
|
@ -196,7 +268,7 @@ func SendUnban(
|
||||||
if queryRes.Membership != spec.Ban {
|
if queryRes.Membership != spec.Ban {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown("can only /unban users that are banned"),
|
JSON: spec.Unknown("can only /unban users that are banned"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: should we be using SendLeave instead?
|
// TODO: should we be using SendLeave instead?
|
||||||
|
|
@ -233,11 +305,19 @@ func SendInvite(
|
||||||
if body.UserID == "" {
|
if body.UserID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("missing user_id"),
|
JSON: spec.BadJSON("missing user_id"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errRes := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to kick this user, bad userID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errRes := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +343,10 @@ func sendInvite(
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||||
return jsonerror.InternalServerError(), err
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
||||||
|
|
@ -277,12 +360,12 @@ func sendInvite(
|
||||||
case roomserverAPI.ErrInvalidID:
|
case roomserverAPI.ErrInvalidID:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(e.Error()),
|
JSON: spec.Unknown(e.Error()),
|
||||||
}, e
|
}, e
|
||||||
case roomserverAPI.ErrNotAllowed:
|
case roomserverAPI.ErrNotAllowed:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(e.Error()),
|
JSON: spec.Forbidden(e.Error()),
|
||||||
}, e
|
}, e
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
|
|
@ -290,7 +373,7 @@ func sendInvite(
|
||||||
sentry.CaptureException(err)
|
sentry.CaptureException(err)
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.InternalServerError(),
|
JSON: spec.InternalServerError{},
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,6 +383,42 @@ func sendInvite(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildMembershipEventDirect(
|
||||||
|
ctx context.Context,
|
||||||
|
targetSenderID spec.SenderID, reason string, userDisplayName, userAvatarURL string,
|
||||||
|
sender spec.SenderID, senderDomain spec.ServerName,
|
||||||
|
membership, roomID string, isDirect bool,
|
||||||
|
keyID gomatrixserverlib.KeyID, privateKey ed25519.PrivateKey, evTime time.Time,
|
||||||
|
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
|
) (*types.HeaderedEvent, error) {
|
||||||
|
targetSenderString := string(targetSenderID)
|
||||||
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
|
SenderID: string(sender),
|
||||||
|
RoomID: roomID,
|
||||||
|
Type: "m.room.member",
|
||||||
|
StateKey: &targetSenderString,
|
||||||
|
}
|
||||||
|
|
||||||
|
content := gomatrixserverlib.MemberContent{
|
||||||
|
Membership: membership,
|
||||||
|
DisplayName: userDisplayName,
|
||||||
|
AvatarURL: userAvatarURL,
|
||||||
|
Reason: reason,
|
||||||
|
IsDirect: isDirect,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := proto.SetContent(content); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
identity := &fclient.SigningIdentity{
|
||||||
|
ServerName: senderDomain,
|
||||||
|
KeyID: keyID,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
}
|
||||||
|
return eventutil.QueryAndBuildEvent(ctx, &proto, identity, evTime, rsAPI, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func buildMembershipEvent(
|
func buildMembershipEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
targetUserID, reason string, profileAPI userapi.ClientUserAPI,
|
targetUserID, reason string, profileAPI userapi.ClientUserAPI,
|
||||||
|
|
@ -313,31 +432,35 @@ func buildMembershipEvent(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
Sender: device.UserID,
|
if err != nil {
|
||||||
RoomID: roomID,
|
|
||||||
Type: "m.room.member",
|
|
||||||
StateKey: &targetUserID,
|
|
||||||
}
|
|
||||||
|
|
||||||
content := gomatrixserverlib.MemberContent{
|
|
||||||
Membership: membership,
|
|
||||||
DisplayName: profile.DisplayName,
|
|
||||||
AvatarURL: profile.AvatarURL,
|
|
||||||
Reason: reason,
|
|
||||||
IsDirect: isDirect,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = proto.SetContent(content); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return eventutil.QueryAndBuildEvent(ctx, &proto, cfg.Matrix, identity, evTime, rsAPI, nil)
|
targetID, err := spec.NewUserID(targetUserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
targetSenderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *targetID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildMembershipEventDirect(ctx, targetSenderID, reason, profile.DisplayName, profile.AvatarURL,
|
||||||
|
senderID, device.UserDomain(), membership, roomID, isDirect, identity.KeyID, identity.PrivateKey, evTime, rsAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadProfile lookups the profile of a given user from the database and returns
|
// loadProfile lookups the profile of a given user from the database and returns
|
||||||
|
|
@ -377,7 +500,7 @@ func extractRequestData(req *http.Request) (body *threepid.MembershipRequest, ev
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resErr = &util.JSONResponse{
|
resErr = &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -399,36 +522,43 @@ func checkAndProcessThreepid(
|
||||||
req.Context(), device, body, cfg, rsAPI, profileAPI,
|
req.Context(), device, body, cfg, rsAPI, profileAPI,
|
||||||
roomID, evTime,
|
roomID, evTime,
|
||||||
)
|
)
|
||||||
if err == threepid.ErrMissingParameter {
|
switch e := err.(type) {
|
||||||
|
case nil:
|
||||||
|
case threepid.ErrMissingParameter:
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||||
return inviteStored, &util.JSONResponse{
|
return inviteStored, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(err.Error()),
|
JSON: spec.BadJSON(err.Error()),
|
||||||
}
|
}
|
||||||
} else if err == threepid.ErrNotTrusted {
|
case threepid.ErrNotTrusted:
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||||
return inviteStored, &util.JSONResponse{
|
return inviteStored, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.NotTrusted(body.IDServer),
|
JSON: spec.NotTrusted(body.IDServer),
|
||||||
}
|
}
|
||||||
} else if err == eventutil.ErrRoomNoExists {
|
case eventutil.ErrRoomNoExists:
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||||
return inviteStored, &util.JSONResponse{
|
return inviteStored, &util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound(err.Error()),
|
JSON: spec.NotFound(err.Error()),
|
||||||
}
|
}
|
||||||
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
|
case gomatrixserverlib.BadJSONError:
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||||
return inviteStored, &util.JSONResponse{
|
return inviteStored, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: spec.BadJSON(e.Error()),
|
||||||
}
|
}
|
||||||
}
|
default:
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||||
er := jsonerror.InternalServerError()
|
return inviteStored, &util.JSONResponse{
|
||||||
return inviteStored, &er
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkMemberInRoom(ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, userID, roomID string) *util.JSONResponse {
|
func checkMemberInRoom(ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, userID spec.UserID, roomID string) *util.JSONResponse {
|
||||||
var membershipRes roomserverAPI.QueryMembershipForUserResponse
|
var membershipRes roomserverAPI.QueryMembershipForUserResponse
|
||||||
err := rsAPI.QueryMembershipForUser(ctx, &roomserverAPI.QueryMembershipForUserRequest{
|
err := rsAPI.QueryMembershipForUser(ctx, &roomserverAPI.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
|
|
@ -436,13 +566,15 @@ func checkMemberInRoom(ctx context.Context, rsAPI roomserverAPI.ClientRoomserver
|
||||||
}, &membershipRes)
|
}, &membershipRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("QueryMembershipForUser: could not query membership for user")
|
util.GetLogger(ctx).WithError(err).Error("QueryMembershipForUser: could not query membership for user")
|
||||||
e := jsonerror.InternalServerError()
|
return &util.JSONResponse{
|
||||||
return &e
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !membershipRes.IsInRoom {
|
if !membershipRes.IsInRoom {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("user does not belong to room"),
|
JSON: spec.Forbidden("user does not belong to room"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -454,26 +586,38 @@ func SendForget(
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
logger := util.GetLogger(ctx).WithField("roomID", roomID).WithField("userID", device.UserID)
|
logger := util.GetLogger(ctx).WithField("roomID", roomID).WithField("userID", device.UserID)
|
||||||
|
|
||||||
|
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("You don't have permission to kick this user, bad userID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var membershipRes roomserverAPI.QueryMembershipForUserResponse
|
var membershipRes roomserverAPI.QueryMembershipForUserResponse
|
||||||
membershipReq := roomserverAPI.QueryMembershipForUserRequest{
|
membershipReq := roomserverAPI.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: device.UserID,
|
UserID: *deviceUserID,
|
||||||
}
|
}
|
||||||
err := rsAPI.QueryMembershipForUser(ctx, &membershipReq, &membershipRes)
|
err = rsAPI.QueryMembershipForUser(ctx, &membershipReq, &membershipRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("QueryMembershipForUser: could not query membership for user")
|
logger.WithError(err).Error("QueryMembershipForUser: could not query membership for user")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !membershipRes.RoomExists {
|
if !membershipRes.RoomExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("room does not exist"),
|
JSON: spec.Forbidden("room does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if membershipRes.IsInRoom {
|
if membershipRes.IsInRoom {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(fmt.Sprintf("User %s is in room %s", device.UserID, roomID)),
|
JSON: spec.Unknown(fmt.Sprintf("User %s is in room %s", device.UserID, roomID)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,7 +628,10 @@ func SendForget(
|
||||||
response := roomserverAPI.PerformForgetResponse{}
|
response := roomserverAPI.PerformForgetResponse{}
|
||||||
if err := rsAPI.PerformForget(ctx, &request, &response); err != nil {
|
if err := rsAPI.PerformForget(ctx, &request, &response); err != nil {
|
||||||
logger.WithError(err).Error("PerformForget: unable to forget room")
|
logger.WithError(err).Error("PerformForget: unable to forget room")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -500,14 +647,14 @@ func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
if plEvent == nil {
|
if plEvent == nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("You don't have permission to perform this action, no power_levels event in this room."),
|
JSON: spec.Forbidden("You don't have permission to perform this action, no power_levels event in this room."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pl, err := plEvent.PowerLevels()
|
pl, err := plEvent.PowerLevels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
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."),
|
JSON: spec.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
|
return pl, nil
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -35,7 +35,10 @@ func GetNotifications(
|
||||||
limit, err = strconv.ParseInt(limitStr, 10, 64)
|
limit, err = strconv.ParseInt(limitStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("ParseInt(limit) failed")
|
util.GetLogger(req.Context()).WithError(err).Error("ParseInt(limit) failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +46,10 @@ func GetNotifications(
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = userAPI.QueryNotifications(req.Context(), &userapi.QueryNotificationsRequest{
|
err = userAPI.QueryNotifications(req.Context(), &userapi.QueryNotificationsRequest{
|
||||||
Localpart: localpart,
|
Localpart: localpart,
|
||||||
|
|
@ -54,7 +60,10 @@ func GetNotifications(
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryNotifications failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryNotifications failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
util.GetLogger(req.Context()).WithField("from", req.URL.Query().Get("from")).WithField("limit", limit).WithField("only", req.URL.Query().Get("only")).WithField("next", queryRes.NextToken).Infof("QueryNotifications: len %d", len(queryRes.Notifications))
|
util.GetLogger(req.Context()).WithField("from", req.URL.Query().Get("from")).WithField("limit", limit).WithField("only", req.URL.Query().Get("only")).WithField("next", queryRes.NextToken).Infof("QueryNotifications: len %d", len(queryRes.Notifications))
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ func CreateOpenIDToken(
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Cannot request tokens for other users"),
|
JSON: spec.Forbidden("Cannot request tokens for other users"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +55,10 @@ func CreateOpenIDToken(
|
||||||
err := userAPI.PerformOpenIDTokenCreation(req.Context(), &request, &response)
|
err := userAPI.PerformOpenIDTokenCreation(req.Context(), &request, &response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("userAPI.CreateOpenIDToken failed")
|
util.GetLogger(req.Context()).WithError(err).Error("userAPI.CreateOpenIDToken failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
@ -90,7 +90,10 @@ func Password(
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the user API to perform the password change.
|
// Ask the user API to perform the password change.
|
||||||
|
|
@ -102,11 +105,17 @@ func Password(
|
||||||
passwordRes := &api.PerformPasswordUpdateResponse{}
|
passwordRes := &api.PerformPasswordUpdateResponse{}
|
||||||
if err := userAPI.PerformPasswordUpdate(req.Context(), passwordReq, passwordRes); err != nil {
|
if err := userAPI.PerformPasswordUpdate(req.Context(), passwordReq, passwordRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformPasswordUpdate failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformPasswordUpdate failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !passwordRes.PasswordUpdated {
|
if !passwordRes.PasswordUpdated {
|
||||||
util.GetLogger(req.Context()).Error("Expected password to have been updated but wasn't")
|
util.GetLogger(req.Context()).Error("Expected password to have been updated but wasn't")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request asks us to log out all other devices then
|
// If the request asks us to log out all other devices then
|
||||||
|
|
@ -120,7 +129,10 @@ func Password(
|
||||||
logoutRes := &api.PerformDeviceDeletionResponse{}
|
logoutRes := &api.PerformDeviceDeletionResponse{}
|
||||||
if err := userAPI.PerformDeviceDeletion(req.Context(), logoutReq, logoutRes); err != nil {
|
if err := userAPI.PerformDeviceDeletion(req.Context(), logoutReq, logoutRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushersReq := &api.PerformPusherDeletionRequest{
|
pushersReq := &api.PerformPusherDeletionRequest{
|
||||||
|
|
@ -130,7 +142,10 @@ func Password(
|
||||||
}
|
}
|
||||||
if err := userAPI.PerformPusherDeletion(req.Context(), pushersReq, &struct{}{}); err != nil {
|
if err := userAPI.PerformPusherDeletion(req.Context(), pushersReq, &struct{}{}); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformPusherDeletion failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformPusherDeletion failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
|
|
@ -61,12 +60,12 @@ func PeekRoomByIDOrAlias(
|
||||||
case roomserverAPI.ErrInvalidID:
|
case roomserverAPI.ErrInvalidID:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(e.Error()),
|
JSON: spec.Unknown(e.Error()),
|
||||||
}
|
}
|
||||||
case roomserverAPI.ErrNotAllowed:
|
case roomserverAPI.ErrNotAllowed:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(e.Error()),
|
JSON: spec.Forbidden(e.Error()),
|
||||||
}
|
}
|
||||||
case *gomatrix.HTTPError:
|
case *gomatrix.HTTPError:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -76,7 +75,10 @@ func PeekRoomByIDOrAlias(
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
logrus.WithError(err).WithField("roomID", roomIDOrAlias).Errorf("Failed to peek room")
|
logrus.WithError(err).WithField("roomID", roomIDOrAlias).Errorf("Failed to peek room")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this user is already joined to the room, we let them peek anyway
|
// if this user is already joined to the room, we let them peek anyway
|
||||||
|
|
@ -107,12 +109,15 @@ func UnpeekRoomByID(
|
||||||
case roomserverAPI.ErrInvalidID:
|
case roomserverAPI.ErrInvalidID:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(e.Error()),
|
JSON: spec.Unknown(e.Error()),
|
||||||
}
|
}
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
logrus.WithError(err).WithField("roomID", roomID).Errorf("Failed to un-peek room")
|
logrus.WithError(err).WithField("roomID", roomID).Errorf("Failed to un-peek room")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
|
@ -54,7 +53,7 @@ func SetPresence(
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Unable to set presence for other user."),
|
JSON: spec.Forbidden("Unable to set presence for other user."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var presence presenceReq
|
var presence presenceReq
|
||||||
|
|
@ -67,7 +66,7 @@ func SetPresence(
|
||||||
if !ok {
|
if !ok {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.Unknown(fmt.Sprintf("Unknown presence '%s'.", presence.Presence)),
|
JSON: spec.Unknown(fmt.Sprintf("Unknown presence '%s'.", presence.Presence)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := producer.SendPresence(req.Context(), userID, presenceStatus, presence.StatusMsg)
|
err := producer.SendPresence(req.Context(), userID, presenceStatus, presence.StatusMsg)
|
||||||
|
|
@ -75,7 +74,7 @@ func SetPresence(
|
||||||
log.WithError(err).Errorf("failed to update presence")
|
log.WithError(err).Errorf("failed to update presence")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.InternalServerError(),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +99,7 @@ func GetPresence(
|
||||||
log.WithError(err).Errorf("unable to get presence")
|
log.WithError(err).Errorf("unable to get presence")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.InternalServerError(),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +118,7 @@ func GetPresence(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.InternalServerError(),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,11 @@ import (
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -49,12 +47,15 @@ func GetProfile(
|
||||||
if err == appserviceAPI.ErrProfileNotExists {
|
if err == appserviceAPI.ErrProfileNotExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("The user does not exist or does not have a profile"),
|
JSON: spec.NotFound("The user does not exist or does not have a profile"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
util.GetLogger(req.Context()).WithError(err).Error("getProfile failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -95,7 +96,7 @@ func SetAvatarURL(
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
JSON: spec.Forbidden("userID does not match the current user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,23 +104,20 @@ func SetAvatarURL(
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
if r.AvatarURL == "" {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.BadJSON("'avatar_url' must be supplied."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Matrix.IsLocalServerName(domain) {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not belong to a locally configured domain"),
|
JSON: spec.Forbidden("userID does not belong to a locally configured domain"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,14 +125,17 @@ func SetAvatarURL(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profile, changed, err := profileAPI.SetAvatarURL(req.Context(), localpart, domain, r.AvatarURL)
|
profile, changed, err := profileAPI.SetAvatarURL(req.Context(), localpart, domain, r.AvatarURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
|
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// No need to build new membership events, since nothing changed
|
// No need to build new membership events, since nothing changed
|
||||||
if !changed {
|
if !changed {
|
||||||
|
|
@ -144,7 +145,7 @@ func SetAvatarURL(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := updateProfile(req.Context(), rsAPI, device, profile, userID, cfg, evTime)
|
response, err := updateProfile(req.Context(), rsAPI, device, profile, userID, evTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +185,7 @@ func SetDisplayName(
|
||||||
if userID != device.UserID {
|
if userID != device.UserID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
JSON: spec.Forbidden("userID does not match the current user"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,23 +193,20 @@ func SetDisplayName(
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
if r.DisplayName == "" {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.BadJSON("'displayname' must be supplied."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Matrix.IsLocalServerName(domain) {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("userID does not belong to a locally configured domain"),
|
JSON: spec.Forbidden("userID does not belong to a locally configured domain"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,14 +214,17 @@ func SetDisplayName(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profile, changed, err := profileAPI.SetDisplayName(req.Context(), localpart, domain, r.DisplayName)
|
profile, changed, err := profileAPI.SetDisplayName(req.Context(), localpart, domain, r.DisplayName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
|
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// No need to build new membership events, since nothing changed
|
// No need to build new membership events, since nothing changed
|
||||||
if !changed {
|
if !changed {
|
||||||
|
|
@ -233,7 +234,7 @@ func SetDisplayName(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := updateProfile(req.Context(), rsAPI, device, profile, userID, cfg, evTime)
|
response, err := updateProfile(req.Context(), rsAPI, device, profile, userID, evTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +248,7 @@ func SetDisplayName(
|
||||||
func updateProfile(
|
func updateProfile(
|
||||||
ctx context.Context, rsAPI api.ClientRoomserverAPI, device *userapi.Device,
|
ctx context.Context, rsAPI api.ClientRoomserverAPI, device *userapi.Device,
|
||||||
profile *authtypes.Profile,
|
profile *authtypes.Profile,
|
||||||
userID string, cfg *config.ClientAPI, evTime time.Time,
|
userID string, evTime time.Time,
|
||||||
) (util.JSONResponse, error) {
|
) (util.JSONResponse, error) {
|
||||||
var res api.QueryRoomsForUserResponse
|
var res api.QueryRoomsForUserResponse
|
||||||
err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||||
|
|
@ -256,33 +257,45 @@ func updateProfile(
|
||||||
}, &res)
|
}, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("QueryRoomsForUser failed")
|
util.GetLogger(ctx).WithError(err).Error("QueryRoomsForUser failed")
|
||||||
return jsonerror.InternalServerError(), err
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError(), err
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := buildMembershipEvents(
|
events, err := buildMembershipEvents(
|
||||||
ctx, device, res.RoomIDs, *profile, userID, cfg, evTime, rsAPI,
|
ctx, res.RoomIDs, *profile, userID, evTime, rsAPI,
|
||||||
)
|
)
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
case gomatrixserverlib.BadJSONError:
|
case gomatrixserverlib.BadJSONError:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: spec.BadJSON(e.Error()),
|
||||||
}, e
|
}, e
|
||||||
default:
|
default:
|
||||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvents failed")
|
||||||
return jsonerror.InternalServerError(), e
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}, e
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, events, device.UserDomain(), domain, domain, nil, true); err != nil {
|
if err := api.SendEvents(ctx, rsAPI, api.KindNew, events, device.UserDomain(), domain, domain, nil, true); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
||||||
return jsonerror.InternalServerError(), err
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}, err
|
||||||
}
|
}
|
||||||
return util.JSONResponse{}, nil
|
return util.JSONResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -331,19 +344,31 @@ func getProfile(
|
||||||
|
|
||||||
func buildMembershipEvents(
|
func buildMembershipEvents(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
device *userapi.Device,
|
|
||||||
roomIDs []string,
|
roomIDs []string,
|
||||||
newProfile authtypes.Profile, userID string, cfg *config.ClientAPI,
|
newProfile authtypes.Profile, userID string,
|
||||||
evTime time.Time, rsAPI api.ClientRoomserverAPI,
|
evTime time.Time, rsAPI api.ClientRoomserverAPI,
|
||||||
) ([]*types.HeaderedEvent, error) {
|
) ([]*types.HeaderedEvent, error) {
|
||||||
evs := []*types.HeaderedEvent{}
|
evs := []*types.HeaderedEvent{}
|
||||||
|
|
||||||
|
fullUserID, err := spec.NewUserID(userID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for _, roomID := range roomIDs {
|
for _, roomID := range roomIDs {
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
senderIDString := string(senderID)
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
Sender: userID,
|
SenderID: senderIDString,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
StateKey: &userID,
|
StateKey: &senderIDString,
|
||||||
}
|
}
|
||||||
|
|
||||||
content := gomatrixserverlib.MemberContent{
|
content := gomatrixserverlib.MemberContent{
|
||||||
|
|
@ -353,16 +378,21 @@ func buildMembershipEvents(
|
||||||
content.DisplayName = newProfile.DisplayName
|
content.DisplayName = newProfile.DisplayName
|
||||||
content.AvatarURL = newProfile.AvatarURL
|
content.AvatarURL = newProfile.AvatarURL
|
||||||
|
|
||||||
if err := proto.SetContent(content); err != nil {
|
if err = proto.SetContent(content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
user, err := spec.NewUserID(userID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, cfg.Matrix, identity, evTime, rsAPI, nil)
|
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
event, err := eventutil.QueryAndBuildEvent(ctx, &proto, &identity, evTime, rsAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,7 +34,10 @@ func GetPushers(
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = userAPI.QueryPushers(req.Context(), &userapi.QueryPushersRequest{
|
err = userAPI.QueryPushers(req.Context(), &userapi.QueryPushersRequest{
|
||||||
Localpart: localpart,
|
Localpart: localpart,
|
||||||
|
|
@ -42,7 +45,10 @@ func GetPushers(
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryPushers failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryPushers failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for i := range queryRes.Pushers {
|
for i := range queryRes.Pushers {
|
||||||
queryRes.Pushers[i].SessionID = 0
|
queryRes.Pushers[i].SessionID = 0
|
||||||
|
|
@ -63,7 +69,10 @@ func SetPusher(
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body := userapi.PerformPusherSetRequest{}
|
body := userapi.PerformPusherSetRequest{}
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &body); resErr != nil {
|
if resErr := httputil.UnmarshalJSONRequest(req, &body); resErr != nil {
|
||||||
|
|
@ -99,7 +108,10 @@ func SetPusher(
|
||||||
err = userAPI.PerformPusherSet(req.Context(), &body, &struct{}{})
|
err = userAPI.PerformPusherSet(req.Context(), &body, &struct{}{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("PerformPusherSet failed")
|
util.GetLogger(req.Context()).WithError(err).Error("PerformPusherSet failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -111,6 +123,6 @@ func SetPusher(
|
||||||
func invalidParam(msg string) util.JSONResponse {
|
func invalidParam(msg string) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam(msg),
|
JSON: spec.InvalidParam(msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,30 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func errorResponse(ctx context.Context, err error, msg string, args ...interface{}) util.JSONResponse {
|
func errorResponse(ctx context.Context, err error, msg string, args ...interface{}) util.JSONResponse {
|
||||||
if eerr, ok := err.(*jsonerror.MatrixError); ok {
|
if eerr, ok := err.(spec.MatrixError); ok {
|
||||||
var status int
|
var status int
|
||||||
switch eerr.ErrCode {
|
switch eerr.ErrCode {
|
||||||
case "M_INVALID_ARGUMENT_VALUE":
|
case spec.ErrorInvalidParam:
|
||||||
status = http.StatusBadRequest
|
status = http.StatusBadRequest
|
||||||
case "M_NOT_FOUND":
|
case spec.ErrorNotFound:
|
||||||
status = http.StatusNotFound
|
status = http.StatusNotFound
|
||||||
default:
|
default:
|
||||||
status = http.StatusInternalServerError
|
status = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
return util.MatrixErrorResponse(status, eerr.ErrCode, eerr.Err)
|
return util.MatrixErrorResponse(status, string(eerr.ErrCode), eerr.Err)
|
||||||
}
|
}
|
||||||
util.GetLogger(ctx).WithError(err).Errorf(msg, args...)
|
util.GetLogger(ctx).WithError(err).Errorf(msg, args...)
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllPushRules(ctx context.Context, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
func GetAllPushRules(ctx context.Context, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
|
||||||
|
|
@ -48,7 +51,7 @@ func GetPushRulesByScope(ctx context.Context, scope string, device *userapi.Devi
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -63,12 +66,12 @@ func GetPushRulesByKind(ctx context.Context, scope, kind string, device *userapi
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
||||||
// Even if rulesPtr is not nil, there may not be any rules for this kind
|
// Even if rulesPtr is not nil, there may not be any rules for this kind
|
||||||
if rulesPtr == nil || (rulesPtr != nil && len(*rulesPtr) == 0) {
|
if rulesPtr == nil || (rulesPtr != nil && len(*rulesPtr) == 0) {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -83,15 +86,15 @@ func GetPushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
||||||
if rulesPtr == nil {
|
if rulesPtr == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||||
}
|
}
|
||||||
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
return errorResponse(ctx, spec.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -104,14 +107,14 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
|
||||||
if err := json.NewDecoder(body).Decode(&newRule); err != nil {
|
if err := json.NewDecoder(body).Decode(&newRule); err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(err.Error()),
|
JSON: spec.BadJSON(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newRule.RuleID = ruleID
|
newRule.RuleID = ruleID
|
||||||
|
|
||||||
errs := pushrules.ValidateRule(pushrules.Kind(kind), &newRule)
|
errs := pushrules.ValidateRule(pushrules.Kind(kind), &newRule)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue(errs[0].Error()), "rule sanity check failed: %v", errs)
|
return errorResponse(ctx, spec.InvalidParam(errs[0].Error()), "rule sanity check failed: %v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
|
ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
|
||||||
|
|
@ -120,12 +123,12 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
||||||
if rulesPtr == nil {
|
if rulesPtr == nil {
|
||||||
// while this should be impossible (ValidateRule would already return an error), better keep it around
|
// while this should be impossible (ValidateRule would already return an error), better keep it around
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||||
}
|
}
|
||||||
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
||||||
if i >= 0 && afterRuleID == "" && beforeRuleID == "" {
|
if i >= 0 && afterRuleID == "" && beforeRuleID == "" {
|
||||||
|
|
@ -172,15 +175,15 @@ func DeletePushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, dev
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
||||||
if rulesPtr == nil {
|
if rulesPtr == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||||
}
|
}
|
||||||
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
return errorResponse(ctx, spec.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
*rulesPtr = append((*rulesPtr)[:i], (*rulesPtr)[i+1:]...)
|
*rulesPtr = append((*rulesPtr)[:i], (*rulesPtr)[i+1:]...)
|
||||||
|
|
@ -203,15 +206,15 @@ func GetPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
||||||
if rulesPtr == nil {
|
if rulesPtr == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||||
}
|
}
|
||||||
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
return errorResponse(ctx, spec.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
|
|
@ -226,7 +229,7 @@ func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
|
||||||
if err := json.NewDecoder(body).Decode(&newPartialRule); err != nil {
|
if err := json.NewDecoder(body).Decode(&newPartialRule); err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(err.Error()),
|
JSON: spec.BadJSON(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if newPartialRule.Actions == nil {
|
if newPartialRule.Actions == nil {
|
||||||
|
|
@ -249,15 +252,15 @@ func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
|
||||||
}
|
}
|
||||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||||
if ruleSet == nil {
|
if ruleSet == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rule set"), "pushRuleSetByScope failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rule set"), "pushRuleSetByScope failed")
|
||||||
}
|
}
|
||||||
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
rulesPtr := pushRuleSetKindPointer(ruleSet, pushrules.Kind(kind))
|
||||||
if rulesPtr == nil {
|
if rulesPtr == nil {
|
||||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||||
}
|
}
|
||||||
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return errorResponse(ctx, jsonerror.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
return errorResponse(ctx, spec.NotFound("push rule ID not found"), "pushRuleIndexByID failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(attrGet((*rulesPtr)[i]), attrGet(&newPartialRule)) {
|
if !reflect.DeepEqual(attrGet((*rulesPtr)[i]), attrGet(&newPartialRule)) {
|
||||||
|
|
@ -313,7 +316,7 @@ func pushRuleAttrGetter(attr string) (func(*pushrules.Rule) interface{}, error)
|
||||||
case "enabled":
|
case "enabled":
|
||||||
return func(rule *pushrules.Rule) interface{} { return rule.Enabled }, nil
|
return func(rule *pushrules.Rule) interface{} { return rule.Enabled }, nil
|
||||||
default:
|
default:
|
||||||
return nil, jsonerror.InvalidArgumentValue("invalid push rule attribute")
|
return nil, spec.InvalidParam("invalid push rule attribute")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,7 +327,7 @@ func pushRuleAttrSetter(attr string) (func(dest, src *pushrules.Rule), error) {
|
||||||
case "enabled":
|
case "enabled":
|
||||||
return func(dest, src *pushrules.Rule) { dest.Enabled = src.Enabled }, nil
|
return func(dest, src *pushrules.Rule) { dest.Enabled = src.Enabled }, nil
|
||||||
default:
|
default:
|
||||||
return nil, jsonerror.InvalidArgumentValue("invalid push rule attribute")
|
return nil, spec.InvalidParam("invalid push rule attribute")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,10 +341,10 @@ func findPushRuleInsertionIndex(rules []*pushrules.Rule, afterID, beforeID strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i == len(rules) {
|
if i == len(rules) {
|
||||||
return 0, jsonerror.NotFound("after: rule ID not found")
|
return 0, spec.NotFound("after: rule ID not found")
|
||||||
}
|
}
|
||||||
if rules[i].Default {
|
if rules[i].Default {
|
||||||
return 0, jsonerror.NotFound("after: rule ID must not be a default rule")
|
return 0, spec.NotFound("after: rule ID must not be a default rule")
|
||||||
}
|
}
|
||||||
// We stopped on the "after" match to differentiate
|
// We stopped on the "after" match to differentiate
|
||||||
// not-found from is-last-entry. Now we move to the earliest
|
// not-found from is-last-entry. Now we move to the earliest
|
||||||
|
|
@ -356,10 +359,10 @@ func findPushRuleInsertionIndex(rules []*pushrules.Rule, afterID, beforeID strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i == len(rules) {
|
if i == len(rules) {
|
||||||
return 0, jsonerror.NotFound("before: rule ID not found")
|
return 0, spec.NotFound("before: rule ID not found")
|
||||||
}
|
}
|
||||||
if rules[i].Default {
|
if rules[i].Default {
|
||||||
return 0, jsonerror.NotFound("before: rule ID must not be a default rule")
|
return 0, spec.NotFound("before: rule ID must not be a default rule")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
|
|
@ -49,7 +48,10 @@ func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *prod
|
||||||
case "m.fully_read":
|
case "m.fully_read":
|
||||||
data, err := json.Marshal(fullyReadEvent{EventID: eventID})
|
data, err := json.Marshal(fullyReadEvent{EventID: eventID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dataReq := api.InputAccountDataRequest{
|
dataReq := api.InputAccountDataRequest{
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -24,7 +25,6 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
@ -47,7 +47,29 @@ func SendRedaction(
|
||||||
txnID *string,
|
txnID *string,
|
||||||
txnCache *transactions.Cache,
|
txnCache *transactions.Cache,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
resErr := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
deviceUserID, userIDErr := spec.NewUserID(device.UserID, true)
|
||||||
|
if userIDErr != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("userID doesn't have power level to redact"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("RoomID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
senderID, queryErr := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
|
if queryErr != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("userID doesn't have power level to redact"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -63,20 +85,20 @@ func SendRedaction(
|
||||||
if ev == nil {
|
if ev == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.NotFound("unknown event ID"), // TODO: is it ok to leak existence?
|
JSON: spec.NotFound("unknown event ID"), // TODO: is it ok to leak existence?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ev.RoomID() != roomID {
|
if ev.RoomID() != roomID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.NotFound("cannot redact event in another room"),
|
JSON: spec.NotFound("cannot redact event in another room"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Users may redact their own events, and any user with a power level greater than or equal
|
// "Users may redact their own events, and any user with a power level greater than or equal
|
||||||
// to the redact power level of the room may redact events there"
|
// to the redact power level of the room may redact events there"
|
||||||
// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||||
allowedToRedact := ev.Sender() == device.UserID
|
allowedToRedact := ev.SenderID() == senderID
|
||||||
if !allowedToRedact {
|
if !allowedToRedact {
|
||||||
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||||
EventType: spec.MRoomPowerLevels,
|
EventType: spec.MRoomPowerLevels,
|
||||||
|
|
@ -85,24 +107,24 @@ func SendRedaction(
|
||||||
if plEvent == nil {
|
if plEvent == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 403,
|
Code: 403,
|
||||||
JSON: jsonerror.Forbidden("You don't have permission to redact this event, no power_levels event in this room."),
|
JSON: spec.Forbidden("You don't have permission to redact this event, no power_levels event in this room."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pl, err := plEvent.PowerLevels()
|
pl, plErr := plEvent.PowerLevels()
|
||||||
if err != nil {
|
if plErr != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 403,
|
Code: 403,
|
||||||
JSON: jsonerror.Forbidden(
|
JSON: spec.Forbidden(
|
||||||
"You don't have permission to redact this event, the power_levels event for this room is malformed so auth checks cannot be performed.",
|
"You don't have permission to redact this event, the power_levels event for this room is malformed so auth checks cannot be performed.",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allowedToRedact = pl.UserLevel(device.UserID) >= pl.Redact
|
allowedToRedact = pl.UserLevel(senderID) >= pl.Redact
|
||||||
}
|
}
|
||||||
if !allowedToRedact {
|
if !allowedToRedact {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 403,
|
Code: 403,
|
||||||
JSON: jsonerror.Forbidden("You don't have permission to redact this event, power level too low."),
|
JSON: spec.Forbidden("You don't have permission to redact this event, power level too low."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,34 +136,43 @@ func SendRedaction(
|
||||||
|
|
||||||
// create the new event and set all the fields we can
|
// create the new event and set all the fields we can
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
Sender: device.UserID,
|
SenderID: string(senderID),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: spec.MRoomRedaction,
|
Type: spec.MRoomRedaction,
|
||||||
Redacts: eventID,
|
Redacts: eventID,
|
||||||
}
|
}
|
||||||
err := proto.SetContent(r)
|
err = proto.SetContent(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("proto.SetContent failed")
|
util.GetLogger(req.Context()).WithError(err).Error("proto.SetContent failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
identity, err := rsAPI.SigningIdentityFor(req.Context(), *validRoomID, *deviceUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
||||||
e, err := eventutil.QueryAndBuildEvent(req.Context(), &proto, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
|
e, err := eventutil.QueryAndBuildEvent(req.Context(), &proto, &identity, time.Now(), rsAPI, &queryRes)
|
||||||
if err == eventutil.ErrRoomNoExists {
|
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("Room does not exist"),
|
JSON: spec.NotFound("Room does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
domain := device.UserDomain()
|
domain := device.UserDomain()
|
||||||
if err = roomserverAPI.SendEvents(context.Background(), rsAPI, roomserverAPI.KindNew, []*types.HeaderedEvent{e}, device.UserDomain(), domain, domain, nil, false); err != nil {
|
if err = roomserverAPI.SendEvents(context.Background(), rsAPI, roomserverAPI.KindNew, []*types.HeaderedEvent{e}, device.UserDomain(), domain, domain, nil, false); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Errorf("failed to SendEvents")
|
util.GetLogger(req.Context()).WithError(err).Errorf("failed to SendEvents")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := util.JSONResponse{
|
res := util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
)
|
)
|
||||||
|
|
@ -428,7 +427,7 @@ func validateApplicationService(
|
||||||
if matchedApplicationService == nil {
|
if matchedApplicationService == nil {
|
||||||
return "", &util.JSONResponse{
|
return "", &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.UnknownToken("Supplied access_token does not match any known application service"),
|
JSON: spec.UnknownToken("Supplied access_token does not match any known application service"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,7 +438,7 @@ func validateApplicationService(
|
||||||
// If we didn't find any matches, return M_EXCLUSIVE
|
// If we didn't find any matches, return M_EXCLUSIVE
|
||||||
return "", &util.JSONResponse{
|
return "", &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.ASExclusive(fmt.Sprintf(
|
JSON: spec.ASExclusive(fmt.Sprintf(
|
||||||
"Supplied username %s did not match any namespaces for application service ID: %s", username, matchedApplicationService.ID)),
|
"Supplied username %s did not match any namespaces for application service ID: %s", username, matchedApplicationService.ID)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +447,7 @@ func validateApplicationService(
|
||||||
if UsernameMatchesMultipleExclusiveNamespaces(cfg, userID) {
|
if UsernameMatchesMultipleExclusiveNamespaces(cfg, userID) {
|
||||||
return "", &util.JSONResponse{
|
return "", &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.ASExclusive(fmt.Sprintf(
|
JSON: spec.ASExclusive(fmt.Sprintf(
|
||||||
"Supplied username %s matches multiple exclusive application service namespaces. Only 1 match allowed", username)),
|
"Supplied username %s matches multiple exclusive application service namespaces. Only 1 match allowed", username)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -474,7 +473,7 @@ func Register(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.NotJSON("Unable to read request body"),
|
JSON: spec.NotJSON("Unable to read request body"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -518,7 +517,7 @@ func Register(
|
||||||
if _, err = strconv.ParseInt(r.Username, 10, 64); err == nil {
|
if _, err = strconv.ParseInt(r.Username, 10, 64); err == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidUsername("Numeric user IDs are reserved"),
|
JSON: spec.InvalidUsername("Numeric user IDs are reserved"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Auto generate a numeric username if r.Username is empty
|
// Auto generate a numeric username if r.Username is empty
|
||||||
|
|
@ -529,7 +528,10 @@ func Register(
|
||||||
nres := &userapi.QueryNumericLocalpartResponse{}
|
nres := &userapi.QueryNumericLocalpartResponse{}
|
||||||
if err = userAPI.QueryNumericLocalpart(req.Context(), nreq, nres); err != nil {
|
if err = userAPI.QueryNumericLocalpart(req.Context(), nreq, nres); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryNumericLocalpart failed")
|
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryNumericLocalpart failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
r.Username = strconv.FormatInt(nres.ID, 10)
|
r.Username = strconv.FormatInt(nres.ID, 10)
|
||||||
}
|
}
|
||||||
|
|
@ -552,7 +554,7 @@ func Register(
|
||||||
// type is not known or specified)
|
// type is not known or specified)
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("A known registration type (e.g. m.login.application_service) must be specified if an access_token is provided"),
|
JSON: spec.MissingParam("A known registration type (e.g. m.login.application_service) must be specified if an access_token is provided"),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Spec-compliant case (neither the access_token nor the login type are
|
// Spec-compliant case (neither the access_token nor the login type are
|
||||||
|
|
@ -590,7 +592,7 @@ func handleGuestRegistration(
|
||||||
if !registrationEnabled || !guestsEnabled {
|
if !registrationEnabled || !guestsEnabled {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(
|
JSON: spec.Forbidden(
|
||||||
fmt.Sprintf("Guest registration is disabled on %q", r.ServerName),
|
fmt.Sprintf("Guest registration is disabled on %q", r.ServerName),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -604,7 +606,7 @@ func handleGuestRegistration(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to create account: " + err.Error()),
|
JSON: spec.Unknown("failed to create account: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
token, err := tokens.GenerateLoginToken(tokens.TokenOptions{
|
token, err := tokens.GenerateLoginToken(tokens.TokenOptions{
|
||||||
|
|
@ -616,7 +618,7 @@ func handleGuestRegistration(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("Failed to generate access token"),
|
JSON: spec.Unknown("Failed to generate access token"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//we don't allow guests to specify their own device_id
|
//we don't allow guests to specify their own device_id
|
||||||
|
|
@ -632,7 +634,7 @@ func handleGuestRegistration(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
JSON: spec.Unknown("failed to create device: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -682,7 +684,7 @@ func handleRegistrationFlow(
|
||||||
if !registrationEnabled && r.Auth.Type != authtypes.LoginTypeSharedSecret {
|
if !registrationEnabled && r.Auth.Type != authtypes.LoginTypeSharedSecret {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(
|
JSON: spec.Forbidden(
|
||||||
fmt.Sprintf("Registration is disabled on %q", r.ServerName),
|
fmt.Sprintf("Registration is disabled on %q", r.ServerName),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -696,7 +698,7 @@ func handleRegistrationFlow(
|
||||||
UsernameMatchesExclusiveNamespaces(cfg, r.Username) {
|
UsernameMatchesExclusiveNamespaces(cfg, r.Username) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.ASExclusive("This username is reserved by an application service."),
|
JSON: spec.ASExclusive("This username is reserved by an application service."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -706,15 +708,15 @@ func handleRegistrationFlow(
|
||||||
err := validateRecaptcha(cfg, r.Auth.Response, req.RemoteAddr)
|
err := validateRecaptcha(cfg, r.Auth.Response, req.RemoteAddr)
|
||||||
switch err {
|
switch err {
|
||||||
case ErrCaptchaDisabled:
|
case ErrCaptchaDisabled:
|
||||||
return util.JSONResponse{Code: http.StatusForbidden, JSON: jsonerror.Unknown(err.Error())}
|
return util.JSONResponse{Code: http.StatusForbidden, JSON: spec.Unknown(err.Error())}
|
||||||
case ErrMissingResponse:
|
case ErrMissingResponse:
|
||||||
return util.JSONResponse{Code: http.StatusBadRequest, JSON: jsonerror.BadJSON(err.Error())}
|
return util.JSONResponse{Code: http.StatusBadRequest, JSON: spec.BadJSON(err.Error())}
|
||||||
case ErrInvalidCaptcha:
|
case ErrInvalidCaptcha:
|
||||||
return util.JSONResponse{Code: http.StatusUnauthorized, JSON: jsonerror.BadJSON(err.Error())}
|
return util.JSONResponse{Code: http.StatusUnauthorized, JSON: spec.BadJSON(err.Error())}
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("failed to validate recaptcha")
|
util.GetLogger(req.Context()).WithError(err).Error("failed to validate recaptcha")
|
||||||
return util.JSONResponse{Code: http.StatusInternalServerError, JSON: jsonerror.InternalServerError()}
|
return util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Recaptcha to the list of completed registration stages
|
// Add Recaptcha to the list of completed registration stages
|
||||||
|
|
@ -732,7 +734,7 @@ func handleRegistrationFlow(
|
||||||
default:
|
default:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotImplemented,
|
Code: http.StatusNotImplemented,
|
||||||
JSON: jsonerror.Unknown("unknown/unimplemented auth type"),
|
JSON: spec.Unknown("unknown/unimplemented auth type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -764,7 +766,7 @@ func handleApplicationServiceRegistration(
|
||||||
if tokenErr != nil {
|
if tokenErr != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.MissingToken(tokenErr.Error()),
|
JSON: spec.MissingToken(tokenErr.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -834,14 +836,14 @@ func completeRegistration(
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("Missing username"),
|
JSON: spec.MissingParam("Missing username"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Blank passwords are only allowed by registered application services
|
// Blank passwords are only allowed by registered application services
|
||||||
if password == "" && appserviceID == "" {
|
if password == "" && appserviceID == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("Missing password"),
|
JSON: spec.MissingParam("Missing password"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var accRes userapi.PerformAccountCreationResponse
|
var accRes userapi.PerformAccountCreationResponse
|
||||||
|
|
@ -857,12 +859,12 @@ func completeRegistration(
|
||||||
if _, ok := err.(*userapi.ErrorConflict); ok { // user already exists
|
if _, ok := err.(*userapi.ErrorConflict); ok { // user already exists
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UserInUse("Desired user ID is already taken."),
|
JSON: spec.UserInUse("Desired user ID is already taken."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to create account: " + err.Error()),
|
JSON: spec.Unknown("failed to create account: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -884,7 +886,7 @@ func completeRegistration(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("Failed to generate access token"),
|
JSON: spec.Unknown("Failed to generate access token"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -893,7 +895,7 @@ func completeRegistration(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to set display name: " + err.Error()),
|
JSON: spec.Unknown("failed to set display name: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -911,7 +913,7 @@ func completeRegistration(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
JSON: spec.Unknown("failed to create device: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1006,7 +1008,7 @@ func RegisterAvailable(
|
||||||
if v.ServerName == domain && !v.AllowRegistration {
|
if v.ServerName == domain && !v.AllowRegistration {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(
|
JSON: spec.Forbidden(
|
||||||
fmt.Sprintf("Registration is not allowed on %q", string(v.ServerName)),
|
fmt.Sprintf("Registration is not allowed on %q", string(v.ServerName)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -1023,7 +1025,7 @@ func RegisterAvailable(
|
||||||
if appservice.OwnsNamespaceCoveringUserId(userID) {
|
if appservice.OwnsNamespaceCoveringUserId(userID) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UserInUse("Desired user ID is reserved by an application service."),
|
JSON: spec.UserInUse("Desired user ID is reserved by an application service."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1036,14 +1038,14 @@ func RegisterAvailable(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: jsonerror.Unknown("failed to check availability:" + err.Error()),
|
JSON: spec.Unknown("failed to check availability:" + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res.Available {
|
if !res.Available {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UserInUse("Desired User ID is already taken."),
|
JSON: spec.UserInUse("Desired User ID is already taken."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1060,7 +1062,7 @@ func handleSharedSecretRegistration(cfg *config.ClientAPI, userAPI userapi.Clien
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.BadJSON(fmt.Sprintf("malformed json: %s", err)),
|
JSON: spec.BadJSON(fmt.Sprintf("malformed json: %s", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
valid, err := sr.IsValidMacLogin(ssrr.Nonce, ssrr.User, ssrr.Password, ssrr.Admin, ssrr.MacBytes)
|
valid, err := sr.IsValidMacLogin(ssrr.Nonce, ssrr.User, ssrr.Password, ssrr.Admin, ssrr.MacBytes)
|
||||||
|
|
@ -1070,7 +1072,7 @@ func handleSharedSecretRegistration(cfg *config.ClientAPI, userAPI userapi.Clien
|
||||||
if !valid {
|
if !valid {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 403,
|
Code: 403,
|
||||||
JSON: jsonerror.Forbidden("bad mac"),
|
JSON: spec.Forbidden("bad mac"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// downcase capitals
|
// downcase capitals
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal"
|
"github.com/matrix-org/dendrite/internal"
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
|
@ -39,6 +38,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/test/testrig"
|
"github.com/matrix-org/dendrite/test/testrig"
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -306,7 +306,7 @@ func Test_register(t *testing.T) {
|
||||||
guestsDisabled: true,
|
guestsDisabled: true,
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(`Guest registration is disabled on "test"`),
|
JSON: spec.Forbidden(`Guest registration is disabled on "test"`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -318,7 +318,7 @@ func Test_register(t *testing.T) {
|
||||||
loginType: "im.not.known",
|
loginType: "im.not.known",
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusNotImplemented,
|
Code: http.StatusNotImplemented,
|
||||||
JSON: jsonerror.Unknown("unknown/unimplemented auth type"),
|
JSON: spec.Unknown("unknown/unimplemented auth type"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -326,7 +326,7 @@ func Test_register(t *testing.T) {
|
||||||
registrationDisabled: true,
|
registrationDisabled: true,
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(`Registration is disabled on "test"`),
|
JSON: spec.Forbidden(`Registration is disabled on "test"`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -344,7 +344,7 @@ func Test_register(t *testing.T) {
|
||||||
username: "success",
|
username: "success",
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UserInUse("Desired user ID is already taken."),
|
JSON: spec.UserInUse("Desired user ID is already taken."),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -361,7 +361,7 @@ func Test_register(t *testing.T) {
|
||||||
username: "1337",
|
username: "1337",
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidUsername("Numeric user IDs are reserved"),
|
JSON: spec.InvalidUsername("Numeric user IDs are reserved"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -369,7 +369,7 @@ func Test_register(t *testing.T) {
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Unknown(ErrCaptchaDisabled.Error()),
|
JSON: spec.Unknown(ErrCaptchaDisabled.Error()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -378,7 +378,7 @@ func Test_register(t *testing.T) {
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(ErrMissingResponse.Error()),
|
JSON: spec.BadJSON(ErrMissingResponse.Error()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -388,7 +388,7 @@ func Test_register(t *testing.T) {
|
||||||
captchaBody: `notvalid`,
|
captchaBody: `notvalid`,
|
||||||
wantResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: jsonerror.BadJSON(ErrInvalidCaptcha.Error()),
|
JSON: spec.BadJSON(ErrInvalidCaptcha.Error()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -402,7 +402,7 @@ func Test_register(t *testing.T) {
|
||||||
enableRecaptcha: true,
|
enableRecaptcha: true,
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
captchaBody: `i should fail for other reasons`,
|
captchaBody: `i should fail for other reasons`,
|
||||||
wantResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: jsonerror.InternalServerError()},
|
wantResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,7 +484,7 @@ func Test_register(t *testing.T) {
|
||||||
if !reflect.DeepEqual(r.Flows, cfg.Derived.Registration.Flows) {
|
if !reflect.DeepEqual(r.Flows, cfg.Derived.Registration.Flows) {
|
||||||
t.Fatalf("unexpected registration flows: %+v, want %+v", r.Flows, cfg.Derived.Registration.Flows)
|
t.Fatalf("unexpected registration flows: %+v, want %+v", r.Flows, cfg.Derived.Registration.Flows)
|
||||||
}
|
}
|
||||||
case *jsonerror.MatrixError:
|
case spec.MatrixError:
|
||||||
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantResponse)
|
t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantResponse)
|
||||||
}
|
}
|
||||||
|
|
@ -541,7 +541,12 @@ func Test_register(t *testing.T) {
|
||||||
resp = Register(req, userAPI, &cfg.ClientAPI)
|
resp = Register(req, userAPI, &cfg.ClientAPI)
|
||||||
|
|
||||||
switch resp.JSON.(type) {
|
switch resp.JSON.(type) {
|
||||||
case *jsonerror.MatrixError:
|
case spec.InternalServerError:
|
||||||
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
|
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case spec.MatrixError:
|
||||||
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,14 +39,17 @@ func GetTags(
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Cannot retrieve another user's tags"),
|
JSON: spec.Forbidden("Cannot retrieve another user's tags"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tagContent, err := obtainSavedTags(req, userID, roomID, userAPI)
|
tagContent, err := obtainSavedTags(req, userID, roomID, userAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -71,7 +74,7 @@ func PutTag(
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Cannot modify another user's tags"),
|
JSON: spec.Forbidden("Cannot modify another user's tags"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +86,10 @@ func PutTag(
|
||||||
tagContent, err := obtainSavedTags(req, userID, roomID, userAPI)
|
tagContent, err := obtainSavedTags(req, userID, roomID, userAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tagContent.Tags == nil {
|
if tagContent.Tags == nil {
|
||||||
|
|
@ -93,7 +99,10 @@ func PutTag(
|
||||||
|
|
||||||
if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil {
|
if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -118,14 +127,17 @@ func DeleteTag(
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Cannot modify another user's tags"),
|
JSON: spec.Forbidden("Cannot modify another user's tags"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tagContent, err := obtainSavedTags(req, userID, roomID, userAPI)
|
tagContent, err := obtainSavedTags(req, userID, roomID, userAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the tag to be deleted exists
|
// Check whether the tag to be deleted exists
|
||||||
|
|
@ -141,7 +153,10 @@ func DeleteTag(
|
||||||
|
|
||||||
if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil {
|
if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,21 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/setup/base"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/setup/base"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/api"
|
"github.com/matrix-org/dendrite/clientapi/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
clientutil "github.com/matrix-org/dendrite/clientapi/httputil"
|
clientutil "github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
|
|
@ -85,6 +86,14 @@ func Setup(
|
||||||
unstableFeatures["org.matrix."+msc] = true
|
unstableFeatures["org.matrix."+msc] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// singleflight protects /join endpoints from being invoked
|
||||||
|
// multiple times from the same user and room, otherwise
|
||||||
|
// a state reset can occur. This also avoids unneeded
|
||||||
|
// state calculations.
|
||||||
|
// TODO: actually fix this in the roomserver, as there are
|
||||||
|
// possibly other ways that can result in a stat reset.
|
||||||
|
sf := singleflight.Group{}
|
||||||
|
|
||||||
if cfg.Matrix.WellKnownClientName != "" {
|
if cfg.Matrix.WellKnownClientName != "" {
|
||||||
logrus.Infof("Setting m.homeserver base_url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownClientName)
|
logrus.Infof("Setting m.homeserver base_url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownClientName)
|
||||||
wkMux.Handle("/client", httputil.MakeExternalAPI("wellknown", func(r *http.Request) util.JSONResponse {
|
wkMux.Handle("/client", httputil.MakeExternalAPI("wellknown", func(r *http.Request) util.JSONResponse {
|
||||||
|
|
@ -148,11 +157,41 @@ func Setup(
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusMethodNotAllowed,
|
Code: http.StatusMethodNotAllowed,
|
||||||
JSON: jsonerror.NotFound("unknown method"),
|
JSON: spec.NotFound("unknown method"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
|
||||||
}
|
}
|
||||||
|
dendriteAdminRouter.Handle("/admin/registrationTokens/new",
|
||||||
|
httputil.MakeAdminAPI("admin_registration_tokens_new", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return AdminCreateNewRegistrationToken(req, cfg, userAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
dendriteAdminRouter.Handle("/admin/registrationTokens",
|
||||||
|
httputil.MakeAdminAPI("admin_list_registration_tokens", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return AdminListRegistrationTokens(req, cfg, userAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
dendriteAdminRouter.Handle("/admin/registrationTokens/{token}",
|
||||||
|
httputil.MakeAdminAPI("admin_get_registration_token", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
switch req.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
return AdminGetRegistrationToken(req, cfg, userAPI)
|
||||||
|
case http.MethodPut:
|
||||||
|
return AdminUpdateRegistrationToken(req, cfg, userAPI)
|
||||||
|
case http.MethodDelete:
|
||||||
|
return AdminDeleteRegistrationToken(req, cfg, userAPI)
|
||||||
|
default:
|
||||||
|
return util.MatrixErrorResponse(
|
||||||
|
404,
|
||||||
|
string(spec.ErrorNotFound),
|
||||||
|
"unknown method",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodPut, http.MethodDelete, http.MethodOptions)
|
||||||
|
|
||||||
dendriteAdminRouter.Handle("/admin/evacuateRoom/{roomID}",
|
dendriteAdminRouter.Handle("/admin/evacuateRoom/{roomID}",
|
||||||
httputil.MakeAdminAPI("admin_evacuate_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAdminAPI("admin_evacuate_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
|
@ -265,9 +304,17 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return JoinRoomByIDOrAlias(
|
// Only execute a join for roomIDOrAlias and UserID once. If there is a join in progress
|
||||||
req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
|
// it waits for it to complete and returns that result for subsequent requests.
|
||||||
)
|
resp, _, _ := sf.Do(vars["roomIDOrAlias"]+device.UserID, func() (any, error) {
|
||||||
|
return JoinRoomByIDOrAlias(
|
||||||
|
req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
|
||||||
|
), nil
|
||||||
|
})
|
||||||
|
// once all joins are processed, drop them from the cache. Further requests
|
||||||
|
// will be processed as usual.
|
||||||
|
sf.Forget(vars["roomIDOrAlias"] + device.UserID)
|
||||||
|
return resp.(util.JSONResponse)
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -301,9 +348,17 @@ func Setup(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
return JoinRoomByIDOrAlias(
|
// Only execute a join for roomID and UserID once. If there is a join in progress
|
||||||
req, device, rsAPI, userAPI, vars["roomID"],
|
// it waits for it to complete and returns that result for subsequent requests.
|
||||||
)
|
resp, _, _ := sf.Do(vars["roomID"]+device.UserID, func() (any, error) {
|
||||||
|
return JoinRoomByIDOrAlias(
|
||||||
|
req, device, rsAPI, userAPI, vars["roomID"],
|
||||||
|
), nil
|
||||||
|
})
|
||||||
|
// once all joins are processed, drop them from the cache. Further requests
|
||||||
|
// will be processed as usual.
|
||||||
|
sf.Forget(vars["roomID"] + device.UserID)
|
||||||
|
return resp.(util.JSONResponse)
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/leave",
|
v3mux.Handle("/rooms/{roomID}/leave",
|
||||||
|
|
@ -659,7 +714,7 @@ func Setup(
|
||||||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("missing trailing slash"),
|
JSON: spec.InvalidParam("missing trailing slash"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
@ -674,7 +729,7 @@ func Setup(
|
||||||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("scope, kind and rule ID must be specified"),
|
JSON: spec.InvalidParam("scope, kind and rule ID must be specified"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut)
|
).Methods(http.MethodPut)
|
||||||
|
|
@ -693,7 +748,7 @@ func Setup(
|
||||||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("missing trailing slash after scope"),
|
JSON: spec.InvalidParam("missing trailing slash after scope"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
@ -702,7 +757,7 @@ func Setup(
|
||||||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("kind and rule ID must be specified"),
|
JSON: spec.InvalidParam("kind and rule ID must be specified"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut)
|
).Methods(http.MethodPut)
|
||||||
|
|
@ -721,7 +776,7 @@ func Setup(
|
||||||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("missing trailing slash after kind"),
|
JSON: spec.InvalidParam("missing trailing slash after kind"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
@ -730,7 +785,7 @@ func Setup(
|
||||||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue("rule ID must be specified"),
|
JSON: spec.InvalidParam("rule ID must be specified"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut)
|
).Methods(http.MethodPut)
|
||||||
|
|
@ -939,7 +994,7 @@ func Setup(
|
||||||
// TODO: Allow people to peek into rooms.
|
// TODO: Allow people to peek into rooms.
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.GuestAccessForbidden("Guest access not implemented"),
|
JSON: spec.GuestAccessForbidden("Guest access not implemented"),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
@ -1244,7 +1299,7 @@ func Setup(
|
||||||
if version == "" {
|
if version == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.InvalidArgumentValue("version must be specified"),
|
JSON: spec.InvalidParam("version must be specified"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var reqBody keyBackupSessionRequest
|
var reqBody keyBackupSessionRequest
|
||||||
|
|
@ -1265,7 +1320,7 @@ func Setup(
|
||||||
if version == "" {
|
if version == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.InvalidArgumentValue("version must be specified"),
|
JSON: spec.InvalidParam("version must be specified"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomID := vars["roomID"]
|
roomID := vars["roomID"]
|
||||||
|
|
@ -1297,7 +1352,7 @@ func Setup(
|
||||||
if version == "" {
|
if version == "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: jsonerror.InvalidArgumentValue("version must be specified"),
|
JSON: spec.InvalidParam("version must be specified"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var reqBody userapi.KeyBackupSession
|
var reqBody userapi.KeyBackupSession
|
||||||
|
|
|
||||||
|
|
@ -23,20 +23,18 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
// http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||||
|
|
@ -69,6 +67,8 @@ var sendEventDuration = prometheus.NewHistogramVec(
|
||||||
// /rooms/{roomID}/send/{eventType}
|
// /rooms/{roomID}/send/{eventType}
|
||||||
// /rooms/{roomID}/send/{eventType}/{txnID}
|
// /rooms/{roomID}/send/{eventType}/{txnID}
|
||||||
// /rooms/{roomID}/state/{eventType}/{stateKey}
|
// /rooms/{roomID}/state/{eventType}/{stateKey}
|
||||||
|
//
|
||||||
|
// nolint: gocyclo
|
||||||
func SendEvent(
|
func SendEvent(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
|
|
@ -81,7 +81,7 @@ func SendEvent(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UnsupportedRoomVersion(err.Error()),
|
JSON: spec.UnsupportedRoomVersion(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,15 +122,26 @@ func SendEvent(
|
||||||
delete(r, "join_authorised_via_users_server")
|
delete(r, "join_authorised_via_users_server")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for power level events we need to replace the userID with the pseudoID
|
||||||
|
if roomVersion == gomatrixserverlib.RoomVersionPseudoIDs && eventType == spec.MRoomPowerLevels {
|
||||||
|
err = updatePowerLevels(req, r, roomID, rsAPI)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
evTime, err := httputil.ParseTSParam(req)
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e, resErr := generateSendEvent(req.Context(), r, device, roomID, eventType, stateKey, cfg, rsAPI, evTime)
|
e, resErr := generateSendEvent(req.Context(), r, device, roomID, eventType, stateKey, rsAPI, evTime)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -145,12 +156,15 @@ func SendEvent(
|
||||||
if !aliasReq.Valid() {
|
if !aliasReq.Valid() {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam("Request contains invalid aliases."),
|
JSON: spec.InvalidParam("Request contains invalid aliases."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aliasRes := &api.GetAliasesForRoomIDResponse{}
|
aliasRes := &api.GetAliasesForRoomIDResponse{}
|
||||||
if err = rsAPI.GetAliasesForRoomID(req.Context(), &api.GetAliasesForRoomIDRequest{RoomID: roomID}, aliasRes); err != nil {
|
if err = rsAPI.GetAliasesForRoomID(req.Context(), &api.GetAliasesForRoomIDRequest{RoomID: roomID}, aliasRes); err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var found int
|
var found int
|
||||||
requestAliases := append(aliasReq.AltAliases, aliasReq.Alias)
|
requestAliases := append(aliasReq.AltAliases, aliasReq.Alias)
|
||||||
|
|
@ -165,7 +179,7 @@ func SendEvent(
|
||||||
if aliasReq.Alias != "" && found < len(requestAliases) {
|
if aliasReq.Alias != "" && found < len(requestAliases) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadAlias("No matching alias found."),
|
JSON: spec.BadAlias("No matching alias found."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +208,10 @@ func SendEvent(
|
||||||
false,
|
false,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
|
util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
timeToSubmitEvent := time.Since(startedSubmittingEvent)
|
timeToSubmitEvent := time.Since(startedSubmittingEvent)
|
||||||
util.GetLogger(req.Context()).WithFields(logrus.Fields{
|
util.GetLogger(req.Context()).WithFields(logrus.Fields{
|
||||||
|
|
@ -220,6 +237,28 @@ func SendEvent(
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updatePowerLevels(req *http.Request, r map[string]interface{}, roomID string, rsAPI api.ClientRoomserverAPI) error {
|
||||||
|
userMap := r["users"].(map[string]interface{})
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for user, level := range userMap {
|
||||||
|
uID, err := spec.NewUserID(user, true)
|
||||||
|
if err != nil {
|
||||||
|
continue // we're modifying the map in place, so we're going to have invalid userIDs after the first iteration
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *uID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userMap[string(senderID)] = level
|
||||||
|
delete(userMap, user)
|
||||||
|
}
|
||||||
|
r["users"] = userMap
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// stateEqual compares the new and the existing state event content. If they are equal, returns a *util.JSONResponse
|
// stateEqual compares the new and the existing state event content. If they are equal, returns a *util.JSONResponse
|
||||||
// with the existing event_id, making this an idempotent request.
|
// with the existing event_id, making this an idempotent request.
|
||||||
func stateEqual(ctx context.Context, rsAPI api.ClientRoomserverAPI, eventType, stateKey, roomID string, newContent map[string]interface{}) *util.JSONResponse {
|
func stateEqual(ctx context.Context, rsAPI api.ClientRoomserverAPI, eventType, stateKey, roomID string, newContent map[string]interface{}) *util.JSONResponse {
|
||||||
|
|
@ -256,60 +295,87 @@ func generateSendEvent(
|
||||||
r map[string]interface{},
|
r map[string]interface{},
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
roomID, eventType string, stateKey *string,
|
roomID, eventType string, stateKey *string,
|
||||||
cfg *config.ClientAPI,
|
|
||||||
rsAPI api.ClientRoomserverAPI,
|
rsAPI api.ClientRoomserverAPI,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
) (gomatrixserverlib.PDU, *util.JSONResponse) {
|
) (gomatrixserverlib.PDU, *util.JSONResponse) {
|
||||||
// parse the incoming http request
|
// parse the incoming http request
|
||||||
userID := device.UserID
|
fullUserID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("Bad userID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("RoomID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: spec.NotFound("Unable to find senderID for user"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create the new event and set all the fields we can
|
// create the new event and set all the fields we can
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
Sender: userID,
|
SenderID: string(senderID),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
StateKey: stateKey,
|
StateKey: stateKey,
|
||||||
}
|
}
|
||||||
err := proto.SetContent(r)
|
err = proto.SetContent(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("proto.SetContent failed")
|
util.GetLogger(ctx).WithError(err).Error("proto.SetContent failed")
|
||||||
resErr := jsonerror.InternalServerError()
|
return nil, &util.JSONResponse{
|
||||||
return nil, &resErr
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *fullUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resErr := jsonerror.InternalServerError()
|
return nil, &util.JSONResponse{
|
||||||
return nil, &resErr
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryRes api.QueryLatestEventsAndStateResponse
|
var queryRes api.QueryLatestEventsAndStateResponse
|
||||||
e, err := eventutil.QueryAndBuildEvent(ctx, &proto, cfg.Matrix, identity, evTime, rsAPI, &queryRes)
|
e, err := eventutil.QueryAndBuildEvent(ctx, &proto, &identity, evTime, rsAPI, &queryRes)
|
||||||
if err == eventutil.ErrRoomNoExists {
|
switch specificErr := err.(type) {
|
||||||
|
case nil:
|
||||||
|
case eventutil.ErrRoomNoExists:
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("Room does not exist"),
|
JSON: spec.NotFound("Room does not exist"),
|
||||||
}
|
}
|
||||||
} else if e, ok := err.(gomatrixserverlib.BadJSONError); ok {
|
case gomatrixserverlib.BadJSONError:
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: spec.BadJSON(specificErr.Error()),
|
||||||
}
|
}
|
||||||
} else if e, ok := err.(gomatrixserverlib.EventValidationError); ok {
|
case gomatrixserverlib.EventValidationError:
|
||||||
if e.Code == gomatrixserverlib.EventValidationTooLarge {
|
if specificErr.Code == gomatrixserverlib.EventValidationTooLarge {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusRequestEntityTooLarge,
|
Code: http.StatusRequestEntityTooLarge,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: spec.BadJSON(specificErr.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: spec.BadJSON(specificErr.Error()),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
default:
|
||||||
util.GetLogger(ctx).WithError(err).Error("eventutil.BuildEvent failed")
|
util.GetLogger(ctx).WithError(err).Error("eventutil.BuildEvent failed")
|
||||||
resErr := jsonerror.InternalServerError()
|
return nil, &util.JSONResponse{
|
||||||
return nil, &resErr
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to see if this user can perform this operation
|
// check to see if this user can perform this operation
|
||||||
|
|
@ -318,10 +384,12 @@ func generateSendEvent(
|
||||||
stateEvents[i] = queryRes.StateEvents[i].PDU
|
stateEvents[i] = queryRes.StateEvents[i].PDU
|
||||||
}
|
}
|
||||||
provider := gomatrixserverlib.NewAuthEvents(gomatrixserverlib.ToPDUs(stateEvents))
|
provider := gomatrixserverlib.NewAuthEvents(gomatrixserverlib.ToPDUs(stateEvents))
|
||||||
if err = gomatrixserverlib.Allowed(e.PDU, &provider); err != nil {
|
if err = gomatrixserverlib.Allowed(e.PDU, &provider, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
|
return rsAPI.QueryUserIDForSender(ctx, *validRoomID, senderID)
|
||||||
|
}); err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
JSON: spec.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,13 +400,13 @@ func generateSendEvent(
|
||||||
util.GetLogger(ctx).WithError(err).Error("Cannot unmarshal the event content.")
|
util.GetLogger(ctx).WithError(err).Error("Cannot unmarshal the event content.")
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Cannot unmarshal the event content."),
|
JSON: spec.BadJSON("Cannot unmarshal the event content."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if content["replacement_room"] == e.RoomID() {
|
if content["replacement_room"] == e.RoomID() {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.InvalidParam("Cannot send tombstone event that points to the same room."),
|
JSON: spec.InvalidParam("Cannot send tombstone event that points to the same room."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
||||||
|
|
@ -53,7 +53,10 @@ func SendToDevice(
|
||||||
req.Context(), device.UserID, userID, deviceID, eventType, message,
|
req.Context(), device.UserID, userID, deviceID, eventType, message,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.SendToDevice failed")
|
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.SendToDevice failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type typingContentJSON struct {
|
type typingContentJSON struct {
|
||||||
|
|
@ -39,12 +39,20 @@ func SendTyping(
|
||||||
if device.UserID != userID {
|
if device.UserID != userID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Cannot set another user's typing state"),
|
JSON: spec.Forbidden("Cannot set another user's typing state"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceUserID, err := spec.NewUserID(userID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("userID doesn't have power level to change visibility"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the user is a member of this room
|
// Verify that the user is a member of this room
|
||||||
resErr := checkMemberInRoom(req.Context(), rsAPI, userID, roomID)
|
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +66,10 @@ func SendTyping(
|
||||||
|
|
||||||
if err := syncProducer.SendTyping(req.Context(), userID, roomID, r.Typing, r.Timeout); err != nil {
|
if err := syncProducer.SendTyping(req.Context(), userID, roomID, r.Typing, r.Timeout); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.Send failed")
|
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.Send failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,12 @@ import (
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unspecced server notice request
|
// Unspecced server notice request
|
||||||
|
|
@ -52,6 +52,7 @@ type sendServerNoticeRequest struct {
|
||||||
StateKey string `json:"state_key,omitempty"`
|
StateKey string `json:"state_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:gocyclo
|
||||||
// SendServerNotice sends a message to a specific user. It can only be invoked by an admin.
|
// SendServerNotice sends a message to a specific user. It can only be invoked by an admin.
|
||||||
func SendServerNotice(
|
func SendServerNotice(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
|
|
@ -68,7 +69,7 @@ func SendServerNotice(
|
||||||
if device.AccountType != userapi.AccountTypeAdmin {
|
if device.AccountType != userapi.AccountTypeAdmin {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("This API can only be used by admin users."),
|
JSON: spec.Forbidden("This API can only be used by admin users."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +91,7 @@ func SendServerNotice(
|
||||||
if !r.valid() {
|
if !r.valid() {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("Invalid request"),
|
JSON: spec.BadJSON("Invalid request"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +156,7 @@ func SendServerNotice(
|
||||||
Invite: []string{r.UserID},
|
Invite: []string{r.UserID},
|
||||||
Name: cfgNotices.RoomName,
|
Name: cfgNotices.RoomName,
|
||||||
Visibility: "private",
|
Visibility: "private",
|
||||||
Preset: presetPrivateChat,
|
Preset: spec.PresetPrivateChat,
|
||||||
CreationContent: cc,
|
CreationContent: cc,
|
||||||
RoomVersion: roomVersion,
|
RoomVersion: roomVersion,
|
||||||
PowerLevelContentOverride: pl,
|
PowerLevelContentOverride: pl,
|
||||||
|
|
@ -175,7 +176,10 @@ func SendServerNotice(
|
||||||
}}
|
}}
|
||||||
if err = saveTagData(req, r.UserID, roomID, userAPI, serverAlertTag); err != nil {
|
if err = saveTagData(req, r.UserID, roomID, userAPI, serverAlertTag); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("saveTagData failed")
|
util.GetLogger(ctx).WithError(err).Error("saveTagData failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -184,12 +188,23 @@ func SendServerNotice(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we've found a room in common, check the membership
|
// we've found a room in common, check the membership
|
||||||
|
deviceUserID, err := spec.NewUserID(r.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden("userID doesn't have power level to change visibility"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
roomID = commonRooms[0]
|
roomID = commonRooms[0]
|
||||||
membershipRes := api.QueryMembershipForUserResponse{}
|
membershipRes := api.QueryMembershipForUserResponse{}
|
||||||
err := rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: r.UserID, RoomID: roomID}, &membershipRes)
|
err = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: *deviceUserID, RoomID: roomID}, &membershipRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("unable to query membership for user")
|
util.GetLogger(ctx).WithError(err).Error("unable to query membership for user")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !membershipRes.IsInRoom {
|
if !membershipRes.IsInRoom {
|
||||||
// re-invite the user
|
// re-invite the user
|
||||||
|
|
@ -206,7 +221,7 @@ func SendServerNotice(
|
||||||
"body": r.Content.Body,
|
"body": r.Content.Body,
|
||||||
"msgtype": r.Content.MsgType,
|
"msgtype": r.Content.MsgType,
|
||||||
}
|
}
|
||||||
e, resErr := generateSendEvent(ctx, request, senderDevice, roomID, "m.room.message", nil, cfgClient, rsAPI, time.Now())
|
e, resErr := generateSendEvent(ctx, request, senderDevice, roomID, "m.room.message", nil, rsAPI, time.Now())
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
logrus.Errorf("failed to send message: %+v", resErr)
|
logrus.Errorf("failed to send message: %+v", resErr)
|
||||||
return *resErr
|
return *resErr
|
||||||
|
|
@ -228,7 +243,7 @@ func SendServerNotice(
|
||||||
ctx, rsAPI,
|
ctx, rsAPI,
|
||||||
api.KindNew,
|
api.KindNew,
|
||||||
[]*types.HeaderedEvent{
|
[]*types.HeaderedEvent{
|
||||||
&types.HeaderedEvent{PDU: e},
|
{PDU: e},
|
||||||
},
|
},
|
||||||
device.UserDomain(),
|
device.UserDomain(),
|
||||||
cfgClient.Matrix.ServerName,
|
cfgClient.Matrix.ServerName,
|
||||||
|
|
@ -237,7 +252,10 @@ func SendServerNotice(
|
||||||
false,
|
false,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
util.GetLogger(ctx).WithFields(logrus.Fields{
|
util.GetLogger(ctx).WithFields(logrus.Fields{
|
||||||
"event_id": e.EventID(),
|
"event_id": e.EventID(),
|
||||||
|
|
@ -332,7 +350,7 @@ func getSenderDevice(
|
||||||
if len(deviceRes.Devices) > 0 {
|
if len(deviceRes.Devices) > 0 {
|
||||||
// If there were changes to the profile, create a new membership event
|
// If there were changes to the profile, create a new membership event
|
||||||
if displayNameChanged || avatarChanged {
|
if displayNameChanged || avatarChanged {
|
||||||
_, err = updateProfile(ctx, rsAPI, &deviceRes.Devices[0], profile, accRes.Account.UserID, cfg, time.Now())
|
_, err = updateProfile(ctx, rsAPI, &deviceRes.Devices[0], profile, accRes.Account.UserID, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/syncapi/synctypes"
|
"github.com/matrix-org/dendrite/syncapi/synctypes"
|
||||||
|
|
@ -57,12 +56,15 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
||||||
StateToFetch: []gomatrixserverlib.StateKeyTuple{},
|
StateToFetch: []gomatrixserverlib.StateKeyTuple{},
|
||||||
}, &stateRes); err != nil {
|
}, &stateRes); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("queryAPI.QueryLatestEventsAndState failed")
|
util.GetLogger(ctx).WithError(err).Error("queryAPI.QueryLatestEventsAndState failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !stateRes.RoomExists {
|
if !stateRes.RoomExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("room does not exist"),
|
JSON: spec.Forbidden("room does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,10 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
||||||
content := map[string]string{}
|
content := map[string]string{}
|
||||||
if err := json.Unmarshal(ev.Content(), &content); err != nil {
|
if err := json.Unmarshal(ev.Content(), &content); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed")
|
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if visibility, ok := content["history_visibility"]; ok {
|
if visibility, ok := content["history_visibility"]; ok {
|
||||||
worldReadable = visibility == "world_readable"
|
worldReadable = visibility == "world_readable"
|
||||||
|
|
@ -94,20 +99,31 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
||||||
if !worldReadable {
|
if !worldReadable {
|
||||||
// The room isn't world-readable so try to work out based on the
|
// The room isn't world-readable so try to work out based on the
|
||||||
// user's membership if we want the latest state or not.
|
// user's membership if we want the latest state or not.
|
||||||
err := rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("UserID is invalid")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.Unknown("Device UserID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: device.UserID,
|
UserID: *userID,
|
||||||
}, &membershipRes)
|
}, &membershipRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If the user has never been in the room then stop at this point.
|
// If the user has never been in the room then stop at this point.
|
||||||
// We won't tell the user about a room they have never joined.
|
// We won't tell the user about a room they have never joined.
|
||||||
if !membershipRes.HasBeenInRoom {
|
if !membershipRes.HasBeenInRoom {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
|
JSON: spec.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise, if the user has been in the room, whether or not we
|
// Otherwise, if the user has been in the room, whether or not we
|
||||||
|
|
@ -134,7 +150,9 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
||||||
for _, ev := range stateRes.StateEvents {
|
for _, ev := range stateRes.StateEvents {
|
||||||
stateEvents = append(
|
stateEvents = append(
|
||||||
stateEvents,
|
stateEvents,
|
||||||
synctypes.ToClientEvent(ev, synctypes.FormatAll),
|
synctypes.ToClientEventDefault(func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
|
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
|
}, ev),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -148,12 +166,34 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
||||||
}, &stateAfterRes)
|
}, &stateAfterRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, ev := range stateAfterRes.StateEvents {
|
for _, ev := range stateAfterRes.StateEvents {
|
||||||
|
sender := spec.UserID{}
|
||||||
|
evRoomID, err := spec.NewRoomID(ev.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("Event roomID is invalid")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userID, err := rsAPI.QueryUserIDForSender(ctx, *evRoomID, ev.SenderID())
|
||||||
|
if err == nil && userID != nil {
|
||||||
|
sender = *userID
|
||||||
|
}
|
||||||
|
|
||||||
|
sk := ev.StateKey()
|
||||||
|
if sk != nil && *sk != "" {
|
||||||
|
skUserID, err := rsAPI.QueryUserIDForSender(ctx, *evRoomID, spec.SenderID(*ev.StateKey()))
|
||||||
|
if err == nil && skUserID != nil {
|
||||||
|
skString := skUserID.String()
|
||||||
|
sk = &skString
|
||||||
|
}
|
||||||
|
}
|
||||||
stateEvents = append(
|
stateEvents = append(
|
||||||
stateEvents,
|
stateEvents,
|
||||||
synctypes.ToClientEvent(ev, synctypes.FormatAll),
|
synctypes.ToClientEvent(ev, synctypes.FormatAll, sender, sk),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +243,10 @@ func OnIncomingStateTypeRequest(
|
||||||
StateToFetch: stateToFetch,
|
StateToFetch: stateToFetch,
|
||||||
}, &stateRes); err != nil {
|
}, &stateRes); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("queryAPI.QueryLatestEventsAndState failed")
|
util.GetLogger(ctx).WithError(err).Error("queryAPI.QueryLatestEventsAndState failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look at the room state and see if we have a history visibility event
|
// Look at the room state and see if we have a history visibility event
|
||||||
|
|
@ -214,7 +257,10 @@ func OnIncomingStateTypeRequest(
|
||||||
content := map[string]string{}
|
content := map[string]string{}
|
||||||
if err := json.Unmarshal(ev.Content(), &content); err != nil {
|
if err := json.Unmarshal(ev.Content(), &content); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed")
|
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if visibility, ok := content["history_visibility"]; ok {
|
if visibility, ok := content["history_visibility"]; ok {
|
||||||
worldReadable = visibility == "world_readable"
|
worldReadable = visibility == "world_readable"
|
||||||
|
|
@ -232,22 +278,33 @@ func OnIncomingStateTypeRequest(
|
||||||
// membershipRes will only be populated if the room is not world-readable.
|
// membershipRes will only be populated if the room is not world-readable.
|
||||||
var membershipRes api.QueryMembershipForUserResponse
|
var membershipRes api.QueryMembershipForUserResponse
|
||||||
if !worldReadable {
|
if !worldReadable {
|
||||||
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("UserID is invalid")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.Unknown("Device UserID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
// The room isn't world-readable so try to work out based on the
|
// The room isn't world-readable so try to work out based on the
|
||||||
// user's membership if we want the latest state or not.
|
// user's membership if we want the latest state or not.
|
||||||
err := rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{
|
err = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
UserID: device.UserID,
|
UserID: *userID,
|
||||||
}, &membershipRes)
|
}, &membershipRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If the user has never been in the room then stop at this point.
|
// If the user has never been in the room then stop at this point.
|
||||||
// We won't tell the user about a room they have never joined.
|
// We won't tell the user about a room they have never joined.
|
||||||
if !membershipRes.HasBeenInRoom || membershipRes.Membership == spec.Ban {
|
if !membershipRes.HasBeenInRoom || membershipRes.Membership == spec.Ban {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
|
JSON: spec.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise, if the user has been in the room, whether or not we
|
// Otherwise, if the user has been in the room, whether or not we
|
||||||
|
|
@ -295,7 +352,10 @@ func OnIncomingStateTypeRequest(
|
||||||
}, &stateAfterRes)
|
}, &stateAfterRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
util.GetLogger(ctx).WithError(err).Error("Failed to QueryMembershipForUser")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(stateAfterRes.StateEvents) > 0 {
|
if len(stateAfterRes.StateEvents) > 0 {
|
||||||
event = stateAfterRes.StateEvents[0]
|
event = stateAfterRes.StateEvents[0]
|
||||||
|
|
@ -307,12 +367,14 @@ func OnIncomingStateTypeRequest(
|
||||||
if event == nil {
|
if event == nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound(fmt.Sprintf("Cannot find state event for %q", evType)),
|
JSON: spec.NotFound(fmt.Sprintf("Cannot find state event for %q", evType)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stateEvent := stateEventInStateResp{
|
stateEvent := stateEventInStateResp{
|
||||||
ClientEvent: synctypes.ToClientEvent(event, synctypes.FormatAll),
|
ClientEvent: synctypes.ToClientEventDefault(func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
|
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
|
}, event),
|
||||||
}
|
}
|
||||||
|
|
||||||
var res interface{}
|
var res interface{}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Protocols implements
|
// Protocols implements
|
||||||
|
|
@ -33,13 +33,16 @@ func Protocols(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, dev
|
||||||
resp := &appserviceAPI.ProtocolResponse{}
|
resp := &appserviceAPI.ProtocolResponse{}
|
||||||
|
|
||||||
if err := asAPI.Protocols(req.Context(), &appserviceAPI.ProtocolRequest{Protocol: protocol}, resp); err != nil {
|
if err := asAPI.Protocols(req.Context(), &appserviceAPI.ProtocolRequest{Protocol: protocol}, resp); err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !resp.Exists {
|
if !resp.Exists {
|
||||||
if protocol != "" {
|
if protocol != "" {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("The protocol is unknown."),
|
JSON: spec.NotFound("The protocol is unknown."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -71,12 +74,15 @@ func User(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, device *
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
Params: params.Encode(),
|
Params: params.Encode(),
|
||||||
}, resp); err != nil {
|
}, resp); err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !resp.Exists {
|
if !resp.Exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("The Matrix User ID was not found"),
|
JSON: spec.NotFound("The Matrix User ID was not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -97,12 +103,15 @@ func Location(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, devi
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
Params: params.Encode(),
|
Params: params.Encode(),
|
||||||
}, resp); err != nil {
|
}, resp); err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !resp.Exists {
|
if !resp.Exists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("No portal rooms were found."),
|
JSON: spec.NotFound("No portal rooms were found."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
userdb "github.com/matrix-org/dendrite/userapi/storage"
|
userdb "github.com/matrix-org/dendrite/userapi/storage"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -60,28 +60,37 @@ func RequestEmailToken(req *http.Request, threePIDAPI api.ClientUserAPI, cfg *co
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threePIDAPI.QueryLocalpartForThreePID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threePIDAPI.QueryLocalpartForThreePID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res.Localpart) > 0 {
|
if len(res.Localpart) > 0 {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MatrixError{
|
JSON: spec.MatrixError{
|
||||||
ErrCode: "M_THREEPID_IN_USE",
|
ErrCode: spec.ErrorThreePIDInUse,
|
||||||
Err: userdb.Err3PIDInUse.Error(),
|
Err: userdb.Err3PIDInUse.Error(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.SID, err = threepid.CreateSession(req.Context(), body, cfg, client)
|
resp.SID, err = threepid.CreateSession(req.Context(), body, cfg, client)
|
||||||
if err == threepid.ErrNotTrusted {
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case threepid.ErrNotTrusted:
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CreateSession failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.NotTrusted(body.IDServer),
|
JSON: spec.NotTrusted(body.IDServer),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
default:
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepid.CreateSession failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CreateSession failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -102,21 +111,27 @@ func CheckAndSave3PIDAssociation(
|
||||||
|
|
||||||
// Check if the association has been validated
|
// Check if the association has been validated
|
||||||
verified, address, medium, err := threepid.CheckAssociation(req.Context(), body.Creds, cfg, client)
|
verified, address, medium, err := threepid.CheckAssociation(req.Context(), body.Creds, cfg, client)
|
||||||
if err == threepid.ErrNotTrusted {
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
case threepid.ErrNotTrusted:
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.NotTrusted(body.Creds.IDServer),
|
JSON: spec.NotTrusted(body.Creds.IDServer),
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
default:
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAssociation failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verified {
|
if !verified {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MatrixError{
|
JSON: spec.MatrixError{
|
||||||
ErrCode: "M_THREEPID_AUTH_FAILED",
|
ErrCode: spec.ErrorThreePIDAuthFailed,
|
||||||
Err: "Failed to auth 3pid",
|
Err: "Failed to auth 3pid",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +142,10 @@ func CheckAndSave3PIDAssociation(
|
||||||
err = threepid.PublishAssociation(req.Context(), body.Creds, device.UserID, cfg, client)
|
err = threepid.PublishAssociation(req.Context(), body.Creds, device.UserID, cfg, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepid.PublishAssociation failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepid.PublishAssociation failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +153,10 @@ func CheckAndSave3PIDAssociation(
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = threePIDAPI.PerformSaveThreePIDAssociation(req.Context(), &api.PerformSaveThreePIDAssociationRequest{
|
if err = threePIDAPI.PerformSaveThreePIDAssociation(req.Context(), &api.PerformSaveThreePIDAssociationRequest{
|
||||||
|
|
@ -145,7 +166,10 @@ func CheckAndSave3PIDAssociation(
|
||||||
Medium: medium,
|
Medium: medium,
|
||||||
}, &struct{}{}); err != nil {
|
}, &struct{}{}); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threePIDAPI.PerformSaveThreePIDAssociation failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threePIDAPI.PerformSaveThreePIDAssociation failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -161,7 +185,10 @@ func GetAssociated3PIDs(
|
||||||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &api.QueryThreePIDsForLocalpartResponse{}
|
res := &api.QueryThreePIDsForLocalpartResponse{}
|
||||||
|
|
@ -171,7 +198,10 @@ func GetAssociated3PIDs(
|
||||||
}, res)
|
}, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepidAPI.QueryThreePIDsForLocalpart failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepidAPI.QueryThreePIDsForLocalpart failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -192,7 +222,10 @@ func Forget3PID(req *http.Request, threepidAPI api.ClientUserAPI) util.JSONRespo
|
||||||
Medium: body.Medium,
|
Medium: body.Medium,
|
||||||
}, &struct{}{}); err != nil {
|
}, &struct{}{}); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("threepidAPI.PerformForgetThreePID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("threepidAPI.PerformForgetThreePID failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ import (
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/version"
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,26 +55,37 @@ func UpgradeRoom(
|
||||||
if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil {
|
if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.UnsupportedRoomVersion("This server does not support that room version"),
|
JSON: spec.UnsupportedRoomVersion("This server does not support that room version"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, device.UserID, gomatrixserverlib.RoomVersion(r.NewVersion))
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("device UserID is invalid")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion))
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
case roomserverAPI.ErrNotAllowed:
|
case roomserverAPI.ErrNotAllowed:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden(e.Error()),
|
JSON: spec.Forbidden(e.Error()),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if errors.Is(err, eventutil.ErrRoomNoExists) {
|
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("Room does not exist"),
|
JSON: spec.NotFound("Room does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ import (
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestTurnServer implements:
|
// RequestTurnServer implements:
|
||||||
|
|
@ -60,7 +60,10 @@ func RequestTurnServer(req *http.Request, device *api.Device, cfg *config.Client
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("mac.Write failed")
|
util.GetLogger(req.Context()).WithError(err).Error("mac.Write failed")
|
||||||
return jsonerror.InternalServerError()
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Password = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
resp.Password = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||||
|
|
|
||||||
|
|
@ -64,14 +64,34 @@ type idServerStoreInviteResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrMissingParameter is the error raised if a request for 3PID invite has
|
errMissingParameter = fmt.Errorf("'address', 'id_server' and 'medium' must all be supplied")
|
||||||
// an incomplete body
|
errNotTrusted = fmt.Errorf("untrusted server")
|
||||||
ErrMissingParameter = errors.New("'address', 'id_server' and 'medium' must all be supplied")
|
|
||||||
// ErrNotTrusted is the error raised if an identity server isn't in the list
|
|
||||||
// of trusted servers in the configuration file.
|
|
||||||
ErrNotTrusted = errors.New("untrusted server")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrMissingParameter is the error raised if a request for 3PID invite has
|
||||||
|
// an incomplete body
|
||||||
|
type ErrMissingParameter struct{}
|
||||||
|
|
||||||
|
func (e ErrMissingParameter) Error() string {
|
||||||
|
return errMissingParameter.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingParameter) Unwrap() error {
|
||||||
|
return errMissingParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNotTrusted is the error raised if an identity server isn't in the list
|
||||||
|
// of trusted servers in the configuration file.
|
||||||
|
type ErrNotTrusted struct{}
|
||||||
|
|
||||||
|
func (e ErrNotTrusted) Error() string {
|
||||||
|
return errNotTrusted.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrNotTrusted) Unwrap() error {
|
||||||
|
return errNotTrusted
|
||||||
|
}
|
||||||
|
|
||||||
// CheckAndProcessInvite analyses the body of an incoming membership request.
|
// CheckAndProcessInvite analyses the body of an incoming membership request.
|
||||||
// If the fields relative to a third-party-invite are all supplied, lookups the
|
// If the fields relative to a third-party-invite are all supplied, lookups the
|
||||||
// matching Matrix ID from the given identity server. If no Matrix ID is
|
// matching Matrix ID from the given identity server. If no Matrix ID is
|
||||||
|
|
@ -99,7 +119,7 @@ func CheckAndProcessInvite(
|
||||||
} else if body.Address == "" || body.IDServer == "" || body.Medium == "" {
|
} else if body.Address == "" || body.IDServer == "" || body.Medium == "" {
|
||||||
// If at least one of the 3PID-specific fields is supplied but not all
|
// If at least one of the 3PID-specific fields is supplied but not all
|
||||||
// of them, return an error
|
// of them, return an error
|
||||||
err = ErrMissingParameter
|
err = ErrMissingParameter{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,8 +355,20 @@ func emit3PIDInviteEvent(
|
||||||
rsAPI api.ClientRoomserverAPI,
|
rsAPI api.ClientRoomserverAPI,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
) error {
|
) error {
|
||||||
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sender, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
proto := &gomatrixserverlib.ProtoEvent{
|
proto := &gomatrixserverlib.ProtoEvent{
|
||||||
Sender: device.UserID,
|
SenderID: string(sender),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: "m.room.third_party_invite",
|
Type: "m.room.third_party_invite",
|
||||||
StateKey: &res.Token,
|
StateKey: &res.Token,
|
||||||
|
|
@ -350,7 +382,7 @@ func emit3PIDInviteEvent(
|
||||||
PublicKeys: res.PublicKeys,
|
PublicKeys: res.PublicKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := proto.SetContent(content); err != nil {
|
if err = proto.SetContent(content); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,7 +392,7 @@ func emit3PIDInviteEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
queryRes := api.QueryLatestEventsAndStateResponse{}
|
queryRes := api.QueryLatestEventsAndStateResponse{}
|
||||||
event, err := eventutil.QueryAndBuildEvent(ctx, proto, cfg.Matrix, identity, evTime, rsAPI, &queryRes)
|
event, err := eventutil.QueryAndBuildEvent(ctx, proto, identity, evTime, rsAPI, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmailAssociationRequest represents the request defined at https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register-email-requesttoken
|
// EmailAssociationRequest represents the request defined at https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register-email-requesttoken
|
||||||
|
|
@ -133,7 +134,7 @@ func CheckAssociation(
|
||||||
return false, "", "", err
|
return false, "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if respBody.ErrCode == "M_SESSION_NOT_VALIDATED" {
|
if respBody.ErrCode == string(spec.ErrorSessionNotValidated) {
|
||||||
return false, "", "", nil
|
return false, "", "", nil
|
||||||
} else if len(respBody.ErrCode) > 0 {
|
} else if len(respBody.ErrCode) > 0 {
|
||||||
return false, "", "", errors.New(respBody.Error)
|
return false, "", "", errors.New(respBody.Error)
|
||||||
|
|
@ -186,5 +187,5 @@ func isTrusted(idServer string, cfg *config.ClientAPI) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrNotTrusted
|
return ErrNotTrusted{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
const userPassword = "this_is_a_long_password"
|
const userPassword = "this_is_a_long_password"
|
||||||
|
|
@ -56,7 +57,7 @@ func runTests(baseURL string, v *semver.Version) error {
|
||||||
|
|
||||||
// create DM room, join it and exchange messages
|
// create DM room, join it and exchange messages
|
||||||
createRoomResp, err := users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
createRoomResp, err := users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||||
Preset: "trusted_private_chat",
|
Preset: spec.PresetTrustedPrivateChat,
|
||||||
Invite: []string{users[1].userID},
|
Invite: []string{users[1].userID},
|
||||||
IsDirect: true,
|
IsDirect: true,
|
||||||
})
|
})
|
||||||
|
|
@ -98,7 +99,7 @@ func runTests(baseURL string, v *semver.Version) error {
|
||||||
publicRoomID := ""
|
publicRoomID := ""
|
||||||
createRoomResp, err = users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
createRoomResp, err = users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||||
RoomAliasName: "global",
|
RoomAliasName: "global",
|
||||||
Preset: "public_chat",
|
Preset: spec.PresetPublicChat,
|
||||||
})
|
})
|
||||||
if err != nil { // this is okay and expected if the room already exists and the aliases clash
|
if err != nil { // this is okay and expected if the room already exists and the aliases clash
|
||||||
// try to join it
|
// try to join it
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,16 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
"github.com/matrix-org/dendrite/roomserver/state"
|
"github.com/matrix-org/dendrite/roomserver/state"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup"
|
"github.com/matrix-org/dendrite/setup"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is a utility for inspecting state snapshots and running state resolution
|
// This is a utility for inspecting state snapshots and running state resolution
|
||||||
|
|
@ -65,10 +68,14 @@ func main() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
natsInstance := &jetstream.NATSInstance{}
|
||||||
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm,
|
||||||
|
natsInstance, caching.NewRistrettoCache(128*1024*1024, time.Hour, true), false)
|
||||||
|
|
||||||
roomInfo := &types.RoomInfo{
|
roomInfo := &types.RoomInfo{
|
||||||
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
||||||
}
|
}
|
||||||
stateres := state.NewStateResolution(roomserverDB, roomInfo)
|
stateres := state.NewStateResolution(roomserverDB, roomInfo, rsAPI)
|
||||||
|
|
||||||
if *difference {
|
if *difference {
|
||||||
if len(snapshotNIDs) != 2 {
|
if len(snapshotNIDs) != 2 {
|
||||||
|
|
@ -91,7 +98,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventEntries []types.Event
|
var eventEntries []types.Event
|
||||||
eventEntries, err = roomserverDB.Events(ctx, roomInfo, eventNIDs)
|
eventEntries, err = roomserverDB.Events(ctx, roomInfo.RoomVersion, eventNIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +156,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Fetching", len(eventNIDMap), "state events")
|
fmt.Println("Fetching", len(eventNIDMap), "state events")
|
||||||
eventEntries, err := roomserverDB.Events(ctx, roomInfo, eventNIDs)
|
eventEntries, err := roomserverDB.Events(ctx, roomInfo.RoomVersion, eventNIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +189,9 @@ func main() {
|
||||||
fmt.Println("Resolving state")
|
fmt.Println("Resolving state")
|
||||||
var resolved Events
|
var resolved Events
|
||||||
resolved, err = gomatrixserverlib.ResolveConflicts(
|
resolved, err = gomatrixserverlib.ResolveConflicts(
|
||||||
gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents,
|
gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
|
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,7 @@ global:
|
||||||
# e.g. localhost:443
|
# e.g. localhost:443
|
||||||
well_known_server_name: ""
|
well_known_server_name: ""
|
||||||
|
|
||||||
# The server name to delegate client-server communications to, with optional port
|
# The base URL to delegate client-server communications to e.g. https://localhost
|
||||||
# e.g. localhost:443
|
|
||||||
well_known_client_name: ""
|
well_known_client_name: ""
|
||||||
|
|
||||||
# Lists of domains that the server will trust as identity servers to verify third
|
# Lists of domains that the server will trust as identity servers to verify third
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ No, although a good portion of the Matrix specification has been implemented. Mo
|
||||||
|
|
||||||
Dendrite development is currently supported by a small team of developers and due to those limited resources, the majority of the effort is focused on getting Dendrite to be
|
Dendrite development is currently supported by a small team of developers and due to those limited resources, the majority of the effort is focused on getting Dendrite to be
|
||||||
specification complete. If there are major features you're requesting (e.g. new administration endpoints), we'd like to strongly encourage you to join the community in supporting
|
specification complete. If there are major features you're requesting (e.g. new administration endpoints), we'd like to strongly encourage you to join the community in supporting
|
||||||
the development efforts through [contributing](https://matrix-org.github.io/dendrite/development/contributing).
|
the development efforts through [contributing](../development/contributing).
|
||||||
|
|
||||||
## Is there a migration path from Synapse to Dendrite?
|
## Is there a migration path from Synapse to Dendrite?
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ This can be done by performing a room upgrade. Use the command `/upgraderoom <ve
|
||||||
|
|
||||||
## How do I reset somebody's password on my server?
|
## How do I reset somebody's password on my server?
|
||||||
|
|
||||||
Use the admin endpoint [resetpassword](https://matrix-org.github.io/dendrite/administration/adminapi#post-_dendriteadminresetpassworduserid)
|
Use the admin endpoint [resetpassword](./administration/adminapi#post-_dendriteadminresetpassworduserid)
|
||||||
|
|
||||||
## Should I use PostgreSQL or SQLite for my databases?
|
## Should I use PostgreSQL or SQLite for my databases?
|
||||||
|
|
||||||
|
|
@ -157,7 +157,7 @@ You may need to revisit the connection limit of your PostgreSQL server and/or ma
|
||||||
|
|
||||||
## VOIP and Video Calls don't appear to work on Dendrite
|
## VOIP and Video Calls don't appear to work on Dendrite
|
||||||
|
|
||||||
There is likely an issue with your STUN/TURN configuration on the server. If you believe your configuration to be correct, please see the [troubleshooting](administration/5_troubleshooting.md) for troubleshooting recommendations.
|
There is likely an issue with your STUN/TURN configuration on the server. If you believe your configuration to be correct, please see the [troubleshooting](administration/6_troubleshooting.md) for troubleshooting recommendations.
|
||||||
|
|
||||||
## What is being reported when enabling phone-home statistics?
|
## What is being reported when enabling phone-home statistics?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ or alternatively, in the [installation](installation/) folder:
|
||||||
|
|
||||||
1. [Planning your deployment](installation/1_planning.md)
|
1. [Planning your deployment](installation/1_planning.md)
|
||||||
2. [Setting up the domain](installation/2_domainname.md)
|
2. [Setting up the domain](installation/2_domainname.md)
|
||||||
3. [Preparing database storage](installation/3_database.md)
|
3. [Installing Dendrite](installation/manual/1_build.md)
|
||||||
4. [Generating signing keys](installation/4_signingkey.md)
|
4. [Preparing database storage](installation/manual/2_database.md)
|
||||||
5. [Installing as a monolith](installation/5_install_monolith.md)
|
5. [Populate the configuration](installation/manual/3_configuration.md)
|
||||||
6. [Populate the configuration](installation/7_configuration.md)
|
6. [Generating signing keys](installation/manual/4_signingkey.md)
|
||||||
7. [Starting the monolith](installation/8_starting_monolith.md)
|
7. [Starting Dendrite](installation/manual/5_starting_dendrite.md)
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,9 @@ User accounts can be created on a Dendrite instance in a number of ways.
|
||||||
|
|
||||||
## From the command line
|
## From the command line
|
||||||
|
|
||||||
The `create-account` tool is built in the `bin` folder when building Dendrite with
|
The `create-account` tool is built in the `bin` folder when [building](../installation/build) Dendrite.
|
||||||
the `build.sh` script.
|
|
||||||
|
|
||||||
It uses the `dendrite.yaml` configuration file to connect to a running Dendrite instance and requires
|
It uses the `dendrite.yaml` configuration file to connect to a **running** Dendrite instance and requires
|
||||||
shared secret registration to be enabled as explained below.
|
shared secret registration to be enabled as explained below.
|
||||||
|
|
||||||
An example of using `create-account` to create a **normal account**:
|
An example of using `create-account` to create a **normal account**:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Supported admin APIs
|
title: Supported admin APIs
|
||||||
parent: Administration
|
parent: Administration
|
||||||
|
nav_order: 4
|
||||||
permalink: /administration/adminapi
|
permalink: /administration/adminapi
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -49,13 +50,17 @@ the room IDs of all affected rooms.
|
||||||
|
|
||||||
## POST `/_dendrite/admin/resetPassword/{userID}`
|
## POST `/_dendrite/admin/resetPassword/{userID}`
|
||||||
|
|
||||||
Reset the password of a local user.
|
Reset the password of a local user.
|
||||||
|
|
||||||
|
**If `logout_devices` is set to `true`, all `access_tokens` will be invalidated, resulting
|
||||||
|
in the potential loss of encrypted messages**
|
||||||
|
|
||||||
Request body format:
|
Request body format:
|
||||||
|
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"password": "new_password_here"
|
"password": "new_password_here",
|
||||||
|
"logout_devices": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -68,11 +73,14 @@ Indexing is done in the background, the server logs every 1000 events (or below)
|
||||||
|
|
||||||
This endpoint instructs Dendrite to immediately query `/devices/{userID}` on a federated server. An empty JSON body will be returned on success, updating all locally stored user devices/keys. This can be used to possibly resolve E2EE issues, where the remote user can't decrypt messages.
|
This endpoint instructs Dendrite to immediately query `/devices/{userID}` on a federated server. An empty JSON body will be returned on success, updating all locally stored user devices/keys. This can be used to possibly resolve E2EE issues, where the remote user can't decrypt messages.
|
||||||
|
|
||||||
|
## POST `/_dendrite/admin/purgeRoom/{roomID}`
|
||||||
|
|
||||||
|
This endpoint instructs Dendrite to remove the given room from its database. Before doing so, it will evacuate all local users from the room. It does **NOT** remove media files. Depending on the size of the room, this may take a while. Will return an empty JSON once other components were instructed to delete the room.
|
||||||
|
|
||||||
## POST `/_synapse/admin/v1/send_server_notice`
|
## POST `/_synapse/admin/v1/send_server_notice`
|
||||||
|
|
||||||
Request body format:
|
Request body format:
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"user_id": "@target_user:server_name",
|
"user_id": "@target_user:server_name",
|
||||||
"content": {
|
"content": {
|
||||||
|
|
@ -85,7 +93,7 @@ Request body format:
|
||||||
Send a server notice to a specific user. See the [Matrix Spec](https://spec.matrix.org/v1.3/client-server-api/#server-notices) for additional details on server notice behaviour.
|
Send a server notice to a specific user. See the [Matrix Spec](https://spec.matrix.org/v1.3/client-server-api/#server-notices) for additional details on server notice behaviour.
|
||||||
If successfully sent, the API will return the following response:
|
If successfully sent, the API will return the following response:
|
||||||
|
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"event_id": "<event_id>"
|
"event_id": "<event_id>"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
title: Optimise your installation
|
title: Optimise your installation
|
||||||
parent: Installation
|
parent: Administration
|
||||||
has_toc: true
|
has_toc: true
|
||||||
nav_order: 11
|
nav_order: 5
|
||||||
permalink: /installation/start/optimisation
|
permalink: /administration/optimisation
|
||||||
---
|
---
|
||||||
|
|
||||||
# Optimise your installation
|
# Optimise your installation
|
||||||
|
|
@ -36,11 +36,6 @@ connections it will open to the database.
|
||||||
**If you are using the `global` database pool** then you only need to configure the
|
**If you are using the `global` database pool** then you only need to configure the
|
||||||
`max_open_conns` setting once in the `global` section.
|
`max_open_conns` setting once in the `global` section.
|
||||||
|
|
||||||
**If you are defining a `database` config per component** then you will need to ensure that
|
|
||||||
the **sum total** of all configured `max_open_conns` to a given database server do not exceed
|
|
||||||
the connection limit. If you configure a total that adds up to more connections than are available
|
|
||||||
then this will cause database queries to fail.
|
|
||||||
|
|
||||||
You may wish to raise the `max_connections` limit on your PostgreSQL server to accommodate
|
You may wish to raise the `max_connections` limit on your PostgreSQL server to accommodate
|
||||||
additional connections, in which case you should also update the `max_open_conns` in your
|
additional connections, in which case you should also update the `max_open_conns` in your
|
||||||
Dendrite configuration accordingly. However be aware that this is only advisable on particularly
|
Dendrite configuration accordingly. However be aware that this is only advisable on particularly
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Troubleshooting
|
title: Troubleshooting
|
||||||
parent: Administration
|
parent: Administration
|
||||||
|
nav_order: 6
|
||||||
permalink: /administration/troubleshooting
|
permalink: /administration/troubleshooting
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ be clues in the logs.
|
||||||
You can increase this log level to the more verbose `debug` level if necessary by adding
|
You can increase this log level to the more verbose `debug` level if necessary by adding
|
||||||
this to the config and restarting Dendrite:
|
this to the config and restarting Dendrite:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
logging:
|
logging:
|
||||||
- type: std
|
- type: std
|
||||||
level: debug
|
level: debug
|
||||||
|
|
@ -56,12 +57,7 @@ number of database connections does not exceed the maximum allowed by PostgreSQL
|
||||||
|
|
||||||
Open your `postgresql.conf` configuration file and check the value of `max_connections`
|
Open your `postgresql.conf` configuration file and check the value of `max_connections`
|
||||||
(which is typically `100` by default). Then open your `dendrite.yaml` configuration file
|
(which is typically `100` by default). Then open your `dendrite.yaml` configuration file
|
||||||
and ensure that:
|
and ensure that in the `global.database` section, `max_open_conns` does not exceed that number.
|
||||||
|
|
||||||
1. If you are using the `global.database` section, that `max_open_conns` does not exceed
|
|
||||||
that number;
|
|
||||||
2. If you are **not** using the `global.database` section, that the sum total of all
|
|
||||||
`max_open_conns` across all `database` blocks does not exceed that number.
|
|
||||||
|
|
||||||
## 5. File descriptors
|
## 5. File descriptors
|
||||||
|
|
||||||
|
|
@ -77,7 +73,7 @@ If there aren't, you will see a log lines like this:
|
||||||
level=warning msg="IMPORTANT: Process file descriptor limit is currently 65535, it is recommended to raise the limit for Dendrite to at least 65535 to avoid issues"
|
level=warning msg="IMPORTANT: Process file descriptor limit is currently 65535, it is recommended to raise the limit for Dendrite to at least 65535 to avoid issues"
|
||||||
```
|
```
|
||||||
|
|
||||||
Follow the [Optimisation](../installation/11_optimisation.md) instructions to correct the
|
Follow the [Optimisation](5_optimisation.md) instructions to correct the
|
||||||
available number of file descriptors.
|
available number of file descriptors.
|
||||||
|
|
||||||
## 6. STUN/TURN Server tester
|
## 6. STUN/TURN Server tester
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
# Sample Caddyfile for using Caddy in front of Dendrite
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
# Customize email address and domain names
|
|
||||||
|
|
||||||
# Optional settings commented out
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
# BE SURE YOUR DOMAINS ARE POINTED AT YOUR SERVER FIRST
|
|
||||||
|
|
||||||
# Documentation: <https://caddyserver.com/docs/>
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
# Bonus tip: If your IP address changes, use Caddy's
|
|
||||||
|
|
||||||
# dynamic DNS plugin to update your DNS records to
|
|
||||||
|
|
||||||
# point to your new IP automatically
|
|
||||||
|
|
||||||
# <https://github.com/mholt/caddy-dynamicdns>
|
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
# Global options block
|
|
||||||
|
|
||||||
{
|
|
||||||
# In case there is a problem with your certificates.
|
|
||||||
# email example@example.com
|
|
||||||
|
|
||||||
# Turn off the admin endpoint if you don't need graceful config
|
|
||||||
# changes and/or are running untrusted code on your machine.
|
|
||||||
# admin off
|
|
||||||
|
|
||||||
# Enable this if your clients don't send ServerName in TLS handshakes.
|
|
||||||
# default_sni example.com
|
|
||||||
|
|
||||||
# Enable debug mode for verbose logging.
|
|
||||||
# debug
|
|
||||||
|
|
||||||
# Use Let's Encrypt's staging endpoint for testing.
|
|
||||||
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
|
||||||
|
|
||||||
# If you're port-forwarding HTTP/HTTPS ports from 80/443 to something
|
|
||||||
# else, enable these and put the alternate port numbers here.
|
|
||||||
# http_port 8080
|
|
||||||
# https_port 8443
|
|
||||||
}
|
|
||||||
|
|
||||||
# The server name of your matrix homeserver. This example shows
|
|
||||||
|
|
||||||
# "well-known delegation" from the registered domain to a subdomain
|
|
||||||
|
|
||||||
# which is only needed if your server_name doesn't match your Matrix
|
|
||||||
|
|
||||||
# homeserver URL (i.e. you can show users a vanity domain that looks
|
|
||||||
|
|
||||||
# nice and is easy to remember but still have your Matrix server on
|
|
||||||
|
|
||||||
# its own subdomain or hosted service)
|
|
||||||
|
|
||||||
example.com {
|
|
||||||
header /.well-known/matrix/*Content-Type application/json
|
|
||||||
header /.well-known/matrix/* Access-Control-Allow-Origin *
|
|
||||||
respond /.well-known/matrix/server `{"m.server": "matrix.example.com:443"}`
|
|
||||||
respond /.well-known/matrix/client `{"m.homeserver": {"base_url": "https://matrix.example.com"}}`
|
|
||||||
}
|
|
||||||
|
|
||||||
# The actual domain name whereby your Matrix server is accessed
|
|
||||||
|
|
||||||
matrix.example.com {
|
|
||||||
# Change the end of each reverse_proxy line to the correct
|
|
||||||
# address for your various services.
|
|
||||||
@sync_api {
|
|
||||||
path_regexp /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|.*?_?members|context/.*?|relations/.*?|event/.*?))$
|
|
||||||
}
|
|
||||||
reverse_proxy @sync_api sync_api:8073
|
|
||||||
|
|
||||||
reverse_proxy /_matrix/client* client_api:8071
|
|
||||||
reverse_proxy /_matrix/federation* federation_api:8071
|
|
||||||
reverse_proxy /_matrix/key* federation_api:8071
|
|
||||||
reverse_proxy /_matrix/media* media_api:8071
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Contributing
|
title: Contributing
|
||||||
parent: Development
|
parent: Development
|
||||||
|
nav_order: 1
|
||||||
permalink: /development/contributing
|
permalink: /development/contributing
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Profiling
|
title: Profiling
|
||||||
parent: Development
|
parent: Development
|
||||||
|
nav_order: 4
|
||||||
permalink: /development/profiling
|
permalink: /development/profiling
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,130 @@
|
||||||
---
|
---
|
||||||
title: Coverage
|
title: Coverage
|
||||||
parent: Development
|
parent: Development
|
||||||
|
nav_order: 3
|
||||||
permalink: /development/coverage
|
permalink: /development/coverage
|
||||||
---
|
---
|
||||||
|
|
||||||
To generate a test coverage report for Sytest, a small patch needs to be applied to the Sytest repository to compile and use the instrumented binary:
|
## Running unit tests with coverage enabled
|
||||||
```patch
|
|
||||||
diff --git a/lib/SyTest/Homeserver/Dendrite.pm b/lib/SyTest/Homeserver/Dendrite.pm
|
Running unit tests with coverage enabled can be done with the following commands, this will generate a `integrationcover.log`
|
||||||
index 8f0e209c..ad057e52 100644
|
```bash
|
||||||
--- a/lib/SyTest/Homeserver/Dendrite.pm
|
go test -covermode=atomic -coverpkg=./... -coverprofile=integrationcover.log $(go list ./... | grep -v '/cmd/')
|
||||||
+++ b/lib/SyTest/Homeserver/Dendrite.pm
|
go tool cover -func=integrationcover.log
|
||||||
@@ -337,7 +337,7 @@ sub _start_monolith
|
```
|
||||||
|
|
||||||
$output->diag( "Starting monolith server" );
|
## Running Sytest with coverage enabled
|
||||||
my @command = (
|
|
||||||
- $self->{bindir} . '/dendrite',
|
To run Sytest with coverage enabled:
|
||||||
+ $self->{bindir} . '/dendrite', '--test.coverprofile=' . $self->{hs_dir} . '/integrationcover.log', "DEVEL",
|
|
||||||
'--config', $self->{paths}{config},
|
```bash
|
||||||
'--http-bind-address', $self->{bind_host} . ':' . $self->unsecure_port,
|
docker run --rm --name sytest -v "/Users/kegan/github/sytest:/sytest" \
|
||||||
'--https-bind-address', $self->{bind_host} . ':' . $self->secure_port,
|
-v "/Users/kegan/github/dendrite:/src" -v "$(pwd)/sytest_logs:/logs" \
|
||||||
diff --git a/scripts/dendrite_sytest.sh b/scripts/dendrite_sytest.sh
|
-v "/Users/kegan/go/:/gopath" -e "POSTGRES=1" \
|
||||||
index f009332b..7ea79869 100755
|
-e "COVER=1" \
|
||||||
--- a/scripts/dendrite_sytest.sh
|
matrixdotorg/sytest-dendrite:latest
|
||||||
+++ b/scripts/dendrite_sytest.sh
|
|
||||||
@@ -34,7 +34,8 @@ export GOBIN=/tmp/bin
|
# to get a more accurate coverage you may also need to run Sytest using SQLite as the database:
|
||||||
echo >&2 "--- Building dendrite from source"
|
docker run --rm --name sytest -v "/Users/kegan/github/sytest:/sytest" \
|
||||||
cd /src
|
-v "/Users/kegan/github/dendrite:/src" -v "$(pwd)/sytest_logs:/logs" \
|
||||||
mkdir -p $GOBIN
|
-v "/Users/kegan/go/:/gopath" \
|
||||||
-go install -v ./cmd/dendrite
|
-e "COVER=1" \
|
||||||
+# go install -v ./cmd/dendrite
|
matrixdotorg/sytest-dendrite:latest
|
||||||
+go test -c -cover -covermode=atomic -o $GOBIN/dendrite -coverpkg "github.com/matrix-org/..." ./cmd/dendrite
|
```
|
||||||
go install -v ./cmd/generate-keys
|
|
||||||
cd -
|
This will generate a folder `covdatafiles` in each server's directory, e.g `server-0/covdatafiles`. To parse them,
|
||||||
```
|
ensure your working directory is under the Dendrite repository then run:
|
||||||
|
|
||||||
Then run Sytest. This will generate a new file `integrationcover.log` in each server's directory e.g `server-0/integrationcover.log`. To parse it,
|
|
||||||
ensure your working directory is under the Dendrite repository then run:
|
|
||||||
```bash
|
```bash
|
||||||
go tool cover -func=/path/to/server-0/integrationcover.log
|
go tool covdata func -i="$(find -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
||||||
```
|
```
|
||||||
which will produce an output like:
|
which will produce an output like:
|
||||||
```
|
```
|
||||||
...
|
...
|
||||||
github.com/matrix-org/util/json.go:83: NewJSONRequestHandler 100.0%
|
github.com/matrix-org/util/json.go:132: MakeJSONAPI 70.0%
|
||||||
github.com/matrix-org/util/json.go:90: Protect 57.1%
|
github.com/matrix-org/util/json.go:151: respond 84.6%
|
||||||
github.com/matrix-org/util/json.go:110: RequestWithLogging 100.0%
|
github.com/matrix-org/util/json.go:180: WithCORSOptions 0.0%
|
||||||
github.com/matrix-org/util/json.go:132: MakeJSONAPI 70.0%
|
github.com/matrix-org/util/json.go:191: SetCORSHeaders 100.0%
|
||||||
github.com/matrix-org/util/json.go:151: respond 61.5%
|
github.com/matrix-org/util/json.go:202: RandomString 100.0%
|
||||||
github.com/matrix-org/util/json.go:180: WithCORSOptions 0.0%
|
github.com/matrix-org/util/json.go:210: init 100.0%
|
||||||
github.com/matrix-org/util/json.go:191: SetCORSHeaders 100.0%
|
github.com/matrix-org/util/unique.go:13: Unique 91.7%
|
||||||
github.com/matrix-org/util/json.go:202: RandomString 100.0%
|
github.com/matrix-org/util/unique.go:48: SortAndUnique 100.0%
|
||||||
github.com/matrix-org/util/json.go:210: init 100.0%
|
github.com/matrix-org/util/unique.go:55: UniqueStrings 100.0%
|
||||||
github.com/matrix-org/util/unique.go:13: Unique 91.7%
|
total (statements) 64.0%
|
||||||
github.com/matrix-org/util/unique.go:48: SortAndUnique 100.0%
|
|
||||||
github.com/matrix-org/util/unique.go:55: UniqueStrings 100.0%
|
|
||||||
total: (statements) 53.7%
|
|
||||||
```
|
```
|
||||||
The total coverage for this run is the last line at the bottom. However, this value is misleading because Dendrite can run in many different configurations,
|
(after running Sytest for Postgres _and_ SQLite)
|
||||||
which will never be tested in a single test run (e.g sqlite or postgres). To get a more accurate value, additional processing is required
|
|
||||||
to remove packages which will never be tested and extension MSCs:
|
The total coverage for this run is the last line at the bottom. However, this value is misleading because Dendrite can run in different configurations,
|
||||||
|
which will never be tested in a single test run (e.g sqlite or postgres). To get a more accurate value, you'll need run Sytest for Postgres and SQLite (see commands above).
|
||||||
|
Additional processing is required also to remove packages which will never be tested and extension MSCs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# These commands are all similar but change which package paths are _removed_ from the output.
|
# If you executed both commands from above, you can get the total coverage using the following commands
|
||||||
|
go tool covdata textfmt -i="$(find -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o sytest.cov
|
||||||
|
grep -Ev 'relayapi|setup/mscs' sytest.cov > final.cov
|
||||||
|
go tool cover -func=final.cov
|
||||||
|
|
||||||
# For Postgres
|
# If you only executed the one for Postgres:
|
||||||
go tool cover -func=/path/to/server-0/integrationcover.log | grep 'github.com/matrix-org/dendrite' | grep -Ev 'inthttp|sqlite|setup/mscs|api_trace' > coverage.txt
|
go tool covdata textfmt -i="$(find -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o sytest.cov
|
||||||
|
grep -Ev 'relayapi|sqlite|setup/mscs' sytest.cov > final.cov
|
||||||
|
go tool cover -func=final.cov
|
||||||
|
|
||||||
# For SQLite
|
# If you only executed the one for SQLite:
|
||||||
go tool cover -func=/path/to/server-0/integrationcover.log | grep 'github.com/matrix-org/dendrite' | grep -Ev 'inthttp|postgres|setup/mscs|api_trace' > coverage.txt
|
go tool covdata textfmt -i="$(find -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o sytest.cov
|
||||||
|
grep -Ev 'relayapi|postgres|setup/mscs' sytest.cov > final.cov
|
||||||
|
go tool cover -func=final.cov
|
||||||
```
|
```
|
||||||
|
|
||||||
A total value can then be calculated using:
|
## Getting coverage from Complement
|
||||||
|
|
||||||
|
Getting the coverage for Complement runs is a bit more involved.
|
||||||
|
|
||||||
|
First you'll need a docker image compatible with Complement, one can be built using
|
||||||
```bash
|
```bash
|
||||||
cat coverage.txt | awk -F '\t+' '{x = x + $3} END {print x/NR}'
|
docker build -t complement-dendrite -f build/scripts/Complement.Dockerfile .
|
||||||
|
```
|
||||||
|
from within the Dendrite repository.
|
||||||
|
|
||||||
|
Clone complement to a directory of your liking:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/matrix-org/complement.git
|
||||||
|
cd complement
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Next we'll need a script to execute after a test finishes, create a new file `posttest.sh`, make the file executable (`chmod +x posttest.sh`)
|
||||||
|
and add the following content:
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
We currently do not have a way to combine Sytest/Complement/Unit Tests into a single coverage report.
|
mkdir -p /tmp/Complement/logs/$2/$1/
|
||||||
|
docker cp $1:/tmp/covdatafiles/. /tmp/Complement/logs/$2/$1/
|
||||||
|
```
|
||||||
|
This will copy the `covdatafiles` files from each container to something like
|
||||||
|
`/tmp/Complement/logs/TestLogin/94f9c428de95779d2b62a3ccd8eab9d5ddcf65cc259a40ece06bdc61687ffed3/`. (`$1` is the containerID, `$2` the test name)
|
||||||
|
|
||||||
|
Now that we have set up everything we need, we can finally execute Complement:
|
||||||
|
```bash
|
||||||
|
COMPLEMENT_BASE_IMAGE=complement-dendrite \
|
||||||
|
COMPLEMENT_SHARE_ENV_PREFIX=COMPLEMENT_DENDRITE_ \
|
||||||
|
COMPLEMENT_DENDRITE_COVER=1 \
|
||||||
|
COMPLEMENT_POST_TEST_SCRIPT=$(pwd)/posttest.sh \
|
||||||
|
go test -tags dendrite_blacklist ./tests/... -count=1 -v -timeout=30m -failfast=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Once this is done, you can copy the resulting `covdatafiles` files to your Dendrite repository for the next step.
|
||||||
|
```bash
|
||||||
|
cp -pr /tmp/Complement/logs PathToYourDendriteRepository
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run the following to get the coverage for Complement runs alone:
|
||||||
|
```bash
|
||||||
|
go tool covdata func -i="$(find /tmp/Complement -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining the results of (almost) all runs
|
||||||
|
|
||||||
|
Now that we have all our `covdatafiles` files within the Dendrite repository, you can now execute the following command, to get the coverage
|
||||||
|
overall (excluding unit tests):
|
||||||
|
```bash
|
||||||
|
go tool covdata func -i="$(find -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
||||||
|
```
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: SyTest
|
title: SyTest
|
||||||
parent: Development
|
parent: Development
|
||||||
|
nav_order: 2
|
||||||
permalink: /development/sytest
|
permalink: /development/sytest
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ After running the tests, a script will print the tests you need to add to
|
||||||
You should proceed after you see no build problems for dendrite after running:
|
You should proceed after you see no build problems for dendrite after running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./build.sh
|
go build -o bin/ ./cmd/...
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are fixing an issue marked with
|
If you are fixing an issue marked with
|
||||||
|
|
@ -61,6 +62,8 @@ When debugging, the following Docker `run` options may also be useful:
|
||||||
* `-e "DENDRITE_TRACE_HTTP=1"`: Adds HTTP tracing to server logs.
|
* `-e "DENDRITE_TRACE_HTTP=1"`: Adds HTTP tracing to server logs.
|
||||||
* `-e "DENDRITE_TRACE_INTERNAL=1"`: Adds roomserver internal API tracing to
|
* `-e "DENDRITE_TRACE_INTERNAL=1"`: Adds roomserver internal API tracing to
|
||||||
server logs.
|
server logs.
|
||||||
|
* `-e "COVER=1"`: Run Sytest with an instrumented binary, producing a Go coverage file per server.
|
||||||
|
* `-e "RACE_DETECTION=1"`: Build the binaries with the `-race` flag (Note: This will significantly slow down test runs)
|
||||||
|
|
||||||
The docker command also supports a single positional argument for the test file to
|
The docker command also supports a single positional argument for the test file to
|
||||||
run, so you can run a single `.pl` file rather than the whole test suite. For example:
|
run, so you can run a single `.pl` file rather than the whole test suite. For example:
|
||||||
|
|
@ -71,68 +74,3 @@ docker run --rm --name sytest -v "/Users/kegan/github/sytest:/sytest"
|
||||||
-v "/Users/kegan/go/:/gopath" -e "POSTGRES=1" -e "DENDRITE_TRACE_HTTP=1"
|
-v "/Users/kegan/go/:/gopath" -e "POSTGRES=1" -e "DENDRITE_TRACE_HTTP=1"
|
||||||
matrixdotorg/sytest-dendrite:latest tests/50federation/40devicelists.pl
|
matrixdotorg/sytest-dendrite:latest tests/50federation/40devicelists.pl
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manually Setting up SyTest
|
|
||||||
|
|
||||||
**We advise AGAINST using manual SyTest setups.**
|
|
||||||
|
|
||||||
If you don't want to use the Docker image, you can also run SyTest by hand. Make
|
|
||||||
sure you have Perl 5 or above, and get SyTest with:
|
|
||||||
|
|
||||||
(Note that this guide assumes your SyTest checkout is next to your
|
|
||||||
`dendrite` checkout.)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone -b develop https://github.com/matrix-org/sytest
|
|
||||||
cd sytest
|
|
||||||
./install-deps.pl
|
|
||||||
```
|
|
||||||
|
|
||||||
Set up the database:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo -u postgres psql -c "CREATE USER dendrite PASSWORD 'itsasecret'"
|
|
||||||
sudo -u postgres psql -c "ALTER USER dendrite CREATEDB"
|
|
||||||
for i in dendrite0 dendrite1 sytest_template; do sudo -u postgres psql -c "CREATE DATABASE $i OWNER dendrite;"; done
|
|
||||||
mkdir -p "server-0"
|
|
||||||
cat > "server-0/database.yaml" << EOF
|
|
||||||
args:
|
|
||||||
user: dendrite
|
|
||||||
password: itsasecret
|
|
||||||
database: dendrite0
|
|
||||||
host: 127.0.0.1
|
|
||||||
sslmode: disable
|
|
||||||
type: pg
|
|
||||||
EOF
|
|
||||||
mkdir -p "server-1"
|
|
||||||
cat > "server-1/database.yaml" << EOF
|
|
||||||
args:
|
|
||||||
user: dendrite
|
|
||||||
password: itsasecret
|
|
||||||
database: dendrite1
|
|
||||||
host: 127.0.0.1
|
|
||||||
sslmode: disable
|
|
||||||
type: pg
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the tests:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
POSTGRES=1 ./run-tests.pl -I Dendrite::Monolith -d ../dendrite/bin -W ../dendrite/sytest-whitelist -O tap --all | tee results.tap
|
|
||||||
```
|
|
||||||
|
|
||||||
where `tee` lets you see the results while they're being piped to the file, and
|
|
||||||
`POSTGRES=1` enables testing with PostgeSQL. If the `POSTGRES` environment
|
|
||||||
variable is not set or is set to 0, SyTest will fall back to SQLite 3. For more
|
|
||||||
flags and options, see <https://github.com/matrix-org/sytest#running>.
|
|
||||||
|
|
||||||
Once the tests are complete, run the helper script to see if you need to add
|
|
||||||
any newly passing test names to `sytest-whitelist` in the project's root
|
|
||||||
directory:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
../dendrite/show-expected-fail-tests.sh results.tap ../dendrite/sytest-whitelist ../dendrite/sytest-blacklist
|
|
||||||
```
|
|
||||||
|
|
||||||
If the script prints nothing/exits with 0, then you're good to go.
|
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
---
|
|
||||||
title: OpenTracing
|
|
||||||
has_children: true
|
|
||||||
parent: Development
|
|
||||||
permalink: /development/opentracing
|
|
||||||
---
|
|
||||||
|
|
||||||
# OpenTracing
|
|
||||||
|
|
||||||
Dendrite extensively uses the [opentracing.io](http://opentracing.io) framework
|
|
||||||
to trace work across the different logical components.
|
|
||||||
|
|
||||||
At its most basic opentracing tracks "spans" of work; recording start and end
|
|
||||||
times as well as any parent span that caused the piece of work.
|
|
||||||
|
|
||||||
A typical example would be a new span being created on an incoming request that
|
|
||||||
finishes when the response is sent. When the code needs to hit out to a
|
|
||||||
different component a new span is created with the initial span as its parent.
|
|
||||||
This would end up looking roughly like:
|
|
||||||
|
|
||||||
```
|
|
||||||
Received request Sent response
|
|
||||||
|<───────────────────────────────────────>|
|
|
||||||
|<────────────────────>|
|
|
||||||
RPC call RPC call returns
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful to see where the time is being spent processing a request on a
|
|
||||||
component. However, opentracing allows tracking of spans across components. This
|
|
||||||
makes it possible to see exactly what work goes into processing a request:
|
|
||||||
|
|
||||||
```
|
|
||||||
Component 1 |<─────────────────── HTTP ────────────────────>|
|
|
||||||
|<──────────────── RPC ─────────────────>|
|
|
||||||
Component 2 |<─ SQL ─>| |<── RPC ───>|
|
|
||||||
Component 3 |<─ SQL ─>|
|
|
||||||
```
|
|
||||||
|
|
||||||
This is achieved by serializing span information during all communication
|
|
||||||
between components. For HTTP requests, this is achieved by the sender
|
|
||||||
serializing the span into a HTTP header, and the receiver deserializing the span
|
|
||||||
on receipt. (Generally a new span is then immediately created with the
|
|
||||||
deserialized span as the parent).
|
|
||||||
|
|
||||||
A collection of spans that are related is called a trace.
|
|
||||||
|
|
||||||
Spans are passed through the code via contexts, rather than manually. It is
|
|
||||||
therefore important that all spans that are created are immediately added to the
|
|
||||||
current context. Thankfully the opentracing library gives helper functions for
|
|
||||||
doing this:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
span, ctx := opentracing.StartSpanFromContext(ctx, spanName)
|
|
||||||
defer span.Finish()
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a new span, adding any span already in `ctx` as a parent to the
|
|
||||||
new span.
|
|
||||||
|
|
||||||
Adding Information
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Opentracing allows adding information to a trace via three mechanisms:
|
|
||||||
|
|
||||||
- "tags" ─ A span can be tagged with a key/value pair. This is typically
|
|
||||||
information that relates to the span, e.g. for spans created for incoming HTTP
|
|
||||||
requests could include the request path and response codes as tags, spans for
|
|
||||||
SQL could include the query being executed.
|
|
||||||
- "logs" ─ Key/value pairs can be looged at a particular instance in a trace.
|
|
||||||
This can be useful to log e.g. any errors that happen.
|
|
||||||
- "baggage" ─ Arbitrary key/value pairs can be added to a span to which all
|
|
||||||
child spans have access. Baggage isn't saved and so isn't available when
|
|
||||||
inspecting the traces, but can be used to add context to logs or tags in child
|
|
||||||
spans.
|
|
||||||
|
|
||||||
See
|
|
||||||
[specification.md](https://github.com/opentracing/specification/blob/master/specification.md)
|
|
||||||
for some of the common tags and log fields used.
|
|
||||||
|
|
||||||
Span Relationships
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Spans can be related to each other. The most common relation is `childOf`, which
|
|
||||||
indicates the child span somehow depends on the parent span ─ typically the
|
|
||||||
parent span cannot complete until all child spans are completed.
|
|
||||||
|
|
||||||
A second relation type is `followsFrom`, where the parent has no dependence on
|
|
||||||
the child span. This usually indicates some sort of fire and forget behaviour,
|
|
||||||
e.g. adding a message to a pipeline or inserting into a kafka topic.
|
|
||||||
|
|
||||||
Jaeger
|
|
||||||
------
|
|
||||||
|
|
||||||
Opentracing is just a framework. We use
|
|
||||||
[jaeger](https://github.com/jaegertracing/jaeger) as the actual implementation.
|
|
||||||
|
|
||||||
Jaeger is responsible for recording, sending and saving traces, as well as
|
|
||||||
giving a UI for viewing and interacting with traces.
|
|
||||||
|
|
||||||
To enable jaeger a `Tracer` object must be instansiated from the config (as well
|
|
||||||
as having a jaeger server running somewhere, usually locally). A `Tracer` does
|
|
||||||
several things:
|
|
||||||
|
|
||||||
- Decides which traces to save and send to the server. There are multiple
|
|
||||||
schemes for doing this, with a simple example being to save a certain fraction
|
|
||||||
of traces.
|
|
||||||
- Communicating with the jaeger backend. If not explicitly specified uses the
|
|
||||||
default port on localhost.
|
|
||||||
- Associates a service name to all spans created by the tracer. This service
|
|
||||||
name equates to a logical component, e.g. spans created by clientapi will have
|
|
||||||
a different service name than ones created by the syncapi. Database access
|
|
||||||
will also typically use a different service name.
|
|
||||||
|
|
||||||
This means that there is a tracer per service name/component.
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
---
|
|
||||||
title: Setup
|
|
||||||
parent: OpenTracing
|
|
||||||
grand_parent: Development
|
|
||||||
permalink: /development/opentracing/setup
|
|
||||||
---
|
|
||||||
|
|
||||||
# OpenTracing Setup
|
|
||||||
|
|
||||||
Dendrite uses [Jaeger](https://www.jaegertracing.io/) for tracing between microservices.
|
|
||||||
Tracing shows the nesting of logical spans which provides visibility on how the microservices interact.
|
|
||||||
This document explains how to set up Jaeger locally on a single machine.
|
|
||||||
|
|
||||||
## Set up the Jaeger backend
|
|
||||||
|
|
||||||
The [easiest way](https://www.jaegertracing.io/docs/1.18/getting-started/) is to use the all-in-one Docker image:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ docker run -d --name jaeger \
|
|
||||||
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
|
|
||||||
-p 5775:5775/udp \
|
|
||||||
-p 6831:6831/udp \
|
|
||||||
-p 6832:6832/udp \
|
|
||||||
-p 5778:5778 \
|
|
||||||
-p 16686:16686 \
|
|
||||||
-p 14268:14268 \
|
|
||||||
-p 14250:14250 \
|
|
||||||
-p 9411:9411 \
|
|
||||||
jaegertracing/all-in-one:1.18
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuring Dendrite to talk to Jaeger
|
|
||||||
|
|
||||||
Modify your config to look like: (this will send every single span to Jaeger which will be slow on large instances, but for local testing it's fine)
|
|
||||||
|
|
||||||
```
|
|
||||||
tracing:
|
|
||||||
enabled: true
|
|
||||||
jaeger:
|
|
||||||
serviceName: "dendrite"
|
|
||||||
disabled: false
|
|
||||||
rpc_metrics: true
|
|
||||||
tags: []
|
|
||||||
sampler:
|
|
||||||
type: const
|
|
||||||
param: 1
|
|
||||||
```
|
|
||||||
|
|
||||||
then run the monolith server:
|
|
||||||
|
|
||||||
```
|
|
||||||
./dendrite --tls-cert server.crt --tls-key server.key --config dendrite.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Checking traces
|
|
||||||
|
|
||||||
Visit <http://localhost:16686> to see traces under `DendriteMonolith`.
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# Depending on which port is used for federation (.well-known/matrix/server or SRV record),
|
|
||||||
# ensure there's a binding for that port in the configuration. Replace "FEDPORT" with port
|
|
||||||
# number, (e.g. "8448"), and "IPV4" with your server's ipv4 address (separate binding for
|
|
||||||
# each ip address, e.g. if you use both ipv4 and ipv6 addresses).
|
|
||||||
|
|
||||||
Binding {
|
|
||||||
Port = FEDPORT
|
|
||||||
Interface = IPV4
|
|
||||||
TLScertFile = /path/to/fullchainandprivkey.pem
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
VirtualHost {
|
|
||||||
...
|
|
||||||
# route requests to:
|
|
||||||
# /_matrix/client/.*/sync
|
|
||||||
# /_matrix/client/.*/user/{userId}/filter
|
|
||||||
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
|
||||||
# /_matrix/client/.*/keys/changes
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/messages
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/context/{eventID}
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/event/{eventID}
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}/{relType}
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}/{relType}/{eventType}
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/members
|
|
||||||
# /_matrix/client/.*/rooms/{roomId}/joined_members
|
|
||||||
# to sync_api
|
|
||||||
ReverseProxy = /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|.*?_?members|context/.*?|relations/.*?|event/.*?))$ http://localhost:8073 600
|
|
||||||
ReverseProxy = /_matrix/client http://localhost:8071 600
|
|
||||||
ReverseProxy = /_matrix/federation http://localhost:8072 600
|
|
||||||
ReverseProxy = /_matrix/key http://localhost:8072 600
|
|
||||||
ReverseProxy = /_matrix/media http://localhost:8074 600
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
@ -7,23 +7,13 @@ permalink: /installation/planning
|
||||||
|
|
||||||
# Planning your installation
|
# Planning your installation
|
||||||
|
|
||||||
## Modes
|
## Database
|
||||||
|
|
||||||
Dendrite consists of several components, each responsible for a different aspect of the Matrix protocol.
|
|
||||||
Users can run Dendrite in one of two modes which dictate how these components are executed and communicate.
|
|
||||||
|
|
||||||
* **Monolith mode** runs all components in a single process. Components communicate through an internal NATS
|
|
||||||
server with generally low overhead. This mode dramatically simplifies deployment complexity and offers the
|
|
||||||
best balance between performance and resource usage for low-to-mid volume deployments.
|
|
||||||
|
|
||||||
|
|
||||||
## Databases
|
|
||||||
|
|
||||||
Dendrite can run with either a PostgreSQL or a SQLite backend. There are considerable tradeoffs
|
Dendrite can run with either a PostgreSQL or a SQLite backend. There are considerable tradeoffs
|
||||||
to consider:
|
to consider:
|
||||||
|
|
||||||
* **PostgreSQL**: Needs to run separately to Dendrite, needs to be installed and configured separately
|
* **PostgreSQL**: Needs to run separately to Dendrite, needs to be installed and configured separately
|
||||||
and and will use more resources over all, but will be **considerably faster** than SQLite. PostgreSQL
|
and will use more resources over all, but will be **considerably faster** than SQLite. PostgreSQL
|
||||||
has much better write concurrency which will allow Dendrite to process more tasks in parallel. This
|
has much better write concurrency which will allow Dendrite to process more tasks in parallel. This
|
||||||
will be necessary for federated deployments to perform adequately.
|
will be necessary for federated deployments to perform adequately.
|
||||||
|
|
||||||
|
|
@ -80,18 +70,17 @@ If using the PostgreSQL database engine, you should install PostgreSQL 12 or lat
|
||||||
### NATS Server
|
### NATS Server
|
||||||
|
|
||||||
Dendrite comes with a built-in [NATS Server](https://github.com/nats-io/nats-server) and
|
Dendrite comes with a built-in [NATS Server](https://github.com/nats-io/nats-server) and
|
||||||
therefore does not need this to be manually installed. If you are planning a monolith installation, you
|
therefore does not need this to be manually installed.
|
||||||
do not need to do anything.
|
|
||||||
|
|
||||||
|
|
||||||
### Reverse proxy
|
### Reverse proxy
|
||||||
|
|
||||||
A reverse proxy such as [Caddy](https://caddyserver.com), [NGINX](https://www.nginx.com) or
|
A reverse proxy such as [Caddy](https://caddyserver.com), [NGINX](https://www.nginx.com) or
|
||||||
[HAProxy](http://www.haproxy.org) is useful for deployments. Configuring those is not covered in this documentation, although sample configurations
|
[HAProxy](http://www.haproxy.org) is useful for deployments. Configuring this is not covered in this documentation, although sample configurations
|
||||||
for [Caddy](https://github.com/matrix-org/dendrite/blob/main/docs/caddy) and
|
for [Caddy](https://github.com/matrix-org/dendrite/blob/main/docs/caddy) and
|
||||||
[NGINX](https://github.com/matrix-org/dendrite/blob/main/docs/nginx) are provided.
|
[NGINX](https://github.com/matrix-org/dendrite/blob/main/docs/nginx) are provided.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Finally, if you want to build Dendrite on Windows, you will need need `gcc` in the path. The best
|
Finally, if you want to build Dendrite on Windows, you will need `gcc` in the path. The best
|
||||||
way to achieve this is by installing and building Dendrite under [MinGW-w64](https://www.mingw-w64.org/).
|
way to achieve this is by installing and building Dendrite under [MinGW-w64](https://www.mingw-w64.org/).
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Matrix servers usually discover each other when federating using the following m
|
||||||
well-known file to connect to the remote homeserver;
|
well-known file to connect to the remote homeserver;
|
||||||
2. If a DNS SRV delegation exists on `example.com`, use the IP address and port from the DNS SRV
|
2. If a DNS SRV delegation exists on `example.com`, use the IP address and port from the DNS SRV
|
||||||
record to connect to the remote homeserver;
|
record to connect to the remote homeserver;
|
||||||
3. If neither well-known or DNS SRV delegation are configured, attempt to connect to the remote
|
3. If neither well-known nor DNS SRV delegation are configured, attempt to connect to the remote
|
||||||
homeserver by connecting to `example.com` port TCP/8448 using HTTPS.
|
homeserver by connecting to `example.com` port TCP/8448 using HTTPS.
|
||||||
|
|
||||||
The exact details of how server name resolution works can be found in
|
The exact details of how server name resolution works can be found in
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
title: Installing as a monolith
|
|
||||||
parent: Installation
|
|
||||||
has_toc: true
|
|
||||||
nav_order: 5
|
|
||||||
permalink: /installation/install/monolith
|
|
||||||
---
|
|
||||||
|
|
||||||
# Installing as a monolith
|
|
||||||
|
|
||||||
You can install the Dendrite monolith binary into `$GOPATH/bin` by using `go install`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go install ./cmd/dendrite
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can specify a custom path for the binary to be written to using `go build`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go build -o /usr/local/bin/ ./cmd/dendrite
|
|
||||||
```
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
---
|
|
||||||
title: Starting the monolith
|
|
||||||
parent: Installation
|
|
||||||
has_toc: true
|
|
||||||
nav_order: 9
|
|
||||||
permalink: /installation/start/monolith
|
|
||||||
---
|
|
||||||
|
|
||||||
# Starting the monolith
|
|
||||||
|
|
||||||
Once you have completed all of the preparation and installation steps,
|
|
||||||
you can start your Dendrite monolith deployment by starting `dendrite`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./dendrite -config /path/to/dendrite.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, Dendrite will listen HTTP on port 8008. If you want to change the addresses
|
|
||||||
or ports that Dendrite listens on, you can use the `-http-bind-address` and
|
|
||||||
`-https-bind-address` command line arguments:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./dendrite -config /path/to/dendrite.yaml \
|
|
||||||
-http-bind-address 1.2.3.4:12345 \
|
|
||||||
-https-bind-address 1.2.3.4:54321
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running under systemd
|
|
||||||
|
|
||||||
A common deployment pattern is to run the monolith under systemd. For this, you
|
|
||||||
will need to create a service unit file. An example service unit file is available
|
|
||||||
in the [GitHub repository](https://github.com/matrix-org/dendrite/blob/main/docs/systemd/monolith-example.service).
|
|
||||||
|
|
||||||
Once you have installed the service unit, you can notify systemd, enable and start
|
|
||||||
the service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable dendrite
|
|
||||||
systemctl start dendrite
|
|
||||||
journalctl -fu dendrite
|
|
||||||
```
|
|
||||||
11
docs/installation/docker.md
Normal file
11
docs/installation/docker.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: Docker
|
||||||
|
parent: Installation
|
||||||
|
has_children: true
|
||||||
|
nav_order: 4
|
||||||
|
permalink: /docker
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installation using Docker
|
||||||
|
|
||||||
|
This section contains documentation how to install Dendrite using Docker
|
||||||
57
docs/installation/docker/1_docker.md
Normal file
57
docs/installation/docker/1_docker.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
parent: Docker
|
||||||
|
grand_parent: Installation
|
||||||
|
has_toc: true
|
||||||
|
nav_order: 1
|
||||||
|
permalink: /installation/docker/install
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installing Dendrite using Docker Compose
|
||||||
|
|
||||||
|
Dendrite provides an [example](https://github.com/matrix-org/dendrite/blob/main/build/docker/docker-compose.yml)
|
||||||
|
Docker compose file, which needs some preparation to start successfully.
|
||||||
|
Please note that this compose file only has Postgres as a dependency, and you need to configure
|
||||||
|
a [reverse proxy](../planning#reverse-proxy).
|
||||||
|
|
||||||
|
## Preparations
|
||||||
|
|
||||||
|
### Generate a private key
|
||||||
|
|
||||||
|
First we'll generate private key, which is used to sign events, the following will create one in `./config`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ./config
|
||||||
|
docker run --rm --entrypoint="/usr/bin/generate-keys" \
|
||||||
|
-v $(pwd)/config:/mnt \
|
||||||
|
matrixdotorg/dendrite-monolith:latest \
|
||||||
|
-private-key /mnt/matrix_key.pem
|
||||||
|
```
|
||||||
|
(**NOTE**: This only needs to be executed **once**, as you otherwise overwrite the key)
|
||||||
|
|
||||||
|
### Generate a config
|
||||||
|
|
||||||
|
Similar to the command above, we can generate a config to be used, which will use the correct paths
|
||||||
|
as specified in the example docker-compose file. Change `server` to your domain and `db` according to your changes
|
||||||
|
to the docker-compose file (`services.postgres.environment` values):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ./config
|
||||||
|
docker run --rm --entrypoint="/bin/sh" \
|
||||||
|
-v $(pwd)/config:/mnt \
|
||||||
|
matrixdotorg/dendrite-monolith:latest \
|
||||||
|
-c "/usr/bin/generate-config \
|
||||||
|
-dir /var/dendrite/ \
|
||||||
|
-db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable \
|
||||||
|
-server YourDomainHere > /mnt/dendrite.yaml"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then change `config/dendrite.yaml` to your liking.
|
||||||
|
|
||||||
|
## Starting Dendrite
|
||||||
|
|
||||||
|
Once you're done changing the config, you can now start up Dendrite with
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.yml up
|
||||||
|
```
|
||||||
11
docs/installation/helm.md
Normal file
11
docs/installation/helm.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: Helm
|
||||||
|
parent: Installation
|
||||||
|
has_children: true
|
||||||
|
nav_order: 3
|
||||||
|
permalink: /helm
|
||||||
|
---
|
||||||
|
|
||||||
|
# Helm
|
||||||
|
|
||||||
|
This section contains documentation how to use [Helm](https://helm.sh/) to install Dendrite on a [Kubernetes](https://kubernetes.io/) cluster.
|
||||||
58
docs/installation/helm/1_helm.md
Normal file
58
docs/installation/helm/1_helm.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
parent: Helm
|
||||||
|
grand_parent: Installation
|
||||||
|
has_toc: true
|
||||||
|
nav_order: 1
|
||||||
|
permalink: /installation/helm/install
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installing Dendrite using Helm
|
||||||
|
|
||||||
|
To install Dendrite using the Helm chart, you first have to add the repository using the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm repo add dendrite https://matrix-org.github.io/dendrite/
|
||||||
|
helm repo update
|
||||||
|
```
|
||||||
|
|
||||||
|
Next you'll need to create a `values.yaml` file and configure it to your liking. All possible values can be found
|
||||||
|
[here](https://github.com/matrix-org/dendrite/blob/main/helm/dendrite/values.yaml), but at least you need to configure
|
||||||
|
a `server_name`, otherwise the chart will complain about it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dendrite_config:
|
||||||
|
global:
|
||||||
|
server_name: "localhost"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are going to use an existing Postgres database, you'll also need to configure this connection:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dendrite_config:
|
||||||
|
global:
|
||||||
|
database:
|
||||||
|
connection_string: "postgresql://PostgresUser:PostgresPassword@PostgresHostName/DendriteDatabaseName"
|
||||||
|
max_open_conns: 90
|
||||||
|
max_idle_conns: 5
|
||||||
|
conn_max_lifetime: -1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing with PostgreSQL
|
||||||
|
|
||||||
|
The chart comes with a dependency on Postgres, which can be installed alongside Dendrite, this needs to be enabled in
|
||||||
|
the `values.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
postgresql:
|
||||||
|
enabled: true # this installs Postgres
|
||||||
|
primary:
|
||||||
|
persistence:
|
||||||
|
size: 1Gi # defines the size for $PGDATA
|
||||||
|
|
||||||
|
dendrite_config:
|
||||||
|
global:
|
||||||
|
server_name: "localhost"
|
||||||
|
```
|
||||||
|
|
||||||
|
Using this option, the `database.connection_string` will be set for you automatically.
|
||||||
11
docs/installation/manual.md
Normal file
11
docs/installation/manual.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
title: Manual
|
||||||
|
parent: Installation
|
||||||
|
has_children: true
|
||||||
|
nav_order: 5
|
||||||
|
permalink: /manual
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manual Installation
|
||||||
|
|
||||||
|
This section contains documentation how to manually install Dendrite
|
||||||
|
|
@ -1,31 +1,26 @@
|
||||||
---
|
---
|
||||||
title: Building Dendrite
|
title: Building/Installing Dendrite
|
||||||
parent: Installation
|
parent: Manual
|
||||||
|
grand_parent: Installation
|
||||||
has_toc: true
|
has_toc: true
|
||||||
nav_order: 3
|
nav_order: 1
|
||||||
permalink: /installation/build
|
permalink: /installation/manual/build
|
||||||
---
|
---
|
||||||
|
|
||||||
# Build all Dendrite commands
|
# Build all Dendrite commands
|
||||||
|
|
||||||
Dendrite has numerous utility commands in addition to the actual server binaries.
|
Dendrite has numerous utility commands in addition to the actual server binaries.
|
||||||
Build them all from the root of the source repo with `build.sh` (Linux/Mac):
|
Build them all from the root of the source repo with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./build.sh
|
go build -o bin/ ./cmd/...
|
||||||
```
|
|
||||||
|
|
||||||
or `build.cmd` (Windows):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
build.cmd
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The resulting binaries will be placed in the `bin` subfolder.
|
The resulting binaries will be placed in the `bin` subfolder.
|
||||||
|
|
||||||
# Installing as a monolith
|
# Installing Dendrite
|
||||||
|
|
||||||
You can install the Dendrite monolith binary into `$GOPATH/bin` by using `go install`:
|
You can install the Dendrite binary into `$GOPATH/bin` by using `go install`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install ./cmd/dendrite
|
go install ./cmd/dendrite
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
---
|
---
|
||||||
title: Preparing database storage
|
title: Preparing database storage
|
||||||
parent: Installation
|
parent: Installation
|
||||||
nav_order: 3
|
nav_order: 2
|
||||||
permalink: /installation/database
|
parent: Manual
|
||||||
|
grand_parent: Installation
|
||||||
|
permalink: /installation/manual/database
|
||||||
---
|
---
|
||||||
|
|
||||||
# Preparing database storage
|
# Preparing database storage
|
||||||
|
|
@ -13,31 +15,22 @@ may need to perform some manual steps outlined below.
|
||||||
## PostgreSQL
|
## PostgreSQL
|
||||||
|
|
||||||
Dendrite can automatically populate the database with the relevant tables and indexes, but
|
Dendrite can automatically populate the database with the relevant tables and indexes, but
|
||||||
it is not capable of creating the databases themselves. You will need to create the databases
|
it is not capable of creating the database itself. You will need to create the database
|
||||||
manually.
|
manually.
|
||||||
|
|
||||||
The databases **must** be created with UTF-8 encoding configured or you will likely run into problems
|
The database **must** be created with UTF-8 encoding configured, or you will likely run into problems
|
||||||
with your Dendrite deployment.
|
with your Dendrite deployment.
|
||||||
|
|
||||||
At this point, you can choose to either use a single database for all Dendrite components,
|
You will need to create a single PostgreSQL database. Deployments
|
||||||
or you can run each component with its own separate database:
|
can use a single global connection pool, which makes updating the configuration file much easier.
|
||||||
|
Only one database connection string to manage and likely simpler to back up the database. All
|
||||||
|
components will be sharing the same database resources (CPU, RAM, storage).
|
||||||
|
|
||||||
* **Single database**: You will need to create a single PostgreSQL database. Monolith deployments
|
You will most likely want to:
|
||||||
can use a single global connection pool, which makes updating the configuration file much easier.
|
|
||||||
Only one database connection string to manage and likely simpler to back up the database. All
|
|
||||||
components will be sharing the same database resources (CPU, RAM, storage).
|
|
||||||
|
|
||||||
* **Separate databases**: You will need to create a separate PostgreSQL database for each
|
|
||||||
component. You will need to configure each component that has storage in the Dendrite
|
|
||||||
configuration file with its own connection parameters. Allows running a different database engine
|
|
||||||
for each component on a different machine if needs be, each with their own CPU, RAM and storage —
|
|
||||||
almost certainly overkill unless you are running a very large Dendrite deployment.
|
|
||||||
|
|
||||||
For either configuration, you will want to:
|
|
||||||
|
|
||||||
1. Configure a role (with a username and password) which Dendrite can use to connect to the
|
1. Configure a role (with a username and password) which Dendrite can use to connect to the
|
||||||
database;
|
database;
|
||||||
2. Create the database(s) themselves, ensuring that the Dendrite role has privileges over them.
|
2. Create the database itself, ensuring that the Dendrite role has privileges over them.
|
||||||
As Dendrite will create and manage the database tables, indexes and sequences by itself, the
|
As Dendrite will create and manage the database tables, indexes and sequences by itself, the
|
||||||
Dendrite role must have suitable privileges over the database.
|
Dendrite role must have suitable privileges over the database.
|
||||||
|
|
||||||
|
|
@ -71,27 +64,6 @@ Create the database itself, using the `dendrite` role from above:
|
||||||
sudo -u postgres createdb -O dendrite -E UTF-8 dendrite
|
sudo -u postgres createdb -O dendrite -E UTF-8 dendrite
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multiple database creation
|
|
||||||
|
|
||||||
The following eight components require a database. In this example they will be named:
|
|
||||||
|
|
||||||
| Appservice API | `dendrite_appservice` |
|
|
||||||
| Federation API | `dendrite_federationapi` |
|
|
||||||
| Media API | `dendrite_mediaapi` |
|
|
||||||
| MSCs | `dendrite_mscs` |
|
|
||||||
| Roomserver | `dendrite_roomserver` |
|
|
||||||
| Sync API | `dendrite_syncapi` |
|
|
||||||
| Key server | `dendrite_keyserver` |
|
|
||||||
| User API | `dendrite_userapi` |
|
|
||||||
|
|
||||||
... therefore you will need to create eight different databases:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for i in appservice federationapi mediaapi mscs roomserver syncapi keyserver userapi; do
|
|
||||||
sudo -u postgres createdb -O dendrite -E UTF-8 dendrite_$i
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
## SQLite
|
## SQLite
|
||||||
|
|
||||||
**WARNING:** The Dendrite SQLite backend is slower, less reliable and not recommended for
|
**WARNING:** The Dendrite SQLite backend is slower, less reliable and not recommended for
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
---
|
---
|
||||||
title: Generating signing keys
|
title: Generating signing keys
|
||||||
parent: Installation
|
parent: Manual
|
||||||
nav_order: 8
|
grand_parent: Installation
|
||||||
permalink: /installation/signingkeys
|
nav_order: 3
|
||||||
|
permalink: /installation/manual/signingkeys
|
||||||
---
|
---
|
||||||
|
|
||||||
# Generating signing keys
|
# Generating signing keys
|
||||||
|
|
@ -11,7 +12,7 @@ All Matrix homeservers require a signing private key, which will be used to auth
|
||||||
federation requests and events.
|
federation requests and events.
|
||||||
|
|
||||||
The `generate-keys` utility can be used to generate a private key. Assuming that Dendrite was
|
The `generate-keys` utility can be used to generate a private key. Assuming that Dendrite was
|
||||||
built using `build.sh`, you should find the `generate-keys` utility in the `bin` folder.
|
built using `go build -o bin/ ./cmd/...`, you should find the `generate-keys` utility in the `bin` folder.
|
||||||
|
|
||||||
To generate a Matrix signing private key:
|
To generate a Matrix signing private key:
|
||||||
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
---
|
---
|
||||||
title: Configuring Dendrite
|
title: Configuring Dendrite
|
||||||
parent: Installation
|
parent: Manual
|
||||||
nav_order: 7
|
grand_parent: Installation
|
||||||
permalink: /installation/configuration
|
nav_order: 4
|
||||||
|
permalink: /installation/manual/configuration
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configuring Dendrite
|
# Configuring Dendrite
|
||||||
|
|
@ -20,7 +21,7 @@ sections:
|
||||||
|
|
||||||
First of all, you will need to configure the server name of your Matrix homeserver.
|
First of all, you will need to configure the server name of your Matrix homeserver.
|
||||||
This must match the domain name that you have selected whilst [configuring the domain
|
This must match the domain name that you have selected whilst [configuring the domain
|
||||||
name delegation](domainname).
|
name delegation](../domainname#delegation).
|
||||||
|
|
||||||
In the `global` section, set the `server_name` to your delegated domain name:
|
In the `global` section, set the `server_name` to your delegated domain name:
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ global:
|
||||||
|
|
||||||
## JetStream configuration
|
## JetStream configuration
|
||||||
|
|
||||||
Monolith deployments can use the built-in NATS Server rather than running a standalone
|
Dendrite deployments can use the built-in NATS Server rather than running a standalone
|
||||||
server. If you want to use a standalone NATS Server anyway, you can also configure that too.
|
server. If you want to use a standalone NATS Server anyway, you can also configure that too.
|
||||||
|
|
||||||
### Built-in NATS Server
|
### Built-in NATS Server
|
||||||
|
|
@ -56,7 +57,6 @@ configured and set a `storage_path` to a persistent folder on the filesystem:
|
||||||
global:
|
global:
|
||||||
# ...
|
# ...
|
||||||
jetstream:
|
jetstream:
|
||||||
in_memory: false
|
|
||||||
storage_path: /path/to/storage/folder
|
storage_path: /path/to/storage/folder
|
||||||
topic_prefix: Dendrite
|
topic_prefix: Dendrite
|
||||||
```
|
```
|
||||||
|
|
@ -79,22 +79,17 @@ You do not need to configure the `storage_path` when using a standalone NATS Ser
|
||||||
In the case that you are connecting to a multi-node NATS cluster, you can configure more than
|
In the case that you are connecting to a multi-node NATS cluster, you can configure more than
|
||||||
one address in the `addresses` field.
|
one address in the `addresses` field.
|
||||||
|
|
||||||
## Database connections
|
## Database connection using a global connection pool
|
||||||
|
|
||||||
Configuring database connections varies based on the [database configuration](database)
|
If you want to use a single connection pool to a single PostgreSQL database,
|
||||||
that you chose.
|
then you must uncomment and configure the `database` section within the `global` section:
|
||||||
|
|
||||||
### Global connection pool
|
|
||||||
|
|
||||||
If you want to use a single connection pool to a single PostgreSQL database, then you must
|
|
||||||
uncomment and configure the `database` section within the `global` section:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
global:
|
global:
|
||||||
# ...
|
# ...
|
||||||
database:
|
database:
|
||||||
connection_string: postgres://user:pass@hostname/database?sslmode=disable
|
connection_string: postgres://user:pass@hostname/database?sslmode=disable
|
||||||
max_open_conns: 100
|
max_open_conns: 90
|
||||||
max_idle_conns: 5
|
max_idle_conns: 5
|
||||||
conn_max_lifetime: -1
|
conn_max_lifetime: -1
|
||||||
```
|
```
|
||||||
|
|
@ -104,42 +99,13 @@ configuration file, e.g. under the `app_service_api`, `federation_api`, `key_ser
|
||||||
`media_api`, `mscs`, `relay_api`, `room_server`, `sync_api` and `user_api` blocks, otherwise
|
`media_api`, `mscs`, `relay_api`, `room_server`, `sync_api` and `user_api` blocks, otherwise
|
||||||
these will override the `global` database configuration.
|
these will override the `global` database configuration.
|
||||||
|
|
||||||
### Per-component connections (all other configurations)
|
|
||||||
|
|
||||||
If you are are using SQLite databases or separate PostgreSQL
|
|
||||||
databases per component, then you must instead configure the `database` sections under each
|
|
||||||
of the component blocks ,e.g. under the `app_service_api`, `federation_api`, `key_server`,
|
|
||||||
`media_api`, `mscs`, `relay_api`, `room_server`, `sync_api` and `user_api` blocks.
|
|
||||||
|
|
||||||
For example, with PostgreSQL:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
room_server:
|
|
||||||
# ...
|
|
||||||
database:
|
|
||||||
connection_string: postgres://user:pass@hostname/dendrite_component?sslmode=disable
|
|
||||||
max_open_conns: 10
|
|
||||||
max_idle_conns: 2
|
|
||||||
conn_max_lifetime: -1
|
|
||||||
```
|
|
||||||
|
|
||||||
... or with SQLite:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
room_server:
|
|
||||||
# ...
|
|
||||||
database:
|
|
||||||
connection_string: file:roomserver.db
|
|
||||||
max_open_conns: 10
|
|
||||||
max_idle_conns: 2
|
|
||||||
conn_max_lifetime: -1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full-text search
|
## Full-text search
|
||||||
|
|
||||||
Dendrite supports experimental full-text indexing using [Bleve](https://github.com/blevesearch/bleve). It is configured in the `sync_api` section as follows.
|
Dendrite supports full-text indexing using [Bleve](https://github.com/blevesearch/bleve). It is configured in the `sync_api` section as follows.
|
||||||
|
|
||||||
Depending on the language most likely to be used on the server, it might make sense to change the `language` used when indexing, to ensure the returned results match the expectations. A full list of possible languages can be found [here](https://github.com/blevesearch/bleve/tree/master/analysis/lang).
|
Depending on the language most likely to be used on the server, it might make sense to change the `language` used when indexing,
|
||||||
|
to ensure the returned results match the expectations. A full list of possible languages
|
||||||
|
can be found [here](https://github.com/matrix-org/dendrite/blob/5b73592f5a4dddf64184fcbe33f4c1835c656480/internal/fulltext/bleve.go#L25-L46).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
sync_api:
|
sync_api:
|
||||||
26
docs/installation/manual/5_starting_dendrite.md
Normal file
26
docs/installation/manual/5_starting_dendrite.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
title: Starting Dendrite
|
||||||
|
parent: Manual
|
||||||
|
grand_parent: Installation
|
||||||
|
nav_order: 5
|
||||||
|
permalink: /installation/manual/start
|
||||||
|
---
|
||||||
|
|
||||||
|
# Starting Dendrite
|
||||||
|
|
||||||
|
Once you have completed all preparation and installation steps,
|
||||||
|
you can start your Dendrite deployment by executing the `dendrite` binary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dendrite -config /path/to/dendrite.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, Dendrite will listen HTTP on port 8008. If you want to change the addresses
|
||||||
|
or ports that Dendrite listens on, you can use the `-http-bind-address` and
|
||||||
|
`-https-bind-address` command line arguments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dendrite -config /path/to/dendrite.yaml \
|
||||||
|
-http-bind-address 1.2.3.4:12345 \
|
||||||
|
-https-bind-address 1.2.3.4:54321
|
||||||
|
```
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue