Merge branch 'release/upstream-v0.13.1' into DEV-801/recent-locations-endpoint

This commit is contained in:
Daniel Aloni 2023-07-10 17:51:33 +03:00
commit 2cca2b0141
457 changed files with 15589 additions and 9147 deletions

View file

@ -67,6 +67,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install libolm
run: sudo apt-get install libolm-dev libolm3
- name: Install Go - name: Install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
@ -101,6 +103,8 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install libolm
run: sudo apt-get install libolm-dev libolm3
- name: Setup go - name: Setup go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
@ -232,6 +236,8 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install libolm
run: sudo apt-get install libolm-dev libolm3
- name: Setup go - name: Setup go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:

View file

@ -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
View file

@ -77,7 +77,10 @@ docs/_site
media_store/ media_store/
__debug_bin # golang workspaces
go.work*
__debug_bin*
cmd/dendrite-monolith-server/dendrite-monolith-server cmd/dendrite-monolith-server/dendrite-monolith-server
build build

View file

@ -1,5 +1,59 @@
# Changelog # Changelog
## Dendrite 0.13.1 (2023-07-06)
This releases fixes a long-standing "off-by-one" error which could result in state resets. Upgrading to this version is **highly** recommended.
When deduplicating state events, we were checking if the event in question was already in a state snapshot. If it was in a previous state snapshot, we would
then remove it from the list of events to store. If this happened, we were, unfortunately, skipping the next event to check. This resulted in
events getting stored in state snapshots where they may not be needed. When we now compared two of those state snapshots, one of them
contained the skipped event, while the other didn't. This difference possibly shouldn't exist, resulting in unexpected state resets and explains
reports of missing state events as well.
Rooms where a state reset occurred earlier should, hopefully, reconcile over time.
### Fixes:
- A long-standing "off-by-one" error has been fixed, which could result in state resets
- Roomserver Prometheus Metrics are available again
### Features
- Updated dependencies
- Internal NATS Server has been updated from v2.9.15 to v2.9.19
## Dendrite 0.13.0 (2023-06-30)
### Features
- Results in responses to `/search` now highlight words more accurately and not only the search terms as before
- Support for connecting to appservices listening on unix sockets has been added (contributed by [cyberb](https://github.com/cyberb))
- Admin APIs for token authenticated registration have been added (contributed by [santhoshivan23](https://github.com/santhoshivan23))
- Initial support for [MSC4014: Pseudonymous Identities](https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/pseudo-ids/proposals/4014-pseudonymous-identities.md)
- This is **highly experimental**, things like changing usernames/avatars, inviting users, upgrading rooms isn't working
### Fixes
- `m.upload.size` is now optional, finally allowing uploads with unlimited file size
- A bug while resolving server names has been fixed (contributed by [anton-molyboha](https://github.com/anton-molyboha))
- Application services should only receive one invitation instead of 2 (or worse), which could result in state resets previously
- Several admin endpoints are now using `POST` instead of `GET`
- `/delete_devices` now uses user-interactive authentication
- Several "membership" (e.g `/kick`, `/ban`) endpoints are using less heavy database queries to check if the user is allowed to perform this action
- `/3pid` endpoints are now available on `/v3` instead of the `/unstable` prefix
- Upgrading rooms ignores state events of other users, which could result in failed upgrades before
- Uploading key backups with a wrong version now returns `M_WRONG_ROOM_KEYS_VERSION`
- A potential state reset when joining the same room multiple times in short sequence has been fixed
- A bug where we returned the full event as `redacted_because` in redaction events has been fixed
- The `displayname` and `avatar_url` can now be set to empty strings
- Unsafe hotserving of files has been fixed (contributed by [joshqou](https://github.com/joshqou))
- Joining new rooms would potentially return "redacted" events, due to history visibility not being set correctly, this could result in events being rejected
- Backfilling resulting in `unsuported room version ''` should now be solved
### Other
- Huge refactoring of Dendrite and gomatrixserverlib
## Dendrite 0.12.0 (2023-03-13) ## Dendrite 0.12.0 (2023-03-13)
### Features ### Features

View file

@ -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

View file

@ -20,10 +20,9 @@ import (
"github.com/matrix-org/dendrite/setup/jetstream" "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/spec"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/matrix-org/gomatrixserverlib"
appserviceAPI "github.com/matrix-org/dendrite/appservice/api" appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
"github.com/matrix-org/dendrite/appservice/consumers" "github.com/matrix-org/dendrite/appservice/consumers"
"github.com/matrix-org/dendrite/appservice/query" "github.com/matrix-org/dendrite/appservice/query"
@ -86,7 +85,7 @@ func NewInternalAPI(
func generateAppServiceAccount( func generateAppServiceAccount(
userAPI userapi.AppserviceUserAPI, userAPI userapi.AppserviceUserAPI,
as config.ApplicationService, as config.ApplicationService,
serverName gomatrixserverlib.ServerName, serverName spec.ServerName,
) error { ) error {
var accRes userapi.PerformAccountCreationResponse var accRes userapi.PerformAccountCreationResponse
err := userAPI.PerformAccountCreation(context.Background(), &userapi.PerformAccountCreationRequest{ err := userAPI.PerformAccountCreation(context.Background(), &userapi.PerformAccountCreationRequest{

View file

@ -27,7 +27,7 @@ import (
"github.com/matrix-org/dendrite/setup/jetstream" "github.com/matrix-org/dendrite/setup/jetstream"
"github.com/matrix-org/dendrite/test" "github.com/matrix-org/dendrite/test"
"github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/dendrite/userapi"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/dendrite/test/testrig" "github.com/matrix-org/dendrite/test/testrig"
) )
@ -326,7 +326,7 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
room := test.NewRoom(t, alice) room := test.NewRoom(t, alice)
// Invite Bob // Invite Bob
room.CreateAndInsert(t, alice, gomatrixserverlib.MRoomMember, map[string]interface{}{ room.CreateAndInsert(t, alice, spec.MRoomMember, map[string]interface{}{
"membership": "invite", "membership": "invite",
}, test.WithStateKey(bob.ID)) }, test.WithStateKey(bob.ID))
@ -345,7 +345,7 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
for _, ev := range txn.Events { for _, ev := range txn.Events {
if ev.Type != gomatrixserverlib.MRoomMember { if ev.Type != spec.MRoomMember {
continue continue
} }
// Usually we would check the event content for the membership, but since // Usually we would check the event content for the membership, but since

View file

@ -26,9 +26,11 @@ import (
"time" "time"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"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/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/setup/process" "github.com/matrix-org/dendrite/setup/process"
@ -103,7 +105,7 @@ func (s *OutputRoomEventConsumer) onMessage(
ctx context.Context, state *appserviceState, msgs []*nats.Msg, ctx context.Context, state *appserviceState, msgs []*nats.Msg,
) bool { ) bool {
log.WithField("appservice", state.ID).Tracef("Appservice worker received %d message(s) from roomserver", len(msgs)) log.WithField("appservice", state.ID).Tracef("Appservice worker received %d message(s) from roomserver", len(msgs))
events := make([]*gomatrixserverlib.HeaderedEvent, 0, len(msgs)) events := make([]*types.HeaderedEvent, 0, len(msgs))
for _, msg := range msgs { for _, msg := range msgs {
// Only handle events we care about // Only handle events we care about
receivedType := api.OutputType(msg.Header.Get(jetstream.RoomEventType)) receivedType := api.OutputType(msg.Header.Get(jetstream.RoomEventType))
@ -173,13 +175,15 @@ func (s *OutputRoomEventConsumer) onMessage(
// endpoint. It will block for the backoff period if necessary. // endpoint. It will block for the backoff period if necessary.
func (s *OutputRoomEventConsumer) sendEvents( func (s *OutputRoomEventConsumer) sendEvents(
ctx context.Context, state *appserviceState, ctx context.Context, state *appserviceState,
events []*gomatrixserverlib.HeaderedEvent, events []*types.HeaderedEvent,
txnID string, txnID string,
) error { ) error {
// Create the transaction body. // Create the transaction body.
transaction, err := json.Marshal( transaction, err := json.Marshal(
ApplicationServiceTransaction{ ApplicationServiceTransaction{
Events: synctypes.HeaderedToClientEvents(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 {
@ -188,7 +192,7 @@ func (s *OutputRoomEventConsumer) sendEvents(
// If txnID is not defined, generate one from the events. // If txnID is not defined, generate one from the events.
if txnID == "" { if txnID == "" {
txnID = fmt.Sprintf("%d_%d", events[0].Event.OriginServerTS(), len(transaction)) txnID = fmt.Sprintf("%d_%d", events[0].PDU.OriginServerTS(), len(transaction))
} }
// Send the transaction to the appservice. // Send the transaction to the appservice.
@ -230,17 +234,27 @@ func (s *appserviceState) backoffAndPause(err error) error {
// event falls within one of a given application service's namespaces. // event falls within one of a given application service's namespaces.
// //
// 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 *gomatrixserverlib.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
} }
if event.Type() == gomatrixserverlib.MRoomMember && event.StateKey() != nil { if event.Type() == spec.MRoomMember && event.StateKey() != nil {
if appservice.IsInterestedInUserID(*event.StateKey()) { if appservice.IsInterestedInUserID(*event.StateKey()) {
return true return true
} }
@ -268,7 +282,7 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
// appserviceJoinedAtEvent returns a boolean depending on whether a given // appserviceJoinedAtEvent returns a boolean depending on whether a given
// appservice has membership at the time a given event was created. // appservice has membership at the time a given event was created.
func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, event *gomatrixserverlib.HeaderedEvent, appservice *config.ApplicationService) bool { func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, event *types.HeaderedEvent, appservice *config.ApplicationService) bool {
// TODO: This is only checking the current room state, not the state at // TODO: This is only checking the current room state, not the state at
// the event in question. Pretty sure this is what Synapse does too, but // the event in question. Pretty sure this is what Synapse does too, but
// until we have a lighter way of checking the state before the event that // until we have a lighter way of checking the state before the event that
@ -286,7 +300,7 @@ func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, e
switch { switch {
case ev.StateKey == nil: case ev.StateKey == nil:
continue continue
case ev.Type != gomatrixserverlib.MRoomMember: case ev.Type != spec.MRoomMember:
continue continue
} }
var membership gomatrixserverlib.MemberContent var membership gomatrixserverlib.MemberContent
@ -294,7 +308,7 @@ func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, e
switch { switch {
case err != nil: case err != nil:
continue continue
case membership.Membership == gomatrixserverlib.Join: case membership.Membership == spec.Join:
if appservice.IsInterestedInUserID(*ev.StateKey) { if appservice.IsInterestedInUserID(*ev.StateKey) {
return true return true
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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:

View file

@ -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

View file

@ -33,6 +33,7 @@ import (
"github.com/matrix-org/dendrite/test" "github.com/matrix-org/dendrite/test"
"github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/dendrite/userapi"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
_ "golang.org/x/mobile/bind" _ "golang.org/x/mobile/bind"
@ -134,7 +135,7 @@ func (m *DendriteMonolith) Start() {
Generate: true, Generate: true,
SingleDatabase: true, SingleDatabase: true,
}) })
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk)) cfg.Global.ServerName = spec.ServerName(hex.EncodeToString(pk))
cfg.Global.PrivateKey = sk cfg.Global.PrivateKey = sk
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID) cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", m.StorageDirectory)) cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/", m.StorageDirectory))

View file

@ -2,13 +2,13 @@ package clientapi
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/federationapi" "github.com/matrix-org/dendrite/federationapi"
"github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/caching"
"github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/dendrite/internal/httputil"
@ -19,17 +19,654 @@ import (
"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/syncapi" "github.com/matrix-org/dendrite/syncapi"
"github.com/matrix-org/gomatrixserverlib"
"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"
"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))
@ -55,10 +692,10 @@ func TestAdminResetPassword(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login // Create the users in the userapi and login
accessTokens := map[*test.User]string{ accessTokens := map[*test.User]userDevice{
aliceAdmin: "", aliceAdmin: {},
bob: "", bob: {},
vhUser: "", vhUser: {},
} }
createAccessTokens(t, accessTokens, userAPI, ctx, routers) createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@ -104,7 +741,7 @@ func TestAdminResetPassword(t *testing.T) {
} }
if tc.withHeader { if tc.withHeader {
req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser]) req.Header.Set("Authorization", "Bearer "+accessTokens[tc.requestingUser].accessToken)
} }
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
@ -124,7 +761,7 @@ func TestPurgeRoom(t *testing.T) {
room := test.NewRoom(t, aliceAdmin, test.RoomPreset(test.PresetTrustedPrivateChat)) room := test.NewRoom(t, aliceAdmin, test.RoomPreset(test.PresetTrustedPrivateChat))
// Invite Bob // Invite Bob
room.CreateAndInsert(t, aliceAdmin, gomatrixserverlib.MRoomMember, map[string]interface{}{ room.CreateAndInsert(t, aliceAdmin, spec.MRoomMember, map[string]interface{}{
"membership": "invite", "membership": "invite",
}, test.WithStateKey(bob.ID)) }, test.WithStateKey(bob.ID))
@ -134,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)
@ -143,8 +784,8 @@ func TestPurgeRoom(t *testing.T) {
// this starts the JetStream consumers // this starts the JetStream consumers
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics) syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics)
federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true) fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
rsAPI.SetFederationAPI(nil, nil) rsAPI.SetFederationAPI(fsAPI, nil)
// Create the room // Create the room
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil { if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
@ -155,8 +796,8 @@ func TestPurgeRoom(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login // Create the users in the userapi and login
accessTokens := map[*test.User]string{ accessTokens := map[*test.User]userDevice{
aliceAdmin: "", aliceAdmin: {},
} }
createAccessTokens(t, accessTokens, userAPI, ctx, routers) createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@ -175,7 +816,7 @@ func TestPurgeRoom(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/purgeRoom/"+tc.roomID) req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/purgeRoom/"+tc.roomID)
req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin]) req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req) routers.DendriteAdmin.ServeHTTP(rec, req)
@ -195,7 +836,7 @@ func TestAdminEvacuateRoom(t *testing.T) {
room := test.NewRoom(t, aliceAdmin) room := test.NewRoom(t, aliceAdmin)
// Join Bob // Join Bob
room.CreateAndInsert(t, bob, gomatrixserverlib.MRoomMember, map[string]interface{}{ room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
"membership": "join", "membership": "join",
}, test.WithStateKey(bob.ID)) }, test.WithStateKey(bob.ID))
@ -225,8 +866,8 @@ func TestAdminEvacuateRoom(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login // Create the users in the userapi and login
accessTokens := map[*test.User]string{ accessTokens := map[*test.User]userDevice{
aliceAdmin: "", aliceAdmin: {},
} }
createAccessTokens(t, accessTokens, userAPI, ctx, routers) createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@ -244,7 +885,7 @@ func TestAdminEvacuateRoom(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/evacuateRoom/"+tc.roomID) req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/evacuateRoom/"+tc.roomID)
req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin]) req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req) routers.DendriteAdmin.ServeHTTP(rec, req)
@ -292,10 +933,10 @@ func TestAdminEvacuateUser(t *testing.T) {
room2 := test.NewRoom(t, aliceAdmin) room2 := test.NewRoom(t, aliceAdmin)
// Join Bob // Join Bob
room.CreateAndInsert(t, bob, gomatrixserverlib.MRoomMember, map[string]interface{}{ room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
"membership": "join", "membership": "join",
}, test.WithStateKey(bob.ID)) }, test.WithStateKey(bob.ID))
room2.CreateAndInsert(t, bob, gomatrixserverlib.MRoomMember, map[string]interface{}{ room2.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
"membership": "join", "membership": "join",
}, test.WithStateKey(bob.ID)) }, test.WithStateKey(bob.ID))
@ -328,8 +969,8 @@ func TestAdminEvacuateUser(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login // Create the users in the userapi and login
accessTokens := map[*test.User]string{ accessTokens := map[*test.User]userDevice{
aliceAdmin: "", aliceAdmin: {},
} }
createAccessTokens(t, accessTokens, userAPI, ctx, routers) createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@ -349,7 +990,7 @@ func TestAdminEvacuateUser(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/evacuateUser/"+tc.userID) req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/evacuateUser/"+tc.userID)
req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin]) req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req) routers.DendriteAdmin.ServeHTTP(rec, req)
@ -410,8 +1051,8 @@ func TestAdminMarkAsStale(t *testing.T) {
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics) AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
// Create the users in the userapi and login // Create the users in the userapi and login
accessTokens := map[*test.User]string{ accessTokens := map[*test.User]userDevice{
aliceAdmin: "", aliceAdmin: {},
} }
createAccessTokens(t, accessTokens, userAPI, ctx, routers) createAccessTokens(t, accessTokens, userAPI, ctx, routers)
@ -429,7 +1070,7 @@ func TestAdminMarkAsStale(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/refreshDevices/"+tc.userID) req := test.NewRequest(t, http.MethodPost, "/_dendrite/admin/refreshDevices/"+tc.userID)
req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin]) req.Header.Set("Authorization", "Bearer "+accessTokens[aliceAdmin].accessToken)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
routers.DendriteAdmin.ServeHTTP(rec, req) routers.DendriteAdmin.ServeHTTP(rec, req)
@ -441,35 +1082,3 @@ func TestAdminMarkAsStale(t *testing.T) {
} }
}) })
} }
func createAccessTokens(t *testing.T, accessTokens map[*test.User]string, userAPI uapi.UserInternalAPI, ctx context.Context, routers httputil.Routers) {
t.Helper()
for u := range accessTokens {
localpart, serverName, _ := gomatrixserverlib.SplitID('@', u.ID)
userRes := &uapi.PerformAccountCreationResponse{}
password := util.RandomString(8)
if err := userAPI.PerformAccountCreation(ctx, &uapi.PerformAccountCreationRequest{
AccountType: u.AccountType,
Localpart: localpart,
ServerName: serverName,
Password: password,
}, userRes); err != nil {
t.Errorf("failed to create account: %s", err)
}
req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
"type": authtypes.LoginTypePassword,
"identifier": map[string]interface{}{
"type": "m.id.user",
"user": u.ID,
},
"password": password,
}))
rec := httptest.NewRecorder()
routers.Client.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("failed to login: %s", rec.Body.String())
}
accessTokens[u] = gjson.GetBytes(rec.Body.Bytes(), "access_token").String()
}
}

View file

@ -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"`
}

View file

@ -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

View file

@ -21,10 +21,10 @@ 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/clientapi/ratelimit" "github.com/matrix-org/dendrite/clientapi/ratelimit"
"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"
) )
@ -38,7 +38,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.C
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
} }
@ -50,7 +50,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.C
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
} }
@ -77,7 +77,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.C
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
} }

View file

@ -8,8 +8,8 @@ import (
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"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/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -41,7 +41,7 @@ func (t *LoginTypeTokenJwt) LoginFromJSON(ctx context.Context, reqBytes []byte)
if r.Token == "" { if r.Token == "" {
return nil, nil, &util.JSONResponse{ return nil, nil, &util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("Token field for JWT is missing"), JSON: spec.Forbidden("Token field for JWT is missing"),
} }
} }
c := &Claims{} c := &Claims{}
@ -56,14 +56,14 @@ func (t *LoginTypeTokenJwt) LoginFromJSON(ctx context.Context, reqBytes []byte)
util.GetLogger(ctx).WithError(err).Error("jwt.ParseWithClaims failed") util.GetLogger(ctx).WithError(err).Error("jwt.ParseWithClaims failed")
return nil, nil, &util.JSONResponse{ return nil, nil, &util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("Couldn't parse JWT"), JSON: spec.Forbidden("Couldn't parse JWT"),
} }
} }
if !token.Valid { if !token.Valid {
return nil, nil, &util.JSONResponse{ return nil, nil, &util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("Invalid JWT"), JSON: spec.Forbidden("Invalid JWT"),
} }
} }

View file

@ -21,12 +21,12 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/ratelimit" "github.com/matrix-org/dendrite/clientapi/ratelimit"
"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"
) )
@ -111,13 +111,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",
@ -127,7 +127,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",
@ -136,7 +136,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",
@ -144,7 +144,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 {
@ -161,7 +161,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)
} }
}) })

View file

@ -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"),
} }
} }

View file

@ -22,15 +22,14 @@ import (
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/matrix-org/gomatrixserverlib"
"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/ratelimit" "github.com/matrix-org/dendrite/clientapi/ratelimit"
"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"
) )
@ -89,14 +88,16 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
}, &res) }, &res)
if err != nil { if err != nil {
util.GetLogger(ctx).WithError(err).Error("userApi.QueryLocalpartForThreePID failed") util.GetLogger(ctx).WithError(err).Error("userApi.QueryLocalpartForThreePID failed")
resp := jsonerror.InternalServerError() return nil, &util.JSONResponse{
return nil, &resp Code: http.StatusInternalServerError,
JSON: spec.Unknown(""),
}
} }
username = "@" + res.Localpart + ":" + string(t.Config.Matrix.ServerName) username = "@" + res.Localpart + ":" + string(t.Config.Matrix.ServerName)
if username == "" { if username == "" {
return nil, &util.JSONResponse{ return nil, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.Forbidden("Invalid username or password"), JSON: spec.Forbidden("Invalid username or password"),
} }
} }
} else { } else {
@ -105,26 +106,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."),
} }
} }
@ -135,7 +136,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
if !ok { if !ok {
return nil, &util.JSONResponse{ return nil, &util.JSONResponse{
Code: http.StatusTooManyRequests, Code: http.StatusTooManyRequests,
JSON: jsonerror.LimitExceeded("Too Many Requests", retryIn.Milliseconds()), JSON: spec.LimitExceeded("Too Many Requests", retryIn.Milliseconds()),
} }
} }
} }
@ -147,7 +148,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."),
} }
} }
@ -177,7 +178,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
return &r.Login, nil return &r.Login, nil
} }
func (t *LoginTypePassword) authenticateDb(ctx context.Context, localpart string, domain gomatrixserverlib.ServerName, password string) (*api.Account, *util.JSONResponse) { func (t *LoginTypePassword) authenticateDb(ctx context.Context, localpart string, domain spec.ServerName, password string) (*api.Account, *util.JSONResponse) {
res := &api.QueryAccountByPasswordResponse{} res := &api.QueryAccountByPasswordResponse{}
err := t.UserApi.QueryAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{ err := t.UserApi.QueryAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
Localpart: strings.ToLower(localpart), Localpart: strings.ToLower(localpart),
@ -187,7 +188,7 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, localpart string
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."),
} }
} }
@ -202,7 +203,7 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, localpart string
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
@ -213,7 +214,7 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, localpart string
} }
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."),
} }
} }
} }
@ -226,9 +227,10 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
if err != nil { if err != nil {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("unable to connect to ldap: " + err.Error()), JSON: spec.Unknown("unable to connect to ldap: " + err.Error()),
} }
} }
// nolint: errcheck
defer conn.Close() defer conn.Close()
if t.Config.Ldap.AdminBindEnabled { if t.Config.Ldap.AdminBindEnabled {
@ -236,7 +238,7 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
if err != nil { if err != nil {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("unable to bind to ldap: " + err.Error()), JSON: spec.Unknown("unable to bind to ldap: " + err.Error()),
} }
} }
filter := strings.ReplaceAll(t.Config.Ldap.SearchFilter, "{username}", username) filter := strings.ReplaceAll(t.Config.Ldap.SearchFilter, "{username}", username)
@ -249,19 +251,19 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
if err != nil { if err != nil {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("unable to bind to search ldap: " + err.Error()), JSON: spec.Unknown("unable to bind to search ldap: " + err.Error()),
} }
} }
if len(result.Entries) > 1 { if len(result.Entries) > 1 {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.BadJSON("'user' must be duplicated."), JSON: spec.BadJSON("'user' must be duplicated."),
} }
} }
if len(result.Entries) < 1 { if len(result.Entries) < 1 {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.BadJSON("'user' not found."), JSON: spec.BadJSON("'user' not found."),
} }
} }
@ -273,7 +275,7 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
if err != nil { if err != nil {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()), JSON: spec.InvalidUsername(err.Error()),
} }
} }
if t.Rt != nil { if t.Rt != nil {
@ -281,7 +283,7 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
} }
return false, &util.JSONResponse{ return false, &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."),
} }
} }
} else { } else {
@ -293,7 +295,7 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
if err != nil { if err != nil {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()), JSON: spec.InvalidUsername(err.Error()),
} }
} }
if t.Rt != nil { if t.Rt != nil {
@ -301,7 +303,7 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
} }
return false, &util.JSONResponse{ return false, &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."),
} }
} }
} }
@ -310,7 +312,7 @@ func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *
if err != nil { if err != nil {
return false, &util.JSONResponse{ return false, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()), JSON: spec.InvalidUsername(err.Error()),
} }
} }
return isAdmin, nil return isAdmin, nil
@ -335,7 +337,7 @@ func (t *LoginTypePassword) isLdapAdmin(conn *ldap.Conn, username string) (bool,
return true, nil return true, nil
} }
func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart string, domain gomatrixserverlib.ServerName, admin bool) (*api.Account, *util.JSONResponse) { func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart string, domain spec.ServerName, admin bool) (*api.Account, *util.JSONResponse) {
var existing api.QueryAccountByLocalpartResponse var existing api.QueryAccountByLocalpartResponse
err := t.UserLoginAPI.QueryAccountByLocalpart(ctx, &api.QueryAccountByLocalpartRequest{ err := t.UserLoginAPI.QueryAccountByLocalpart(ctx, &api.QueryAccountByLocalpartRequest{
Localpart: localpart, Localpart: localpart,
@ -348,7 +350,7 @@ func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart st
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return nil, &util.JSONResponse{ return nil, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()), JSON: spec.InvalidUsername(err.Error()),
} }
} }
@ -369,12 +371,12 @@ func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, localpart st
if _, ok := err.(*api.ErrorConflict); ok { if _, ok := err.(*api.ErrorConflict); ok {
return nil, &util.JSONResponse{ return nil, &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 nil, &util.JSONResponse{ return nil, &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()),
} }
} }
return created.Account, nil return created.Account, nil

View file

@ -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"
@ -180,8 +180,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{}
@ -195,15 +197,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)
@ -236,7 +242,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, _ *api.D
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),
} }
} }
@ -252,7 +258,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, _ *api.D
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."),
} }
} }
} }

View file

@ -8,14 +8,14 @@ import (
"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/fclient" "github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
var ( var (
ctx = context.Background() ctx = context.Background()
serverName = gomatrixserverlib.ServerName("example.com") serverName = spec.ServerName("example.com")
// space separated localpart+password -> account // space separated localpart+password -> account
lookup = make(map[string]*api.Account) lookup = make(map[string]*api.Account)
device = &api.Device{ device = &api.Device{

View file

@ -37,7 +37,7 @@ func AddPublicRoutes(
routers httputil.Routers, routers httputil.Routers,
cfg *config.Dendrite, cfg *config.Dendrite,
natsInstance *jetstream.NATSInstance, natsInstance *jetstream.NATSInstance,
federation *fclient.FederationClient, federation fclient.FederationClient,
rsAPI roomserverAPI.ClientRoomserverAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
asAPI appserviceAPI.AppServiceInternalAPI, asAPI appserviceAPI.AppServiceInternalAPI,
transactionsCache *transactions.Cache, transactionsCache *transactions.Cache,

2133
clientapi/clientapi_test.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -1,229 +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",
}
}
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.",
},
}
}

View file

@ -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))
}
}

View file

@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -38,13 +39,13 @@ type SyncAPIProducer struct {
TopicPresenceEvent string TopicPresenceEvent string
TopicMultiRoomCast string TopicMultiRoomCast string
JetStream nats.JetStreamContext JetStream nats.JetStreamContext
ServerName gomatrixserverlib.ServerName ServerName spec.ServerName
UserAPI userapi.ClientUserAPI UserAPI userapi.ClientUserAPI
} }
func (p *SyncAPIProducer) SendReceipt( func (p *SyncAPIProducer) SendReceipt(
ctx context.Context, ctx context.Context,
userID, roomID, eventID, receiptType string, timestamp gomatrixserverlib.Timestamp, userID, roomID, eventID, receiptType string, timestamp spec.Timestamp,
) error { ) error {
m := &nats.Msg{ m := &nats.Msg{
Subject: p.TopicReceiptEvent, Subject: p.TopicReceiptEvent,
@ -155,7 +156,7 @@ func (p *SyncAPIProducer) SendPresence(
m.Header.Set("status_msg", *statusMsg) m.Header.Set("status_msg", *statusMsg)
} }
m.Header.Set("last_active_ts", strconv.Itoa(int(gomatrixserverlib.AsTimestamp(time.Now())))) m.Header.Set("last_active_ts", strconv.Itoa(int(spec.AsTimestamp(time.Now()))))
_, err := p.JetStream.PublishMsg(m, nats.Context(ctx)) _, err := p.JetStream.PublishMsg(m, nats.Context(ctx))
return err return err

View file

@ -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{

View file

@ -3,112 +3,328 @@ package routing
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"regexp"
"strconv"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/eventutil"
"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/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 {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
res := &roomserverAPI.PerformAdminEvacuateRoomResponse{}
if err := rsAPI.PerformAdminEvacuateRoom( affected, err := rsAPI.PerformAdminEvacuateRoom(req.Context(), vars["roomID"])
req.Context(), switch err.(type) {
&roomserverAPI.PerformAdminEvacuateRoomRequest{ case nil:
RoomID: vars["roomID"], case eventutil.ErrRoomNoExists:
}, return util.JSONResponse{
res, Code: http.StatusNotFound,
); err != nil { JSON: spec.NotFound(err.Error()),
return util.ErrorResponse(err)
} }
if err := res.Error; err != nil { default:
return err.JSONResponse() logrus.WithError(err).WithField("roomID", vars["roomID"]).Error("Failed to evacuate room")
return util.ErrorResponse(err)
} }
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: map[string]interface{}{ JSON: map[string]interface{}{
"affected": res.Affected, "affected": affected,
}, },
} }
} }
func AdminEvacuateUser(req *http.Request, cfg *config.ClientAPI, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { func AdminEvacuateUser(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
userID := vars["userID"]
_, domain, err := gomatrixserverlib.SplitID('@', userID) affected, err := rsAPI.PerformAdminEvacuateUser(req.Context(), vars["userID"])
if err != nil { if err != nil {
logrus.WithError(err).WithField("userID", vars["userID"]).Error("Failed to evacuate user")
return util.MessageResponse(http.StatusBadRequest, err.Error()) return util.MessageResponse(http.StatusBadRequest, err.Error())
} }
if !cfg.Matrix.IsLocalServerName(domain) {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.MissingArgument("User ID must belong to this server."),
}
}
res := &roomserverAPI.PerformAdminEvacuateUserResponse{}
if err := rsAPI.PerformAdminEvacuateUser(
req.Context(),
&roomserverAPI.PerformAdminEvacuateUserRequest{
UserID: userID,
},
res,
); err != nil {
return jsonerror.InternalAPIError(req.Context(), err)
}
if err := res.Error; err != nil {
return err.JSONResponse()
}
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: map[string]interface{}{ JSON: map[string]interface{}{
"affected": res.Affected, "affected": affected,
}, },
} }
} }
func AdminPurgeRoom(req *http.Request, cfg *config.ClientAPI, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { func AdminPurgeRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
roomID := vars["roomID"]
res := &roomserverAPI.PerformAdminPurgeRoomResponse{} if err = rsAPI.PerformAdminPurgeRoom(context.Background(), vars["roomID"]); err != nil {
if err := rsAPI.PerformAdminPurgeRoom(
context.Background(),
&roomserverAPI.PerformAdminPurgeRoomRequest{
RoomID: roomID,
},
res,
); err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
if err := res.Error; err != nil {
return err.JSONResponse()
}
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: res, JSON: struct{}{},
} }
} }
@ -116,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))
@ -129,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{}
@ -139,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."),
} }
} }
@ -172,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{
@ -195,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,
@ -217,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"),
} }
} }
@ -228,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{
@ -237,7 +457,7 @@ func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.Clien
} }
} }
func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { func AdminDownloadState(req *http.Request, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
@ -246,33 +466,32 @@ func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *api.De
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."),
} }
} }
res := &roomserverAPI.PerformAdminDownloadStateResponse{} if err = rsAPI.PerformAdminDownloadState(req.Context(), roomID, device.UserID, spec.ServerName(serverName)); err != nil {
if err := rsAPI.PerformAdminDownloadState( if errors.Is(err, eventutil.ErrRoomNoExists{}) {
req.Context(), return util.JSONResponse{
&roomserverAPI.PerformAdminDownloadStateRequest{ Code: 200,
UserID: device.UserID, JSON: spec.NotFound(err.Error()),
RoomID: roomID,
ServerName: gomatrixserverlib.ServerName(serverName),
},
res,
); err != nil {
return jsonerror.InternalAPIError(req.Context(), err)
} }
if err := res.Error; err != nil { }
return err.JSONResponse() logrus.WithError(err).WithFields(logrus.Fields{
"userID": device.UserID,
"serverName": serverName,
"roomID": roomID,
}).Error("failed to download state")
return util.ErrorResponse(err)
} }
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: map[string]interface{}{}, JSON: struct{}{},
} }
} }

View file

@ -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)

View file

@ -15,14 +15,14 @@
package routing package routing
import ( import (
"encoding/json"
"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/util" "github.com/matrix-org/util"
) )
@ -31,7 +31,7 @@ func GetAliases(
req *http.Request, rsAPI api.ClientRoomserverAPI, device *userapi.Device, roomID string, req *http.Request, rsAPI api.ClientRoomserverAPI, device *userapi.Device, roomID string,
) util.JSONResponse { ) util.JSONResponse {
stateTuple := gomatrixserverlib.StateKeyTuple{ stateTuple := gomatrixserverlib.StateKeyTuple{
EventType: gomatrixserverlib.MRoomHistoryVisibility, EventType: spec.MRoomHistoryVisibility,
StateKey: "", StateKey: "",
} }
stateReq := &api.QueryCurrentStateRequest{ stateReq := &api.QueryCurrentStateRequest{
@ -47,26 +47,37 @@ func GetAliases(
visibility := gomatrixserverlib.HistoryVisibilityInvited visibility := gomatrixserverlib.HistoryVisibilityInvited
if historyVisEvent, ok := stateRes.StateEvents[stateTuple]; ok { if historyVisEvent, ok := stateRes.StateEvents[stateTuple]; ok {
var err error var err error
visibility, err = historyVisEvent.HistoryVisibility() var content gomatrixserverlib.HistoryVisibilityContent
if err != nil { if err = json.Unmarshal(historyVisEvent.Content(), &content); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("historyVisEvent.HistoryVisibility failed") util.GetLogger(req.Context()).WithError(err).Error("historyVisEvent.HistoryVisibility failed")
return util.ErrorResponse(fmt.Errorf("historyVisEvent.HistoryVisibility: %w", err)) return util.ErrorResponse(fmt.Errorf("historyVisEvent.HistoryVisibility: %w", err))
} }
visibility = content.HistoryVisibility
}
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"),
}
} }
if visibility != gomatrixserverlib.WorldReadable {
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."),
} }
} }
} }

View file

@ -27,7 +27,7 @@ import (
func GetCapabilities() util.JSONResponse { func GetCapabilities() util.JSONResponse {
versionsMap := map[gomatrixserverlib.RoomVersion]string{} versionsMap := map[gomatrixserverlib.RoomVersion]string{}
for v, desc := range version.SupportedRoomVersions() { for v, desc := range version.SupportedRoomVersions() {
if desc.Stable { if desc.Stable() {
versionsMap[v] = "stable" versionsMap[v] = "stable"
} else { } else {
versionsMap[v] = "unstable" versionsMap[v] = "unstable"

View file

@ -26,10 +26,9 @@ import (
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
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/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"
@ -44,27 +43,13 @@ type createRoomRequest struct {
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"`
GuestCanJoin bool `json:"guest_can_join"`
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
@ -72,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'"),
} }
} }
@ -105,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"),
} }
} }
@ -114,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"),
} }
} }
@ -127,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,
@ -141,456 +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: gomatrixserverlib.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"),
}
}
}
switch r.Preset {
case presetPrivateChat:
joinRuleContent.JoinRule = gomatrixserverlib.Invite
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
case presetTrustedPrivateChat:
joinRuleContent.JoinRule = gomatrixserverlib.Invite
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
for _, invitee := range r.Invite {
powerLevelContent.Users[invitee] = 100
}
case presetPublicChat:
joinRuleContent.JoinRule = gomatrixserverlib.Public
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
}
createEvent := fledglingEvent{
Type: gomatrixserverlib.MRoomCreate,
Content: createContent,
}
powerLevelEvent := fledglingEvent{
Type: gomatrixserverlib.MRoomPowerLevels,
Content: powerLevelContent,
}
joinRuleEvent := fledglingEvent{
Type: gomatrixserverlib.MRoomJoinRules,
Content: joinRuleContent,
}
historyVisibilityEvent := fledglingEvent{
Type: gomatrixserverlib.MRoomHistoryVisibility,
Content: historyVisibilityContent,
}
membershipEvent := fledglingEvent{
Type: gomatrixserverlib.MRoomMember,
StateKey: userID,
Content: gomatrixserverlib.MemberContent{
Membership: gomatrixserverlib.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: gomatrixserverlib.MRoomName,
Content: eventutil.NameContent{
Name: r.Name,
},
}
}
if r.Topic != "" {
topicEvent = &fledglingEvent{
Type: gomatrixserverlib.MRoomTopic,
Content: eventutil.TopicContent{
Topic: r.Topic,
},
}
}
if r.GuestCanJoin {
guestAccessEvent = &fledglingEvent{
Type: gomatrixserverlib.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: gomatrixserverlib.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 gomatrixserverlib.MRoomCreate:
continue
case gomatrixserverlib.MRoomPowerLevels:
powerLevelEvent = r.InitialState[i]
case gomatrixserverlib.MRoomJoinRules:
joinRuleEvent = r.InitialState[i]
case gomatrixserverlib.MRoomHistoryVisibility:
historyVisibilityEvent = r.InitialState[i]
case gomatrixserverlib.MRoomGuestAccess:
guestAccessEvent = &r.InitialState[i]
case gomatrixserverlib.MRoomName:
nameEvent = &r.InitialState[i]
case gomatrixserverlib.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
var builtEvents []*gomatrixserverlib.HeaderedEvent
authEvents := gomatrixserverlib.NewAuthEvents(nil)
for i, e := range eventsToMake {
depth := i + 1 // depth starts at 1
builder := gomatrixserverlib.EventBuilder{
Sender: userID,
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.Event
ev, err = buildEvent(&builder, userDomain, &authEvents, cfg, evTime, roomVersion)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
return jsonerror.InternalServerError()
}
if err = gomatrixserverlib.Allowed(ev, &authEvents); err != nil {
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
return jsonerror.InternalServerError()
}
// Add the event to the list of auth events
builtEvents = append(builtEvents, ev.Headered(roomVersion))
err = authEvents.AddEvent(ev)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
return jsonerror.InternalServerError()
}
}
inputs := make([]roomserverAPI.InputRoomEvent, 0, len(builtEvents))
for _, event := range builtEvents {
inputs = append(inputs, roomserverAPI.InputRoomEvent{
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 []gomatrixserverlib.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 gomatrixserverlib.MRoomCreate:
fallthrough
case gomatrixserverlib.MRoomName:
fallthrough
case gomatrixserverlib.MRoomAvatar:
fallthrough
case gomatrixserverlib.MRoomTopic:
fallthrough
case gomatrixserverlib.MRoomCanonicalAlias:
fallthrough
case gomatrixserverlib.MRoomEncryption:
fallthrough
case gomatrixserverlib.MRoomMember:
fallthrough
case gomatrixserverlib.MRoomJoinRules:
ev := event.Event
globalStrippedState = append(
globalStrippedState,
gomatrixserverlib.NewInviteV2StrippedState(ev),
)
}
}
// Process the invites.
for _, invitee := range r.Invite {
// Build the invite event.
inviteEvent, err := buildMembershipEvent(
ctx, invitee, "", profileAPI, device, gomatrixserverlib.Invite,
roomID, r.IsDirect, cfg, evTime, rsAPI, asAPI,
)
if err != nil {
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
continue
}
inviteStrippedState := append(
globalStrippedState,
gomatrixserverlib.NewInviteV2StrippedState(inviteEvent.Event),
)
// Send the invite event to the roomserver.
var inviteRes roomserverAPI.PerformInviteResponse
event := inviteEvent.Headered(roomVersion)
if err := rsAPI.PerformInvite(ctx, &roomserverAPI.PerformInviteRequest{
Event: event,
InviteRoomState: inviteStrippedState,
RoomVersion: event.RoomVersion,
SendAsServer: string(userDomain),
}, &inviteRes); err != nil {
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(), JSON: spec.InternalServerError{},
}
}
if inviteRes.Error != nil {
return inviteRes.Error.JSONResponse()
}
} }
} }
if r.Visibility == "public" { userDisplayName := profile.DisplayName
// expose this room in the published room list userAvatarURL := profile.AvatarURL
var pubRes roomserverAPI.PerformPublishResponse
if err := rsAPI.PerformPublish(ctx, &roomserverAPI.PerformPublishRequest{ keyID := cfg.Matrix.KeyID
RoomID: roomID, privateKey := cfg.Matrix.PrivateKey
Visibility: "public",
}, &pubRes); err != nil { req := roomserverAPI.PerformCreateRoomRequest{
return jsonerror.InternalAPIError(ctx, err) InvitedUsers: createRequest.Invite,
} RoomName: createRequest.Name,
if pubRes.Error != nil { Visibility: createRequest.Visibility,
// treat as non-fatal since the room is already made by this point Topic: createRequest.Topic,
util.GetLogger(ctx).WithError(pubRes.Error).Error("failed to visibility:public") StatePreset: createRequest.Preset,
CreationContent: createRequest.CreationContent,
InitialState: createRequest.InitialState,
RoomAliasName: createRequest.RoomAliasName,
RoomVersion: roomVersion,
PowerLevelContentOverride: createRequest.PowerLevelContentOverride,
IsDirect: createRequest.IsDirect,
UserDisplayName: userDisplayName,
UserAvatarURL: userAvatarURL,
KeyID: keyID,
PrivateKey: privateKey,
EventTime: evTime,
} }
roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req)
if createRes != nil {
return *createRes
} }
response := createRoomResponse{ response := createRoomResponse{
RoomID: roomID, RoomID: roomID.String(),
RoomAlias: roomAlias, RoomAlias: roomAlias,
} }
@ -599,31 +240,3 @@ func createRoom(
JSON: response, JSON: response,
} }
} }
// buildEvent fills out auth_events for the builder then builds the event
func buildEvent(
builder *gomatrixserverlib.EventBuilder,
serverName gomatrixserverlib.ServerName,
provider gomatrixserverlib.AuthEventProvider,
cfg *config.ClientAPI,
evTime time.Time,
roomVersion gomatrixserverlib.RoomVersion,
) (*gomatrixserverlib.Event, error) {
eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder)
if err != nil {
return nil, err
}
refs, err := eventsNeeded.AuthEventReferences(provider)
if err != nil {
return nil, err
}
builder.AuthEvents = refs
event, err := builder.Build(
evTime, serverName, cfg.Matrix.KeyID,
cfg.Matrix.PrivateKey, roomVersion,
)
if err != nil {
return nil, fmt.Errorf("cannot build event %s : Builder failed to build. %w", builder.Type, err)
}
return event, nil
}

View file

@ -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()),
} }
} }
var userId string var userId string
@ -41,7 +41,10 @@ func Deactivate(
localpart, _, err := gomatrixserverlib.SplitID('@', userId) localpart, _, 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{},
}
} }
var res api.PerformAccountDeactivationResponse var res api.PerformAccountDeactivationResponse
@ -50,7 +53,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{

View file

@ -15,15 +15,16 @@
package routing package routing
import ( import (
"encoding/json"
"io" "io"
"net" "net"
"net/http" "net/http"
"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"
) )
@ -59,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 {
@ -71,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"),
} }
} }
@ -96,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{}
@ -138,18 +145,21 @@ 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"),
} }
} }
if performRes.Forbidden { if performRes.Forbidden {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("device not owned by current user"), JSON: spec.Forbidden("device not owned by current user"),
} }
} }
@ -179,7 +189,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()),
} }
} }
@ -189,7 +199,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 & device mismatch"), JSON: spec.Forbidden("session and device mismatch"),
} }
} }
} }
@ -211,7 +221,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
@ -219,7 +232,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"),
} }
} }
@ -229,7 +242,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
@ -242,13 +258,39 @@ func DeleteDeviceById(
// DeleteDevices handles POST requests to /delete_devices // DeleteDevices handles POST requests to /delete_devices
func DeleteDevices( func DeleteDevices(
req *http.Request, userAPI api.ClientUserAPI, device *api.Device, req *http.Request, userInteractiveAuth *auth.UserInteractive, userAPI api.ClientUserAPI, device *api.Device,
) util.JSONResponse { ) util.JSONResponse {
ctx := req.Context() ctx := req.Context()
payload := devicesDeleteJSON{}
if resErr := httputil.UnmarshalJSONRequest(req, &payload); resErr != nil { bodyBytes, err := io.ReadAll(req.Body)
return *resErr if err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
}
}
defer req.Body.Close() // nolint:errcheck
// initiate UIA
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device)
if errRes != nil {
return *errRes
}
if login.Username() != device.UserID {
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden("unable to delete devices for other user"),
}
}
payload := devicesDeleteJSON{}
if err = json.Unmarshal(bodyBytes, &payload); err != nil {
util.GetLogger(ctx).WithError(err).Error("unable to unmarshal device deletion request")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
} }
defer req.Body.Close() // nolint: errcheck defer req.Body.Close() // nolint: errcheck
@ -259,7 +301,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{

View file

@ -20,10 +20,10 @@ import (
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"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"
"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"
@ -35,7 +35,7 @@ type roomDirectoryResponse struct {
Servers []string `json:"servers"` Servers []string `json:"servers"`
} }
func (r *roomDirectoryResponse) fillServers(servers []gomatrixserverlib.ServerName) { func (r *roomDirectoryResponse) fillServers(servers []spec.ServerName) {
r.Servers = make([]string, len(servers)) r.Servers = make([]string, len(servers))
for i, s := range servers { for i, s := range servers {
r.Servers[i] = string(s) r.Servers[i] = string(s)
@ -46,7 +46,7 @@ func (r *roomDirectoryResponse) fillServers(servers []gomatrixserverlib.ServerNa
func DirectoryRoom( func DirectoryRoom(
req *http.Request, req *http.Request,
roomAlias string, roomAlias string,
federation *fclient.FederationClient, federation fclient.FederationClient,
cfg *config.ClientAPI, cfg *config.ClientAPI,
rsAPI roomserverAPI.ClientRoomserverAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
fedSenderAPI federationAPI.ClientFederationAPI, fedSenderAPI federationAPI.ClientFederationAPI,
@ -55,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'"),
} }
} }
@ -69,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
@ -83,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)
@ -92,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),
), ),
} }
@ -102,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)
} }
@ -125,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"),
} }
} }
@ -145,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 {
@ -157,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"),
} }
} }
} }
@ -180,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."),
} }
} }
@ -203,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."),
} }
} }
@ -248,12 +296,15 @@ 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
if len(res.RoomIDs) == 1 { if len(res.RoomIDs) == 1 {
v.Visibility = gomatrixserverlib.Public v.Visibility = spec.Public
} else { } else {
v.Visibility = "private" v.Visibility = "private"
} }
@ -270,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
} }
@ -278,23 +352,26 @@ func SetVisibility(
queryEventsReq := roomserverAPI.QueryLatestEventsAndStateRequest{ queryEventsReq := roomserverAPI.QueryLatestEventsAndStateRequest{
RoomID: roomID, RoomID: roomID,
StateToFetch: []gomatrixserverlib.StateKeyTuple{{ StateToFetch: []gomatrixserverlib.StateKeyTuple{{
EventType: gomatrixserverlib.MRoomPowerLevels, EventType: spec.MRoomPowerLevels,
StateKey: "", StateKey: "",
}}, }},
} }
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].Event) power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU)
if power.UserLevel(dev.UserID) < power.EventLevel(gomatrixserverlib.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"),
} }
} }
@ -303,16 +380,15 @@ func SetVisibility(
return *reqErr return *reqErr
} }
var publishRes roomserverAPI.PerformPublishResponse if err = rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
RoomID: roomID, RoomID: roomID,
Visibility: v.Visibility, Visibility: v.Visibility,
}, &publishRes); err != nil { }); err != nil {
return jsonerror.InternalAPIError(req.Context(), err) util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
} }
if publishRes.Error != nil {
util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed")
return publishRes.Error.JSONResponse()
} }
return util.JSONResponse{ return util.JSONResponse{
@ -328,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
@ -341,18 +417,17 @@ func SetVisibilityAS(
return *reqErr return *reqErr
} }
} }
var publishRes roomserverAPI.PerformPublishResponse
if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{ if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
RoomID: roomID, RoomID: roomID,
Visibility: v.Visibility, Visibility: v.Visibility,
NetworkID: networkID, NetworkID: networkID,
AppserviceID: dev.AppserviceID, AppserviceID: dev.AppserviceID,
}, &publishRes); err != nil { }); err != nil {
return jsonerror.InternalAPIError(req.Context(), err) util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
} }
if publishRes.Error != nil {
util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed")
return publishRes.Error.JSONResponse()
} }
return util.JSONResponse{ return util.JSONResponse{

View file

@ -23,13 +23,12 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/matrix-org/gomatrixserverlib"
"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"
"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"
) )
@ -57,7 +56,7 @@ type filter struct {
func GetPostPublicRooms( func GetPostPublicRooms(
req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI,
extRoomsProvider api.ExtraPublicRoomsProvider, extRoomsProvider api.ExtraPublicRoomsProvider,
federation *fclient.FederationClient, federation fclient.FederationClient,
cfg *config.ClientAPI, cfg *config.ClientAPI,
) util.JSONResponse { ) util.JSONResponse {
var request PublicRoomReq var request PublicRoomReq
@ -68,11 +67,11 @@ 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"),
} }
} }
serverName := gomatrixserverlib.ServerName(request.Server) serverName := spec.ServerName(request.Server)
if serverName != "" && !cfg.Matrix.IsLocalServerName(serverName) { if serverName != "" && !cfg.Matrix.IsLocalServerName(serverName) {
res, err := federation.GetPublicRoomsFiltered( res, err := federation.GetPublicRoomsFiltered(
req.Context(), cfg.Matrix.ServerName, serverName, req.Context(), cfg.Matrix.ServerName, serverName,
@ -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)

View file

@ -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{}

View file

@ -15,15 +15,17 @@
package routing package routing
import ( import (
"encoding/json"
"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"
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" "github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -41,7 +43,6 @@ func JoinRoomByIDOrAlias(
IsGuest: device.AccountType == api.AccountTypeGuest, IsGuest: device.AccountType == api.AccountTypeGuest,
Content: map[string]interface{}{}, Content: map[string]interface{}{},
} }
joinRes := roomserverAPI.PerformJoinResponse{}
// Check to see if any ?server_name= query parameters were // Check to see if any ?server_name= query parameters were
// given in the request. // given in the request.
@ -49,7 +50,7 @@ func JoinRoomByIDOrAlias(
for _, serverName := range serverNames { for _, serverName := range serverNames {
joinReq.ServerNames = append( joinReq.ServerNames = append(
joinReq.ServerNames, joinReq.ServerNames,
gomatrixserverlib.ServerName(serverName), spec.ServerName(serverName),
) )
} }
} }
@ -72,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:
} }
@ -81,37 +82,65 @@ func JoinRoomByIDOrAlias(
done := make(chan util.JSONResponse, 1) done := make(chan util.JSONResponse, 1)
go func() { go func() {
defer close(done) defer close(done)
if err := rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes); err != nil { roomID, _, err := rsAPI.PerformJoin(req.Context(), &joinReq)
done <- jsonerror.InternalAPIError(req.Context(), err) var response util.JSONResponse
} else if joinRes.Error != nil {
if joinRes.Error.Code == roomserverAPI.PerformErrorNotAllowed && device.AccountType == api.AccountTypeGuest { switch e := err.(type) {
done <- util.JSONResponse{ case nil: // success case
Code: http.StatusForbidden, response = util.JSONResponse{
JSON: jsonerror.GuestAccessForbidden(joinRes.Error.Msg),
}
} else {
done <- joinRes.Error.JSONResponse()
}
} else {
done <- util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,
// TODO: Put the response struct somewhere internal. // TODO: Put the response struct somewhere internal.
JSON: struct { JSON: struct {
RoomID string `json:"room_id"` RoomID string `json:"room_id"`
}{joinRes.RoomID}, }{roomID},
}
case roomserverAPI.ErrInvalidID:
response = util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.Unknown(e.Error()),
}
case roomserverAPI.ErrNotAllowed:
jsonErr := spec.Forbidden(e.Error())
if device.AccountType == api.AccountTypeGuest {
jsonErr = spec.GuestAccessForbidden(e.Error())
}
response = util.JSONResponse{
Code: http.StatusForbidden,
JSON: jsonErr,
}
case *gomatrix.HTTPError: // this ensures we proxy responses over federation to the client
response = util.JSONResponse{
Code: e.Code,
JSON: json.RawMessage(e.Message),
}
case eventutil.ErrRoomNoExists:
response = util.JSONResponse{
Code: http.StatusNotFound,
JSON: spec.NotFound(e.Error()),
}
default:
response = util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
} }
} }
done <- response
}() }()
// Wait either for the join to finish, or for us to hit a reasonable // Wait either for the join to finish, or for us to hit a reasonable
// timeout, at which point we'll just return a 200 to placate clients. // timeout, at which point we'll just return a 200 to placate clients.
timer := time.NewTimer(time.Second * 20)
select { select {
case <-time.After(time.Second * 20): 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
if !timer.Stop() {
<-timer.C
}
return result return result
} }
} }

View file

@ -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,10 +64,9 @@ 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},
GuestCanJoin: false,
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now()) }, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
crResp, ok := resp.JSON.(createRoomResponse) crResp, ok := resp.JSON.(createRoomResponse)
if !ok { if !ok {
@ -79,9 +79,8 @@ 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},
GuestCanJoin: true,
}, 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)
if !ok { if !ok {

View file

@ -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"
) )
@ -61,28 +61,26 @@ func CreateKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
if resErr != nil { if resErr != nil {
return *resErr return *resErr
} }
var performKeyBackupResp userapi.PerformKeyBackupResponse if len(kb.AuthData) == 0 {
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.BadJSON("missing auth_data"),
}
}
version, err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
UserID: device.UserID, UserID: device.UserID,
Version: "", Version: "",
AuthData: kb.AuthData, AuthData: kb.AuthData,
Algorithm: kb.Algorithm, Algorithm: kb.Algorithm,
}, &performKeyBackupResp); err != nil { })
return jsonerror.InternalServerError() if err != nil {
} return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", err))
if performKeyBackupResp.Error != "" {
if performKeyBackupResp.BadInput {
return util.JSONResponse{
Code: 400,
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error),
}
}
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
} }
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: keyBackupVersionCreateResponse{ JSON: keyBackupVersionCreateResponse{
Version: performKeyBackupResp.Version, Version: version,
}, },
} }
} }
@ -90,20 +88,17 @@ func CreateKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
// KeyBackupVersion returns the key backup version specified. If `version` is empty, the latest `keyBackupVersionResponse` is returned. // KeyBackupVersion returns the key backup version specified. If `version` is empty, the latest `keyBackupVersionResponse` is returned.
// Implements GET /_matrix/client/r0/room_keys/version and GET /_matrix/client/r0/room_keys/version/{version} // Implements GET /_matrix/client/r0/room_keys/version and GET /_matrix/client/r0/room_keys/version/{version}
func KeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse { func KeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse {
var queryResp userapi.QueryKeyBackupResponse queryResp, err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
if err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
UserID: device.UserID, UserID: device.UserID,
Version: version, Version: version,
}, &queryResp); err != nil { })
return jsonerror.InternalAPIError(req.Context(), err) if err != nil {
} return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", err))
if queryResp.Error != "" {
return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", queryResp.Error))
} }
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{
@ -126,31 +121,29 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse
if resErr != nil { if resErr != nil {
return *resErr return *resErr
} }
var performKeyBackupResp userapi.PerformKeyBackupResponse performKeyBackupResp, err := userAPI.UpdateBackupKeyAuthData(req.Context(), &userapi.PerformKeyBackupRequest{
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
UserID: device.UserID, UserID: device.UserID,
Version: version, Version: version,
AuthData: kb.AuthData, AuthData: kb.AuthData,
Algorithm: kb.Algorithm, Algorithm: kb.Algorithm,
}, &performKeyBackupResp); err != nil { })
return jsonerror.InternalServerError() switch e := err.(type) {
} case spec.ErrRoomKeysVersion:
if performKeyBackupResp.Error != "" {
if performKeyBackupResp.BadInput {
return util.JSONResponse{ return util.JSONResponse{
Code: 400, Code: http.StatusForbidden,
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error), JSON: e,
} }
case nil:
default:
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", e))
} }
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
}
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"),
} }
} }
// Unclear what the 200 body should be
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: keyBackupVersionCreateResponse{ JSON: keyBackupVersionCreateResponse{
@ -162,35 +155,19 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse
// Delete a version of key backup. Version must not be empty. If the key backup was previously deleted, will return 200 OK. // Delete a version of key backup. Version must not be empty. If the key backup was previously deleted, will return 200 OK.
// Implements DELETE /_matrix/client/r0/room_keys/version/{version} // Implements DELETE /_matrix/client/r0/room_keys/version/{version}
func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse { func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse {
var performKeyBackupResp userapi.PerformKeyBackupResponse exists, err := userAPI.DeleteKeyBackup(req.Context(), device.UserID, version)
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{ if err != nil {
UserID: device.UserID, return util.ErrorResponse(fmt.Errorf("DeleteKeyBackup: %s", err))
Version: version,
DeleteBackup: true,
}, &performKeyBackupResp); err != nil {
return jsonerror.InternalServerError()
} }
if performKeyBackupResp.Error != "" { if !exists {
if performKeyBackupResp.BadInput {
return util.JSONResponse{
Code: 400,
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error),
}
}
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
}
if !performKeyBackupResp.Exists {
return util.JSONResponse{ return util.JSONResponse{
Code: 404, Code: 404,
JSON: jsonerror.NotFound("backup version not found"), JSON: spec.NotFound("backup version not found"),
} }
} }
// Unclear what the 200 body should be
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
JSON: keyBackupVersionCreateResponse{ JSON: struct{}{},
Version: performKeyBackupResp.Version,
},
} }
} }
@ -198,27 +175,26 @@ func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
func UploadBackupKeys( func UploadBackupKeys(
req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string, keys *keyBackupSessionRequest, req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string, keys *keyBackupSessionRequest,
) util.JSONResponse { ) util.JSONResponse {
var performKeyBackupResp userapi.PerformKeyBackupResponse performKeyBackupResp, err := userAPI.UpdateBackupKeyAuthData(req.Context(), &userapi.PerformKeyBackupRequest{
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
UserID: device.UserID, UserID: device.UserID,
Version: version, Version: version,
Keys: *keys, Keys: *keys,
}, &performKeyBackupResp); err != nil && performKeyBackupResp.Error == "" { })
return jsonerror.InternalServerError()
} switch e := err.(type) {
if performKeyBackupResp.Error != "" { case spec.ErrRoomKeysVersion:
if performKeyBackupResp.BadInput {
return util.JSONResponse{ return util.JSONResponse{
Code: 400, Code: http.StatusForbidden,
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error), JSON: e,
} }
} case nil:
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error)) default:
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", e))
} }
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{
@ -234,23 +210,20 @@ func UploadBackupKeys(
func GetBackupKeys( func GetBackupKeys(
req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version, roomID, sessionID string, req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version, roomID, sessionID string,
) util.JSONResponse { ) util.JSONResponse {
var queryResp userapi.QueryKeyBackupResponse queryResp, err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
if err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
UserID: device.UserID, UserID: device.UserID,
Version: version, Version: version,
ReturnKeys: true, ReturnKeys: true,
KeysForRoomID: roomID, KeysForRoomID: roomID,
KeysForSessionID: sessionID, KeysForSessionID: sessionID,
}, &queryResp); err != nil { })
return jsonerror.InternalAPIError(req.Context(), err) if err != nil {
} return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %w", err))
if queryResp.Error != "" {
return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", queryResp.Error))
} }
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 != "" {
@ -267,7 +240,10 @@ func GetBackupKeys(
} }
} else if roomID != "" { } else if roomID != "" {
roomData, ok := queryResp.Keys[roomID] roomData, ok := queryResp.Keys[roomID]
if ok { if !ok {
// If no keys are found, then an object with an empty sessions property will be returned
roomData = make(map[string]userapi.KeyBackupSession)
}
// wrap response in "sessions" // wrap response in "sessions"
return util.JSONResponse{ return util.JSONResponse{
Code: 200, Code: 200,
@ -277,7 +253,7 @@ func GetBackupKeys(
Sessions: roomData, Sessions: roomData,
}, },
} }
}
} else { } else {
// response is the same as the upload request // response is the same as the upload request
var resp keyBackupSessionRequest var resp keyBackupSessionRequest
@ -298,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"),
} }
} }

View file

@ -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"
) )
@ -72,31 +72,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()),
} }
} }
} }
@ -116,31 +114,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()),
} }
} }
} }

View file

@ -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,

View file

@ -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()),
} }
} }

View file

@ -19,19 +19,18 @@ 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/ratelimit" "github.com/matrix-org/dendrite/clientapi/ratelimit"
"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" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
type loginResponse struct { type loginResponse struct {
UserID string `json:"user_id"` UserID string `json:"user_id"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
HomeServer gomatrixserverlib.ServerName `json:"home_server"` HomeServer spec.ServerName `json:"home_server"`
DeviceID string `json:"device_id"` DeviceID string `json:"device_id"`
} }
@ -87,7 +86,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"),
} }
} }
@ -98,13 +97,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
@ -120,7 +125,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()),
} }
} }

View file

@ -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{

View file

@ -16,22 +16,25 @@ package routing
import ( import (
"context" "context"
"crypto/ed25519"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/matrix-org/gomatrixserverlib" "github.com/getsentry/sentry-go"
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"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"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"
) )
@ -49,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
} }
@ -62,15 +87,15 @@ 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."),
} }
} }
return sendMembership(req.Context(), profileAPI, device, roomID, gomatrixserverlib.Ban, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI) return sendMembership(req.Context(), profileAPI, device, roomID, spec.Ban, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI)
} }
func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, device *userapi.Device, func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, device *userapi.Device,
@ -83,14 +108,17 @@ 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()
if err = roomserverAPI.SendEvents( if err = roomserverAPI.SendEvents(
ctx, rsAPI, ctx, rsAPI,
roomserverAPI.KindNew, roomserverAPI.KindNew,
[]*gomatrixserverlib.HeaderedEvent{event}, []*types.HeaderedEvent{event},
device.UserDomain(), device.UserDomain(),
serverName, serverName,
serverName, serverName,
@ -98,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{
@ -119,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
} }
@ -132,31 +185,38 @@ 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)
} }
// kick is only valid if the user is not currently banned or left (that is, they are joined or invited) // kick is only valid if the user is not currently banned or left (that is, they are joined or invited)
if queryRes.Membership != gomatrixserverlib.Join && queryRes.Membership != gomatrixserverlib.Invite { 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?
return sendMembership(req.Context(), profileAPI, device, roomID, gomatrixserverlib.Leave, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI) return sendMembership(req.Context(), profileAPI, device, roomID, spec.Leave, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI)
} }
func SendUnban( func SendUnban(
@ -171,33 +231,48 @@ 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)
} }
// unban is only valid if the user is currently banned // unban is only valid if the user is currently banned
if queryRes.Membership != gomatrixserverlib.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?
return sendMembership(req.Context(), profileAPI, device, roomID, gomatrixserverlib.Leave, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI) return sendMembership(req.Context(), profileAPI, device, roomID, spec.Leave, body.Reason, cfg, body.UserID, evTime, rsAPI, asAPI)
} }
func SendInvite( func SendInvite(
@ -230,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
} }
@ -255,29 +338,43 @@ func sendInvite(
asAPI appserviceAPI.AppServiceInternalAPI, evTime time.Time, asAPI appserviceAPI.AppServiceInternalAPI, evTime time.Time,
) (util.JSONResponse, error) { ) (util.JSONResponse, error) {
event, err := buildMembershipEvent( event, err := buildMembershipEvent(
ctx, userID, reason, profileAPI, device, gomatrixserverlib.Invite, ctx, userID, reason, profileAPI, device, spec.Invite,
roomID, false, cfg, evTime, rsAPI, asAPI, roomID, false, cfg, evTime, rsAPI, asAPI,
) )
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
}
var inviteRes api.PerformInviteResponse
if err := rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
Event: event,
InviteRoomState: nil, // ask the roomserver to draw up invite room state for us
RoomVersion: event.RoomVersion,
SendAsServer: string(device.UserDomain()),
}, &inviteRes); err != nil {
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(), JSON: spec.InternalServerError{},
}, err }, err
} }
if inviteRes.Error != nil {
return inviteRes.Error.JSONResponse(), inviteRes.Error err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
Event: event,
InviteRoomState: nil, // ask the roomserver to draw up invite room state for us
RoomVersion: event.Version(),
SendAsServer: string(device.UserDomain()),
})
switch e := err.(type) {
case roomserverAPI.ErrInvalidID:
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.Unknown(e.Error()),
}, e
case roomserverAPI.ErrNotAllowed:
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden(e.Error()),
}, e
case nil:
default:
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
sentry.CaptureException(err)
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}, err
} }
return util.JSONResponse{ return util.JSONResponse{
@ -286,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,
@ -293,37 +426,41 @@ func buildMembershipEvent(
membership, roomID string, isDirect bool, membership, roomID string, isDirect bool,
cfg *config.ClientAPI, evTime time.Time, cfg *config.ClientAPI, evTime time.Time,
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI, rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
) (*gomatrixserverlib.HeaderedEvent, error) { ) (*types.HeaderedEvent, error) {
profile, err := loadProfile(ctx, targetUserID, cfg, profileAPI, asAPI) profile, err := loadProfile(ctx, targetUserID, cfg, profileAPI, asAPI)
if err != nil { if err != nil {
return nil, err return nil, err
} }
builder := gomatrixserverlib.EventBuilder{ 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 = builder.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, &builder, 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
@ -363,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
} }
@ -385,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,
@ -422,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
@ -440,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)),
} }
} }
@ -470,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,
@ -480,20 +641,20 @@ func SendForget(
func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, roomID string) (*gomatrixserverlib.PowerLevelContent, *util.JSONResponse) { func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, roomID string) (*gomatrixserverlib.PowerLevelContent, *util.JSONResponse) {
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{ plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
EventType: gomatrixserverlib.MRoomPowerLevels, EventType: spec.MRoomPowerLevels,
StateKey: "", StateKey: "",
}) })
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

View file

@ -4,10 +4,10 @@ import (
"io" "io"
"net/http" "net/http"
"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/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -23,14 +23,14 @@ func PostMultiroom(
log.WithError(err).Errorf("failed to read request body") log.WithError(err).Errorf("failed to read request body")
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(), JSON: spec.InternalServerError{},
} }
} }
canonicalB, err := gomatrixserverlib.CanonicalJSON(b) canonicalB, err := gomatrixserverlib.CanonicalJSON(b)
if err != nil { if err != nil {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("The request body is not valid canonical JSON." + err.Error()), JSON: spec.BadJSON("The request body is not valid canonical JSON." + err.Error()),
} }
} }
err = producer.SendMultiroom(req.Context(), device.UserID, dataType, canonicalB) err = producer.SendMultiroom(req.Context(), device.UserID, dataType, canonicalB)
@ -38,7 +38,7 @@ func PostMultiroom(
log.WithError(err).Errorf("failed to send multiroomcast") log.WithError(err).Errorf("failed to send multiroomcast")
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.InternalServerError(), JSON: spec.InternalServerError{},
} }
} }
return util.JSONResponse{ return util.JSONResponse{

View file

@ -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{

View file

@ -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{

View file

@ -7,12 +7,12 @@ 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/threepid" "github.com/matrix-org/dendrite/clientapi/threepid"
"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"
) )
@ -62,7 +62,7 @@ func Password(
sessionID = util.RandomString(sessionIDLength) sessionID = util.RandomString(sessionIDLength)
} }
var localpart string var localpart string
var domain gomatrixserverlib.ServerName var domain spec.ServerName
switch r.Auth.Type { switch r.Auth.Type {
case authtypes.LoginTypePassword: case authtypes.LoginTypePassword:
// Check if the existing password is correct. // Check if the existing password is correct.
@ -78,7 +78,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{},
}
} }
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword) sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
case authtypes.LoginTypeEmail: case authtypes.LoginTypeEmail:
@ -91,12 +94,15 @@ func Password(
bound, threePid.Address, threePid.Medium, err = threepid.CheckAssociation(req.Context(), r.Auth.ThreePidCreds, cfg, nil) bound, threePid.Address, threePid.Medium, err = threepid.CheckAssociation(req.Context(), r.Auth.ThreePidCreds, cfg, nil)
if err != nil { if err != nil {
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 !bound { if !bound {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.MatrixError{ JSON: spec.MatrixError{
ErrCode: "M_THREEPID_AUTH_FAILED", ErrCode: "M_THREEPID_AUTH_FAILED",
Err: "Failed to auth 3pid", Err: "Failed to auth 3pid",
}, },
@ -109,12 +115,15 @@ func Password(
}, &res) }, &res)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryLocalpartForThreePID failed") util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryLocalpartForThreePID failed")
return jsonerror.InternalServerError() return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
} }
if res.Localpart == "" { if res.Localpart == "" {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.MatrixError{ JSON: spec.MatrixError{
ErrCode: "M_THREEPID_NOT_FOUND", ErrCode: "M_THREEPID_NOT_FOUND",
Err: "3pid is not bound to any account", Err: "3pid is not bound to any account",
}, },
@ -161,11 +170,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
@ -191,7 +206,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{
@ -201,7 +219,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{},
}
} }
} }

View file

@ -15,13 +15,15 @@
package routing package routing
import ( import (
"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/gomatrixserverlib" "github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/sirupsen/logrus"
) )
func PeekRoomByIDOrAlias( func PeekRoomByIDOrAlias(
@ -41,25 +43,42 @@ func PeekRoomByIDOrAlias(
UserID: device.UserID, UserID: device.UserID,
DeviceID: device.ID, DeviceID: device.ID,
} }
peekRes := roomserverAPI.PerformPeekResponse{}
// Check to see if any ?server_name= query parameters were // Check to see if any ?server_name= query parameters were
// given in the request. // given in the request.
if serverNames, ok := req.URL.Query()["server_name"]; ok { if serverNames, ok := req.URL.Query()["server_name"]; ok {
for _, serverName := range serverNames { for _, serverName := range serverNames {
peekReq.ServerNames = append( peekReq.ServerNames = append(
peekReq.ServerNames, peekReq.ServerNames,
gomatrixserverlib.ServerName(serverName), spec.ServerName(serverName),
) )
} }
} }
// Ask the roomserver to perform the peek. // Ask the roomserver to perform the peek.
if err := rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes); err != nil { roomID, err := rsAPI.PerformPeek(req.Context(), &peekReq)
return util.ErrorResponse(err) switch e := err.(type) {
case roomserverAPI.ErrInvalidID:
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.Unknown(e.Error()),
}
case roomserverAPI.ErrNotAllowed:
return util.JSONResponse{
Code: http.StatusForbidden,
JSON: spec.Forbidden(e.Error()),
}
case *gomatrix.HTTPError:
return util.JSONResponse{
Code: e.Code,
JSON: json.RawMessage(e.Message),
}
case nil:
default:
logrus.WithError(err).WithField("roomID", roomIDOrAlias).Errorf("Failed to peek room")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
} }
if peekRes.Error != nil {
return peekRes.Error.JSONResponse()
} }
// 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
@ -75,7 +94,7 @@ func PeekRoomByIDOrAlias(
// TODO: Put the response struct somewhere internal. // TODO: Put the response struct somewhere internal.
JSON: struct { JSON: struct {
RoomID string `json:"room_id"` RoomID string `json:"room_id"`
}{peekRes.RoomID}, }{roomID},
} }
} }
@ -85,18 +104,20 @@ func UnpeekRoomByID(
rsAPI roomserverAPI.ClientRoomserverAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
roomID string, roomID string,
) util.JSONResponse { ) util.JSONResponse {
unpeekReq := roomserverAPI.PerformUnpeekRequest{ err := rsAPI.PerformUnpeek(req.Context(), roomID, device.UserID, device.ID)
RoomID: roomID, switch e := err.(type) {
UserID: device.UserID, case roomserverAPI.ErrInvalidID:
DeviceID: device.ID, return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.Unknown(e.Error()),
} }
unpeekRes := roomserverAPI.PerformUnpeekResponse{} case nil:
default:
if err := rsAPI.PerformUnpeek(req.Context(), &unpeekReq, &unpeekRes); err != nil { logrus.WithError(err).WithField("roomID", roomID).Errorf("Failed to un-peek room")
return jsonerror.InternalAPIError(req.Context(), err) return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
} }
if unpeekRes.Error != nil {
return unpeekRes.Error.JSONResponse()
} }
return util.JSONResponse{ return util.JSONResponse{

View file

@ -21,13 +21,12 @@ 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"
"github.com/matrix-org/dendrite/syncapi/types" "github.com/matrix-org/dendrite/syncapi/types"
"github.com/matrix-org/dendrite/userapi/api" "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/matrix-org/util"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -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,11 +118,11 @@ 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{},
} }
} }
p := types.PresenceInternal{LastActiveTS: gomatrixserverlib.Timestamp(lastActive)} p := types.PresenceInternal{LastActiveTS: spec.Timestamp(lastActive)}
currentlyActive := p.CurrentlyActive() currentlyActive := p.CurrentlyActive()
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,

View file

@ -21,16 +21,16 @@ import (
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/fclient"
"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/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/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"
) )
@ -40,19 +40,22 @@ func GetProfile(
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI, req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
userID string, userID string,
asAPI appserviceAPI.AppServiceInternalAPI, asAPI appserviceAPI.AppServiceInternalAPI,
federation *fclient.FederationClient, federation fclient.FederationClient,
) util.JSONResponse { ) util.JSONResponse {
profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation) profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
if err != nil { if err != nil {
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{
@ -68,7 +71,7 @@ func GetProfile(
func GetAvatarURL( func GetAvatarURL(
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI, req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
userID string, asAPI appserviceAPI.AppServiceInternalAPI, userID string, asAPI appserviceAPI.AppServiceInternalAPI,
federation *fclient.FederationClient, federation fclient.FederationClient,
) util.JSONResponse { ) util.JSONResponse {
profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation) profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation)
p, ok := profile.JSON.(eventutil.UserProfile) p, ok := profile.JSON.(eventutil.UserProfile)
@ -93,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"),
} }
} }
@ -105,13 +108,16 @@ func SetAvatarURL(
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"),
} }
} }
@ -119,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 {
@ -136,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
} }
@ -151,7 +160,7 @@ func SetAvatarURL(
func GetDisplayName( func GetDisplayName(
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI, req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
userID string, asAPI appserviceAPI.AppServiceInternalAPI, userID string, asAPI appserviceAPI.AppServiceInternalAPI,
federation *fclient.FederationClient, federation fclient.FederationClient,
) util.JSONResponse { ) util.JSONResponse {
profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation) profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation)
p, ok := profile.JSON.(eventutil.UserProfile) p, ok := profile.JSON.(eventutil.UserProfile)
@ -176,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"),
} }
} }
@ -184,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"),
} }
} }
@ -208,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 {
@ -225,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
} }
@ -239,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{
@ -248,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
} }
@ -287,7 +308,7 @@ func getProfile(
ctx context.Context, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI, ctx context.Context, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
userID string, userID string,
asAPI appserviceAPI.AppServiceInternalAPI, asAPI appserviceAPI.AppServiceInternalAPI,
federation *fclient.FederationClient, federation fclient.FederationClient,
) (*authtypes.Profile, error) { ) (*authtypes.Profile, error) {
localpart, domain, err := gomatrixserverlib.SplitID('@', userID) localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
if err != nil { if err != nil {
@ -323,49 +344,60 @@ 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,
) ([]*gomatrixserverlib.HeaderedEvent, error) { ) ([]*types.HeaderedEvent, error) {
evs := []*gomatrixserverlib.HeaderedEvent{} evs := []*types.HeaderedEvent{}
for _, roomID := range roomIDs { fullUserID, err := spec.NewUserID(userID, true)
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} if err != nil {
verRes := api.QueryRoomVersionForRoomResponse{}
if err := rsAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
return nil, err return nil, err
} }
for _, roomID := range roomIDs {
builder := gomatrixserverlib.EventBuilder{ validRoomID, err := spec.NewRoomID(roomID)
Sender: userID, 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{
SenderID: senderIDString,
RoomID: roomID, RoomID: roomID,
Type: "m.room.member", Type: "m.room.member",
StateKey: &userID, StateKey: &senderIDString,
} }
content := gomatrixserverlib.MemberContent{ content := gomatrixserverlib.MemberContent{
Membership: gomatrixserverlib.Join, Membership: spec.Join,
} }
content.DisplayName = newProfile.DisplayName content.DisplayName = newProfile.DisplayName
content.AvatarURL = newProfile.AvatarURL content.AvatarURL = newProfile.AvatarURL
if err := builder.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, &builder, cfg.Matrix, identity, evTime, rsAPI, nil) identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *user)
if err != nil { if err != nil {
return nil, err return nil, err
} }
evs = append(evs, event.Headered(verRes.RoomVersion)) event, err := eventutil.QueryAndBuildEvent(ctx, &proto, &identity, evTime, rsAPI, nil)
if err != nil {
return nil, err
}
evs = append(evs, event)
} }
return evs, nil return evs, nil

View file

@ -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),
} }
} }

View file

@ -7,31 +7,34 @@ 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 {
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRulesJSON failed") return errorResponse(ctx, err, "queryPushRulesJSON failed")
} }
@ -42,13 +45,13 @@ func GetAllPushRules(ctx context.Context, device *userapi.Device, userAPI userap
} }
func GetPushRulesByScope(ctx context.Context, scope string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse { func GetPushRulesByScope(ctx context.Context, scope string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRulesJSON failed") return errorResponse(ctx, err, "queryPushRulesJSON failed")
} }
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,
@ -57,17 +60,18 @@ func GetPushRulesByScope(ctx context.Context, scope string, device *userapi.Devi
} }
func GetPushRulesByKind(ctx context.Context, scope, kind string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse { func GetPushRulesByKind(ctx context.Context, scope, kind string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRules failed") return errorResponse(ctx, err, "queryPushRules failed")
} }
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 { // Even if rulesPtr is not nil, there may not be any rules for this kind
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed") if rulesPtr == nil || (rulesPtr != nil && len(*rulesPtr) == 0) {
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
} }
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,
@ -76,21 +80,21 @@ func GetPushRulesByKind(ctx context.Context, scope, kind string, device *userapi
} }
func GetPushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse { func GetPushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRules failed") return errorResponse(ctx, err, "queryPushRules failed")
} }
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,
@ -101,26 +105,30 @@ func GetPushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device
func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID, beforeRuleID string, body io.Reader, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse { func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID, beforeRuleID string, body io.Reader, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
var newRule pushrules.Rule var newRule pushrules.Rule
if err := json.NewDecoder(body).Decode(&newRule); err != nil { if err := json.NewDecoder(body).Decode(&newRule); err != nil {
return errorResponse(ctx, err, "JSON Decode failed") return util.JSONResponse{
Code: http.StatusBadRequest,
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 := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRules failed") return errorResponse(ctx, err, "queryPushRules failed")
} }
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") // while this should be impossible (ValidateRule would already return an error), better keep it around
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 == "" {
@ -144,7 +152,7 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
} }
// Add new rule. // Add new rule.
i, err := findPushRuleInsertionIndex(*rulesPtr, afterRuleID, beforeRuleID) i, err = findPushRuleInsertionIndex(*rulesPtr, afterRuleID, beforeRuleID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "findPushRuleInsertionIndex failed") return errorResponse(ctx, err, "findPushRuleInsertionIndex failed")
} }
@ -153,7 +161,7 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
util.GetLogger(ctx).WithField("after", afterRuleID).WithField("before", beforeRuleID).Infof("Added new push rule at %d", i) util.GetLogger(ctx).WithField("after", afterRuleID).WithField("before", beforeRuleID).Infof("Added new push rule at %d", i)
} }
if err := putPushRules(ctx, device.UserID, ruleSets, userAPI); err != nil { if err = userAPI.PerformPushRulesPut(ctx, device.UserID, ruleSets); err != nil {
return errorResponse(ctx, err, "putPushRules failed") return errorResponse(ctx, err, "putPushRules failed")
} }
@ -161,26 +169,26 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
} }
func DeletePushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse { func DeletePushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device *userapi.Device, userAPI userapi.ClientUserAPI) util.JSONResponse {
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRules failed") return errorResponse(ctx, err, "queryPushRules failed")
} }
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:]...)
if err := putPushRules(ctx, device.UserID, ruleSets, userAPI); err != nil { if err = userAPI.PerformPushRulesPut(ctx, device.UserID, ruleSets); err != nil {
return errorResponse(ctx, err, "putPushRules failed") return errorResponse(ctx, err, "putPushRules failed")
} }
@ -192,21 +200,21 @@ func GetPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
if err != nil { if err != nil {
return errorResponse(ctx, err, "pushRuleAttrGetter failed") return errorResponse(ctx, err, "pushRuleAttrGetter failed")
} }
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRules failed") return errorResponse(ctx, err, "queryPushRules failed")
} }
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,
@ -221,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 {
@ -238,27 +246,27 @@ func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
return errorResponse(ctx, err, "pushRuleAttrSetter failed") return errorResponse(ctx, err, "pushRuleAttrSetter failed")
} }
ruleSets, err := queryPushRules(ctx, device.UserID, userAPI) ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
if err != nil { if err != nil {
return errorResponse(ctx, err, "queryPushRules failed") return errorResponse(ctx, err, "queryPushRules failed")
} }
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)) {
attrSet((*rulesPtr)[i], &newPartialRule) attrSet((*rulesPtr)[i], &newPartialRule)
if err := putPushRules(ctx, device.UserID, ruleSets, userAPI); err != nil { if err = userAPI.PerformPushRulesPut(ctx, device.UserID, ruleSets); err != nil {
return errorResponse(ctx, err, "putPushRules failed") return errorResponse(ctx, err, "putPushRules failed")
} }
} }
@ -266,28 +274,6 @@ func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
return util.JSONResponse{Code: http.StatusOK, JSON: struct{}{}} return util.JSONResponse{Code: http.StatusOK, JSON: struct{}{}}
} }
func queryPushRules(ctx context.Context, userID string, userAPI userapi.ClientUserAPI) (*pushrules.AccountRuleSets, error) {
var res userapi.QueryPushRulesResponse
if err := userAPI.QueryPushRules(ctx, &userapi.QueryPushRulesRequest{UserID: userID}, &res); err != nil {
util.GetLogger(ctx).WithError(err).Error("userAPI.QueryPushRules failed")
return nil, err
}
return res.RuleSets, nil
}
func putPushRules(ctx context.Context, userID string, ruleSets *pushrules.AccountRuleSets, userAPI userapi.ClientUserAPI) error {
req := userapi.PerformPushRulesPutRequest{
UserID: userID,
RuleSets: ruleSets,
}
var res struct{}
if err := userAPI.PerformPushRulesPut(ctx, &req, &res); err != nil {
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformPushRulesPut failed")
return err
}
return nil
}
func pushRuleSetByScope(ruleSets *pushrules.AccountRuleSets, scope pushrules.Scope) *pushrules.RuleSet { func pushRuleSetByScope(ruleSets *pushrules.AccountRuleSets, scope pushrules.Scope) *pushrules.RuleSet {
switch scope { switch scope {
case pushrules.GlobalScope: case pushrules.GlobalScope:
@ -330,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")
} }
} }
@ -341,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")
} }
} }
@ -355,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
@ -373,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")
} }
} }

View file

@ -20,9 +20,8 @@ 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" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
@ -31,7 +30,7 @@ import (
) )
func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse { func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
timestamp := gomatrixserverlib.AsTimestamp(time.Now()) timestamp := spec.AsTimestamp(time.Now())
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"roomID": roomID, "roomID": roomID,
"receiptType": receiptType, "receiptType": receiptType,
@ -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{

View file

@ -16,17 +16,19 @@ package routing
import ( import (
"context" "context"
"errors"
"net/http" "net/http"
"time" "time"
"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/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"
"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"
) )
@ -45,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
} }
@ -61,46 +85,46 @@ 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: gomatrixserverlib.MRoomPowerLevels, EventType: spec.MRoomPowerLevels,
StateKey: "", StateKey: "",
}) })
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."),
} }
} }
@ -111,35 +135,44 @@ func SendRedaction(
} }
// create the new event and set all the fields we can // create the new event and set all the fields we can
builder := gomatrixserverlib.EventBuilder{ proto := gomatrixserverlib.ProtoEvent{
Sender: device.UserID, SenderID: string(senderID),
RoomID: roomID, RoomID: roomID,
Type: gomatrixserverlib.MRoomRedaction, Type: spec.MRoomRedaction,
Redacts: eventID, Redacts: eventID,
} }
err := builder.SetContent(r) err = proto.SetContent(r)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("builder.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(), &builder, 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, []*gomatrixserverlib.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{

View file

@ -37,6 +37,7 @@ import (
"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/gomatrixserverlib/spec"
"github.com/matrix-org/gomatrixserverlib/tokens" "github.com/matrix-org/gomatrixserverlib/tokens"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -45,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/threepid" "github.com/matrix-org/dendrite/clientapi/threepid"
"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"
@ -165,7 +165,7 @@ func (d *sessionsDict) addCompletedSessionStage(sessionID string, stage authtype
return return
} }
} }
d.sessions[sessionID] = append(sessions.sessions[sessionID], stage) d.sessions[sessionID] = append(d.sessions[sessionID], stage)
} }
func (d *sessionsDict) addDeviceToDelete(sessionID, deviceID string) { func (d *sessionsDict) addDeviceToDelete(sessionID, deviceID string) {
@ -209,7 +209,7 @@ type registerRequest struct {
// registration parameters // registration parameters
Password string `json:"password"` Password string `json:"password"`
Username string `json:"username"` Username string `json:"username"`
ServerName gomatrixserverlib.ServerName `json:"-"` ServerName spec.ServerName `json:"-"`
Admin bool `json:"admin"` Admin bool `json:"admin"`
// user-interactive auth params // user-interactive auth params
Auth authDict `json:"auth"` Auth authDict `json:"auth"`
@ -429,7 +429,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"),
} }
} }
@ -440,7 +440,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)),
} }
} }
@ -449,7 +449,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)),
} }
} }
@ -475,12 +475,12 @@ 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"),
} }
} }
var r registerRequest var r registerRequest
host := gomatrixserverlib.ServerName(req.Host) host := spec.ServerName(req.Host)
if v := cfg.Matrix.VirtualHostForHTTPHost(host); v != nil { if v := cfg.Matrix.VirtualHostForHTTPHost(host); v != nil {
r.ServerName = v.ServerName r.ServerName = v.ServerName
} else { } else {
@ -519,7 +519,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
@ -530,7 +530,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)
} }
@ -553,7 +556,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
@ -591,7 +594,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),
), ),
} }
@ -605,7 +608,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{
@ -617,7 +620,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
@ -633,7 +636,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{
@ -683,7 +686,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),
), ),
} }
@ -697,7 +700,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."),
} }
} }
@ -708,15 +711,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
@ -737,12 +740,15 @@ func handleRegistrationFlow(
bound, threePid.Address, threePid.Medium, err = threepid.CheckAssociation(req.Context(), r.Auth.ThreePidCreds, cfg, nil) bound, threePid.Address, threePid.Medium, err = threepid.CheckAssociation(req.Context(), r.Auth.ThreePidCreds, cfg, nil)
if err != nil { if err != nil {
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 !bound { if !bound {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.MatrixError{ JSON: spec.MatrixError{
ErrCode: "M_THREEPID_AUTH_FAILED", ErrCode: "M_THREEPID_AUTH_FAILED",
Err: "Failed to auth 3pid", Err: "Failed to auth 3pid",
}, },
@ -757,7 +763,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"),
} }
} }
@ -789,7 +795,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()),
} }
} }
@ -849,7 +855,7 @@ func checkAndCompleteFlow(
func completeRegistration( func completeRegistration(
ctx context.Context, ctx context.Context,
userAPI userapi.ClientUserAPI, userAPI userapi.ClientUserAPI,
username string, serverName gomatrixserverlib.ServerName, displayName string, username string, serverName spec.ServerName, displayName string,
password, appserviceID, ipAddr, userAgent, sessionID string, password, appserviceID, ipAddr, userAgent, sessionID string,
inhibitLogin eventutil.WeakBoolean, inhibitLogin eventutil.WeakBoolean,
deviceDisplayName, deviceID *string, deviceDisplayName, deviceID *string,
@ -859,14 +865,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
@ -882,12 +888,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()),
} }
} }
@ -905,7 +911,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 save 3PID association: " + err.Error()), JSON: spec.Unknown("Failed to save 3PID association: " + err.Error()),
} }
} }
} }
@ -925,7 +931,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"),
} }
} }
@ -934,7 +940,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()),
} }
} }
} }
@ -952,7 +958,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()),
} }
} }
@ -1036,7 +1042,7 @@ func RegisterAvailable(
// Squash username to all lowercase letters // Squash username to all lowercase letters
username = strings.ToLower(username) username = strings.ToLower(username)
domain := cfg.Matrix.ServerName domain := cfg.Matrix.ServerName
host := gomatrixserverlib.ServerName(req.Host) host := spec.ServerName(req.Host)
if v := cfg.Matrix.VirtualHostForHTTPHost(host); v != nil { if v := cfg.Matrix.VirtualHostForHTTPHost(host); v != nil {
domain = v.ServerName domain = v.ServerName
} }
@ -1047,7 +1053,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)),
), ),
} }
@ -1064,7 +1070,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."),
} }
} }
} }
@ -1077,14 +1083,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."),
} }
} }
@ -1101,7 +1107,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)
@ -1111,7 +1117,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

View file

@ -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)
} }

View file

@ -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{

View file

@ -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"
"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"
"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"
"github.com/matrix-org/dendrite/clientapi/ratelimit" "github.com/matrix-org/dendrite/clientapi/ratelimit"
federationAPI "github.com/matrix-org/dendrite/federationapi/api" federationAPI "github.com/matrix-org/dendrite/federationapi/api"
@ -57,7 +58,7 @@ func Setup(
asAPI appserviceAPI.AppServiceInternalAPI, asAPI appserviceAPI.AppServiceInternalAPI,
userAPI userapi.ClientUserAPI, userAPI userapi.ClientUserAPI,
userDirectoryProvider userapi.QuerySearchProfilesAPI, userDirectoryProvider userapi.QuerySearchProfilesAPI,
federation *fclient.FederationClient, federation fclient.FederationClient,
syncProducer *producers.SyncAPIProducer, syncProducer *producers.SyncAPIProducer,
transactionsCache *transactions.Cache, transactionsCache *transactions.Cache,
federationSender federationAPI.ClientFederationAPI, federationSender federationAPI.ClientFederationAPI,
@ -87,6 +88,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 {
@ -150,11 +159,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 {
@ -164,13 +203,13 @@ func Setup(
dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}", dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}",
httputil.MakeAdminAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAdminAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminEvacuateUser(req, cfg, rsAPI) return AdminEvacuateUser(req, rsAPI)
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/purgeRoom/{roomID}", dendriteAdminRouter.Handle("/admin/purgeRoom/{roomID}",
httputil.MakeAdminAPI("admin_purge_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAdminAPI("admin_purge_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminPurgeRoom(req, cfg, device, rsAPI) return AdminPurgeRoom(req, rsAPI)
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -182,7 +221,7 @@ func Setup(
dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}", dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}",
httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminDownloadState(req, cfg, device, rsAPI) return AdminDownloadState(req, device, rsAPI)
}), }),
).Methods(http.MethodGet, http.MethodOptions) ).Methods(http.MethodGet, http.MethodOptions)
@ -259,7 +298,7 @@ func Setup(
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/join/{roomIDOrAlias}", v3mux.Handle("/join/{roomIDOrAlias}",
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil { if r := rateLimits.Limit(req, device); r != nil {
return *r return *r
} }
@ -267,15 +306,23 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
// Only execute a join for roomIDOrAlias and UserID once. If there is a join in progress
// 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( return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomIDOrAlias"], 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)
if mscCfg.Enabled("msc2753") { if mscCfg.Enabled("msc2753") {
v3mux.Handle("/peek/{roomIDOrAlias}", v3mux.Handle("/peek/{roomIDOrAlias}",
httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI(spec.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil { if r := rateLimits.Limit(req, device); r != nil {
return *r return *r
} }
@ -295,7 +342,7 @@ func Setup(
}, httputil.WithAllowGuests()), }, httputil.WithAllowGuests()),
).Methods(http.MethodGet, http.MethodOptions) ).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/join", v3mux.Handle("/rooms/{roomID}/join",
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil { if r := rateLimits.Limit(req, device); r != nil {
return *r return *r
} }
@ -303,9 +350,17 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
// Only execute a join for roomID and UserID once. If there is a join in progress
// 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( return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomID"], 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",
@ -672,7 +727,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)
@ -687,7 +742,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)
@ -706,7 +761,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)
@ -715,7 +770,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)
@ -734,7 +789,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)
@ -743,7 +798,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)
@ -952,7 +1007,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)
@ -1120,7 +1175,7 @@ func Setup(
v3mux.Handle("/delete_devices", v3mux.Handle("/delete_devices",
httputil.MakeAuthAPI("delete_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI("delete_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return DeleteDevices(req, userAPI, device) return DeleteDevices(req, userInteractiveAuth, userAPI, device)
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -1257,7 +1312,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
@ -1278,7 +1333,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"]
@ -1310,7 +1365,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
@ -1411,7 +1466,7 @@ func Setup(
}, httputil.WithAllowGuests()), }, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}", v3mux.Handle("/rooms/{roomId}/receipt/{receiptType}/{eventId}",
httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
if r := rateLimits.Limit(req, device); r != nil { if r := rateLimits.Limit(req, device); r != nil {
return *r return *r
} }

View file

@ -23,18 +23,18 @@ import (
"sync" "sync"
"time" "time"
"github.com/matrix-org/gomatrixserverlib"
"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/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
@ -67,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,
@ -75,12 +77,11 @@ func SendEvent(
rsAPI api.ClientRoomserverAPI, rsAPI api.ClientRoomserverAPI,
txnCache *transactions.Cache, txnCache *transactions.Cache,
) util.JSONResponse { ) util.JSONResponse {
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} roomVersion, err := rsAPI.QueryRoomVersionForRoom(req.Context(), roomID)
verRes := api.QueryRoomVersionForRoomResponse{} if err != nil {
if err := rsAPI.QueryRoomVersionForRoom(req.Context(), &verReq, &verRes); err != nil {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.UnsupportedRoomVersion(err.Error()), JSON: spec.UnsupportedRoomVersion(err.Error()),
} }
} }
@ -117,26 +118,37 @@ func SendEvent(
// If we're sending a membership update, make sure to strip the authorised // If we're sending a membership update, make sure to strip the authorised
// via key if it is present, otherwise other servers won't be able to auth // via key if it is present, otherwise other servers won't be able to auth
// the event if the room is set to the "restricted" join rule. // the event if the room is set to the "restricted" join rule.
if eventType == gomatrixserverlib.MRoomMember { if eventType == spec.MRoomMember {
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
} }
timeToGenerateEvent := time.Since(startedGeneratingEvent) timeToGenerateEvent := time.Since(startedGeneratingEvent)
// validate that the aliases exists // validate that the aliases exists
if eventType == gomatrixserverlib.MRoomCanonicalAlias && stateKey != nil && *stateKey == "" { if eventType == spec.MRoomCanonicalAlias && stateKey != nil && *stateKey == "" {
aliasReq := api.AliasEvent{} aliasReq := api.AliasEvent{}
if err = json.Unmarshal(e.Content(), &aliasReq); err != nil { if err = json.Unmarshal(e.Content(), &aliasReq); err != nil {
return util.ErrorResponse(fmt.Errorf("unable to parse alias event: %w", err)) return util.ErrorResponse(fmt.Errorf("unable to parse alias event: %w", err))
@ -144,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)
@ -164,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."),
} }
} }
} }
@ -183,8 +198,8 @@ func SendEvent(
if err := api.SendEvents( if err := api.SendEvents(
req.Context(), rsAPI, req.Context(), rsAPI,
api.KindNew, api.KindNew,
[]*gomatrixserverlib.HeaderedEvent{ []*types.HeaderedEvent{
e.Headered(verRes.RoomVersion), &types.HeaderedEvent{PDU: e},
}, },
device.UserDomain(), device.UserDomain(),
domain, domain,
@ -193,13 +208,16 @@ 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{
"event_id": e.EventID(), "event_id": e.EventID(),
"room_id": roomID, "room_id": roomID,
"room_version": verRes.RoomVersion, "room_version": roomVersion,
}).Info("Sent event to roomserver") }).Info("Sent event to roomserver")
res := util.JSONResponse{ res := util.JSONResponse{
@ -219,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 {
@ -255,72 +295,101 @@ 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.Event, *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
builder := gomatrixserverlib.EventBuilder{ proto := gomatrixserverlib.ProtoEvent{
Sender: userID, SenderID: string(senderID),
RoomID: roomID, RoomID: roomID,
Type: eventType, Type: eventType,
StateKey: stateKey, StateKey: stateKey,
} }
err := builder.SetContent(r) err = proto.SetContent(r)
if err != nil { if err != nil {
util.GetLogger(ctx).WithError(err).Error("builder.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, &builder, 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
stateEvents := make([]*gomatrixserverlib.Event, len(queryRes.StateEvents)) stateEvents := make([]gomatrixserverlib.PDU, len(queryRes.StateEvents))
for i := range queryRes.StateEvents { for i := range queryRes.StateEvents {
stateEvents[i] = queryRes.StateEvents[i].Event stateEvents[i] = queryRes.StateEvents[i].PDU
} }
provider := gomatrixserverlib.NewAuthEvents(stateEvents) provider := gomatrixserverlib.NewAuthEvents(gomatrixserverlib.ToPDUs(stateEvents))
if err = gomatrixserverlib.Allowed(e.Event, &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?
} }
} }
@ -331,16 +400,16 @@ 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."),
} }
} }
} }
return e.Event, nil return e.PDU, nil
} }

View file

@ -20,10 +20,10 @@ import (
"github.com/sirupsen/logrus" "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/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}
@ -54,7 +54,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{},
}
} }
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"to_device_id": deviceID, "to_device_id": deviceID,

View file

@ -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{

View file

@ -22,22 +22,22 @@ import (
"time" "time"
"github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/tokens" "github.com/matrix-org/gomatrixserverlib/tokens"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/dendrite/roomserver/version" "github.com/matrix-org/dendrite/roomserver/version"
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,9 +156,8 @@ 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,
GuestCanJoin: false,
RoomVersion: roomVersion, RoomVersion: roomVersion,
PowerLevelContentOverride: pl, PowerLevelContentOverride: pl,
} }
@ -176,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:
@ -185,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
@ -207,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,8 +242,8 @@ func SendServerNotice(
if err := api.SendEvents( if err := api.SendEvents(
ctx, rsAPI, ctx, rsAPI,
api.KindNew, api.KindNew,
[]*gomatrixserverlib.HeaderedEvent{ []*types.HeaderedEvent{
e.Headered(roomVersion), {PDU: e},
}, },
device.UserDomain(), device.UserDomain(),
cfgClient.Matrix.ServerName, cfgClient.Matrix.ServerName,
@ -238,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(),
@ -333,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
} }

View file

@ -20,11 +20,12 @@ 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/syncapi/synctypes" "github.com/matrix-org/dendrite/syncapi/synctypes"
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"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -55,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"),
} }
} }
@ -68,11 +72,14 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
// that marks the room as world-readable. If we don't then we assume that // that marks the room as world-readable. If we don't then we assume that
// the room is not world-readable. // the room is not world-readable.
for _, ev := range stateRes.StateEvents { for _, ev := range stateRes.StateEvents {
if ev.Type() == gomatrixserverlib.MRoomHistoryVisibility { if ev.Type() == spec.MRoomHistoryVisibility {
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"
@ -92,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 && membershipRes.Membership != gomatrixserverlib.Invite { if !membershipRes.HasBeenInRoom && membershipRes.Membership != spec.Invite {
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
@ -132,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.HeaderedToClientEvent(ev, synctypes.FormatAll), synctypes.ToClientEventDefault(func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
}, ev),
) )
} }
} else { } else {
@ -146,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.HeaderedToClientEvent(ev, synctypes.FormatAll), synctypes.ToClientEvent(ev, synctypes.FormatAll, sender, sk),
) )
} }
} }
@ -185,9 +227,9 @@ func OnIncomingStateTypeRequest(
StateKey: stateKey, StateKey: stateKey,
}, },
} }
if evType != gomatrixserverlib.MRoomHistoryVisibility && stateKey != "" { if evType != spec.MRoomHistoryVisibility && stateKey != "" {
stateToFetch = append(stateToFetch, gomatrixserverlib.StateKeyTuple{ stateToFetch = append(stateToFetch, gomatrixserverlib.StateKeyTuple{
EventType: gomatrixserverlib.MRoomHistoryVisibility, EventType: spec.MRoomHistoryVisibility,
StateKey: "", StateKey: "",
}) })
} }
@ -201,18 +243,24 @@ 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
// that marks the room as world-readable. If we don't then we assume that // that marks the room as world-readable. If we don't then we assume that
// the room is not world-readable. // the room is not world-readable.
for _, ev := range stateRes.StateEvents { for _, ev := range stateRes.StateEvents {
if ev.Type() == gomatrixserverlib.MRoomHistoryVisibility { if ev.Type() == spec.MRoomHistoryVisibility {
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"
@ -230,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 != gomatrixserverlib.Invite || membershipRes.Membership == gomatrixserverlib.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
@ -265,7 +324,7 @@ func OnIncomingStateTypeRequest(
"state_at_event": !wantLatestState, "state_at_event": !wantLatestState,
}).Info("Fetching state") }).Info("Fetching state")
var event *gomatrixserverlib.HeaderedEvent var event *types.HeaderedEvent
if wantLatestState { if wantLatestState {
// If we are happy to use the latest state, either because the user is // If we are happy to use the latest state, either because the user is
// still in the room, or because the room is world-readable, then just // still in the room, or because the room is world-readable, then just
@ -293,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]
@ -305,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.HeaderedToClientEvent(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{}

View file

@ -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{

View file

@ -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{

View file

@ -15,16 +15,18 @@
package routing package routing
import ( import (
"errors"
"net/http" "net/http"
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"
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"
) )
@ -53,42 +55,43 @@ 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"),
} }
} }
upgradeReq := roomserverAPI.PerformRoomUpgradeRequest{ userID, err := spec.NewUserID(device.UserID, true)
UserID: device.UserID, if err != nil {
RoomID: roomID, util.GetLogger(req.Context()).WithError(err).Error("device UserID is invalid")
RoomVersion: gomatrixserverlib.RoomVersion(r.NewVersion),
}
upgradeResp := roomserverAPI.PerformRoomUpgradeResponse{}
if err := rsAPI.PerformRoomUpgrade(req.Context(), &upgradeReq, &upgradeResp); err != nil {
return jsonerror.InternalAPIError(req.Context(), err)
}
if upgradeResp.Error != nil {
if upgradeResp.Error.Code == roomserverAPI.PerformErrorNoRoom {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusNotFound, Code: http.StatusInternalServerError,
JSON: jsonerror.NotFound("Room does not exist"), JSON: spec.InternalServerError{},
} }
} else if upgradeResp.Error.Code == roomserverAPI.PerformErrorNotAllowed { }
newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion))
switch e := err.(type) {
case nil:
case roomserverAPI.ErrNotAllowed:
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusForbidden, Code: http.StatusForbidden,
JSON: jsonerror.Forbidden(upgradeResp.Error.Msg), JSON: spec.Forbidden(e.Error()),
} }
} else { default:
return jsonerror.InternalServerError() if errors.Is(err, eventutil.ErrRoomNoExists{}) {
return util.JSONResponse{
Code: http.StatusNotFound,
JSON: spec.NotFound("Room does not exist"),
}
}
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
} }
} }
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,
JSON: upgradeRoomResponse{ JSON: upgradeRoomResponse{
ReplacementRoom: upgradeResp.NewRoomID, ReplacementRoom: newRoomID,
}, },
} }
} }

View file

@ -27,6 +27,7 @@ import (
"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/fclient" "github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -42,8 +43,8 @@ func SearchUserDirectory(
provider userapi.QuerySearchProfilesAPI, provider userapi.QuerySearchProfilesAPI,
searchString string, searchString string,
limit int, limit int,
federation *fclient.FederationClient, federation fclient.FederationClient,
localServerName gomatrixserverlib.ServerName, localServerName spec.ServerName,
) util.JSONResponse { ) util.JSONResponse {
if limit < 10 { if limit < 10 {
limit = 10 limit = 10

View file

@ -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))

View file

@ -27,9 +27,11 @@ import (
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"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/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"
) )
// MembershipRequest represents the body of an incoming POST request // MembershipRequest represents the body of an incoming POST request
@ -62,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
@ -97,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
} }
@ -278,7 +300,7 @@ func queryIDServerPubKey(ctx context.Context, idServerName string, keyID string)
} }
var pubKeyRes struct { var pubKeyRes struct {
PublicKey gomatrixserverlib.Base64Bytes `json:"public_key"` PublicKey spec.Base64Bytes `json:"public_key"`
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
@ -333,8 +355,20 @@ func emit3PIDInviteEvent(
rsAPI api.ClientRoomserverAPI, rsAPI api.ClientRoomserverAPI,
evTime time.Time, evTime time.Time,
) error { ) error {
builder := &gomatrixserverlib.EventBuilder{ userID, err := spec.NewUserID(device.UserID, true)
Sender: device.UserID, 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{
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,
@ -348,7 +382,7 @@ func emit3PIDInviteEvent(
PublicKeys: res.PublicKeys, PublicKeys: res.PublicKeys,
} }
if err := builder.SetContent(content); err != nil { if err = proto.SetContent(content); err != nil {
return err return err
} }
@ -358,7 +392,7 @@ func emit3PIDInviteEvent(
} }
queryRes := api.QueryLatestEventsAndStateResponse{} queryRes := api.QueryLatestEventsAndStateResponse{}
event, err := eventutil.QueryAndBuildEvent(ctx, builder, 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
} }
@ -366,8 +400,8 @@ func emit3PIDInviteEvent(
return api.SendEvents( return api.SendEvents(
ctx, rsAPI, ctx, rsAPI,
api.KindNew, api.KindNew,
[]*gomatrixserverlib.HeaderedEvent{ []*types.HeaderedEvent{
event.Headered(queryRes.RoomVersion), event,
}, },
device.UserDomain(), device.UserDomain(),
cfg.Matrix.ServerName, cfg.Matrix.ServerName,

View file

@ -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
@ -138,7 +139,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)
@ -191,5 +192,5 @@ func isTrusted(idServer string, cfg *config.ClientAPI) error {
return nil return nil
} }
} }
return ErrNotTrusted return ErrNotTrusted{}
} }

View file

@ -19,13 +19,14 @@ import (
"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/gomatrixserverlib/spec"
) )
// ParseUsernameParam extracts localpart from usernameParam. // ParseUsernameParam extracts localpart from usernameParam.
// usernameParam can either be a user ID or just the localpart/username. // usernameParam can either be a user ID or just the localpart/username.
// If serverName is passed, it is verified against the domain obtained from usernameParam (if present) // If serverName is passed, it is verified against the domain obtained from usernameParam (if present)
// Returns error in case of invalid usernameParam. // Returns error in case of invalid usernameParam.
func ParseUsernameParam(usernameParam string, cfg *config.Global) (string, gomatrixserverlib.ServerName, error) { func ParseUsernameParam(usernameParam string, cfg *config.Global) (string, spec.ServerName, error) {
localpart := usernameParam localpart := usernameParam
if strings.HasPrefix(usernameParam, "@") { if strings.HasPrefix(usernameParam, "@") {
@ -45,6 +46,6 @@ func ParseUsernameParam(usernameParam string, cfg *config.Global) (string, gomat
} }
// MakeUserID generates user ID from localpart & server name // MakeUserID generates user ID from localpart & server name
func MakeUserID(localpart string, server gomatrixserverlib.ServerName) string { func MakeUserID(localpart string, server spec.ServerName) string {
return fmt.Sprintf("@%s:%s", localpart, string(server)) return fmt.Sprintf("@%s:%s", localpart, string(server))
} }

View file

@ -16,14 +16,14 @@ import (
"testing" "testing"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
) )
var ( var (
localpart = "somelocalpart" localpart = "somelocalpart"
serverName gomatrixserverlib.ServerName = "someservername" serverName spec.ServerName = "someservername"
invalidServerName gomatrixserverlib.ServerName = "invalidservername" invalidServerName spec.ServerName = "invalidservername"
goodUserID = "@" + localpart + ":" + string(serverName) goodUserID = "@" + localpart + ":" + string(serverName)
badUserID = "@bad:user:name@noservername:" badUserID = "@bad:user:name@noservername:"
) )

View file

@ -33,6 +33,7 @@ import (
"github.com/matrix-org/dendrite/setup/jetstream" "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"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/matrix-org/dendrite/appservice" "github.com/matrix-org/dendrite/appservice"
@ -145,7 +146,7 @@ func main() {
} }
} }
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk)) cfg.Global.ServerName = spec.ServerName(hex.EncodeToString(pk))
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID) cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
configErrors := &config.ConfigErrors{} configErrors := &config.ConfigErrors{}

View file

@ -21,6 +21,7 @@ import (
"time" "time"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
) )
const KeyID = "ed25519:dendrite-demo-yggdrasil" const KeyID = "ed25519:dendrite-demo-yggdrasil"
@ -36,7 +37,7 @@ func (f *YggdrasilKeys) KeyRing() *gomatrixserverlib.KeyRing {
func (f *YggdrasilKeys) FetchKeys( func (f *YggdrasilKeys) FetchKeys(
ctx context.Context, ctx context.Context,
requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, requests map[gomatrixserverlib.PublicKeyLookupRequest]spec.Timestamp,
) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) { ) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
res := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) res := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult)
for req := range requests { for req := range requests {
@ -54,7 +55,7 @@ func (f *YggdrasilKeys) FetchKeys(
Key: hexkey, Key: hexkey,
}, },
ExpiredTS: gomatrixserverlib.PublicKeyNotExpired, ExpiredTS: gomatrixserverlib.PublicKeyNotExpired,
ValidUntilTS: gomatrixserverlib.AsTimestamp(time.Now().Add(24 * time.Hour * 365)), ValidUntilTS: spec.AsTimestamp(time.Now().Add(24 * time.Hour * 365)),
} }
} }
return res, nil return res, nil

View file

@ -38,7 +38,7 @@ func (n *Node) CreateClient() *fclient.Client {
func (n *Node) CreateFederationClient( func (n *Node) CreateFederationClient(
cfg *config.Dendrite, cfg *config.Dendrite,
) *fclient.FederationClient { ) fclient.FederationClient {
tr := &http.Transport{} tr := &http.Transport{}
tr.RegisterProtocol( tr.RegisterProtocol(
"matrix", &yggroundtripper{ "matrix", &yggroundtripper{

View file

@ -23,7 +23,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec"
"github.com/neilalexander/utp" "github.com/neilalexander/utp"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -134,14 +134,14 @@ func (n *Node) PeerCount() int {
return len(n.core.GetPeers()) return len(n.core.GetPeers())
} }
func (n *Node) KnownNodes() []gomatrixserverlib.ServerName { func (n *Node) KnownNodes() []spec.ServerName {
nodemap := map[string]struct{}{} nodemap := map[string]struct{}{}
for _, peer := range n.core.GetPeers() { for _, peer := range n.core.GetPeers() {
nodemap[hex.EncodeToString(peer.Key)] = struct{}{} nodemap[hex.EncodeToString(peer.Key)] = struct{}{}
} }
var nodes []gomatrixserverlib.ServerName var nodes []spec.ServerName
for node := range nodemap { for node := range nodemap {
nodes = append(nodes, gomatrixserverlib.ServerName(node)) nodes = append(nodes, spec.ServerName(node))
} }
return nodes return nodes
} }

View file

@ -21,19 +21,19 @@ import (
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn" "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn"
"github.com/matrix-org/dendrite/federationapi/api" "github.com/matrix-org/dendrite/federationapi/api"
"github.com/matrix-org/gomatrixserverlib"
"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"
) )
type YggdrasilRoomProvider struct { type YggdrasilRoomProvider struct {
node *yggconn.Node node *yggconn.Node
fedSender api.FederationInternalAPI fedSender api.FederationInternalAPI
fedClient *fclient.FederationClient fedClient fclient.FederationClient
} }
func NewYggdrasilRoomProvider( func NewYggdrasilRoomProvider(
node *yggconn.Node, fedSender api.FederationInternalAPI, fedClient *fclient.FederationClient, node *yggconn.Node, fedSender api.FederationInternalAPI, fedClient fclient.FederationClient,
) *YggdrasilRoomProvider { ) *YggdrasilRoomProvider {
p := &YggdrasilRoomProvider{ p := &YggdrasilRoomProvider{
node: node, node: node,
@ -46,7 +46,7 @@ func NewYggdrasilRoomProvider(
func (p *YggdrasilRoomProvider) Rooms() []fclient.PublicRoom { func (p *YggdrasilRoomProvider) Rooms() []fclient.PublicRoom {
return bulkFetchPublicRoomsFromServers( return bulkFetchPublicRoomsFromServers(
context.Background(), p.fedClient, context.Background(), p.fedClient,
gomatrixserverlib.ServerName(p.node.DerivedServerName()), spec.ServerName(p.node.DerivedServerName()),
p.node.KnownNodes(), p.node.KnownNodes(),
) )
} }
@ -54,9 +54,9 @@ func (p *YggdrasilRoomProvider) Rooms() []fclient.PublicRoom {
// bulkFetchPublicRoomsFromServers fetches public rooms from the list of homeservers. // bulkFetchPublicRoomsFromServers fetches public rooms from the list of homeservers.
// Returns a list of public rooms. // Returns a list of public rooms.
func bulkFetchPublicRoomsFromServers( func bulkFetchPublicRoomsFromServers(
ctx context.Context, fedClient *fclient.FederationClient, ctx context.Context, fedClient fclient.FederationClient,
origin gomatrixserverlib.ServerName, origin spec.ServerName,
homeservers []gomatrixserverlib.ServerName, homeservers []spec.ServerName,
) (publicRooms []fclient.PublicRoom) { ) (publicRooms []fclient.PublicRoom) {
limit := 200 limit := 200
// follow pipeline semantics, see https://blog.golang.org/pipelines for more info. // follow pipeline semantics, see https://blog.golang.org/pipelines for more info.
@ -69,7 +69,7 @@ func bulkFetchPublicRoomsFromServers(
wg.Add(len(homeservers)) wg.Add(len(homeservers))
// concurrently query for public rooms // concurrently query for public rooms
for _, hs := range homeservers { for _, hs := range homeservers {
go func(homeserverDomain gomatrixserverlib.ServerName) { go func(homeserverDomain spec.ServerName) {
defer wg.Done() defer wg.Done()
util.GetLogger(ctx).WithField("hs", homeserverDomain).Info("Querying HS for public rooms") util.GetLogger(ctx).WithField("hs", homeserverDomain).Info("Querying HS for public rooms")
fres, err := fedClient.GetPublicRooms(ctx, origin, homeserverDomain, int(limit), "", false, "") fres, err := fedClient.GetPublicRooms(ctx, origin, homeserverDomain, int(limit), "", false, "")

View file

@ -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

View file

@ -14,6 +14,7 @@ import (
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
) )
var requestFrom = flag.String("from", "", "the server name that the request should originate from") var requestFrom = flag.String("from", "", "the server name that the request should originate from")
@ -49,7 +50,7 @@ func main() {
panic("unexpected key block") panic("unexpected key block")
} }
serverName := gomatrixserverlib.ServerName(*requestFrom) serverName := spec.ServerName(*requestFrom)
client := fclient.NewFederationClient( client := fclient.NewFederationClient(
[]*fclient.SigningIdentity{ []*fclient.SigningIdentity{
{ {
@ -83,10 +84,10 @@ func main() {
} }
} }
req := gomatrixserverlib.NewFederationRequest( req := fclient.NewFederationRequest(
method, method,
serverName, serverName,
gomatrixserverlib.ServerName(u.Host), spec.ServerName(u.Host),
u.RequestURI(), u.RequestURI(),
) )
@ -97,7 +98,7 @@ func main() {
} }
if err = req.Sign( if err = req.Sign(
gomatrixserverlib.ServerName(*requestFrom), spec.ServerName(*requestFrom),
gomatrixserverlib.KeyID(keyBlock.Headers["Key-ID"]), gomatrixserverlib.KeyID(keyBlock.Headers["Key-ID"]),
privateKey, privateKey,
); err != nil { ); err != nil {

View file

@ -5,11 +5,11 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"github.com/matrix-org/gomatrixserverlib"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/gomatrixserverlib/spec"
) )
func main() { func main() {
@ -30,7 +30,7 @@ func main() {
SingleDatabase: true, SingleDatabase: true,
}) })
if *serverName != "" { if *serverName != "" {
cfg.Global.ServerName = gomatrixserverlib.ServerName(*serverName) cfg.Global.ServerName = spec.ServerName(*serverName)
} }
uri := config.DataSource(*dbURI) uri := config.DataSource(*dbURI)
if uri.IsSQLite() || uri == "" { if uri.IsSQLite() || uri == "" {

View file

@ -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,14 +98,14 @@ 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)
} }
events := make(map[types.EventNID]*gomatrixserverlib.Event, len(eventEntries)) events := make(map[types.EventNID]gomatrixserverlib.PDU, len(eventEntries))
for _, entry := range eventEntries { for _, entry := range eventEntries {
events[entry.EventNID] = entry.Event events[entry.EventNID] = entry.PDU
} }
if len(removed) > 0 { if len(removed) > 0 {
@ -149,15 +156,15 @@ 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)
} }
authEventIDMap := make(map[string]struct{}) authEventIDMap := make(map[string]struct{})
events := make([]*gomatrixserverlib.Event, len(eventEntries)) events := make([]gomatrixserverlib.PDU, len(eventEntries))
for i := range eventEntries { for i := range eventEntries {
events[i] = eventEntries[i].Event events[i] = eventEntries[i].PDU
for _, authEventID := range eventEntries[i].AuthEventIDs() { for _, authEventID := range eventEntries[i].AuthEventIDs() {
authEventIDMap[authEventID] = struct{}{} authEventIDMap[authEventID] = struct{}{}
} }
@ -174,17 +181,17 @@ func main() {
panic(err) panic(err)
} }
authEvents := make([]*gomatrixserverlib.Event, len(authEventEntries)) authEvents := make([]gomatrixserverlib.PDU, len(authEventEntries))
for i := range authEventEntries { for i := range authEventEntries {
authEvents[i] = authEventEntries[i].Event authEvents[i] = authEventEntries[i].PDU
} }
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), gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
events, return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
authEvents, },
) )
if err != nil { if err != nil {
panic(err) panic(err)
@ -208,7 +215,7 @@ func main() {
fmt.Println("Returned", count, "state events after filtering") fmt.Println("Returned", count, "state events after filtering")
} }
type Events []*gomatrixserverlib.Event type Events []gomatrixserverlib.PDU
func (e Events) Len() int { func (e Events) Len() int {
return len(e) return len(e)

View file

@ -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

View file

@ -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?

View file

@ -14,7 +14,7 @@ GEM
execjs execjs
coffee-script-source (1.11.1) coffee-script-source (1.11.1)
colorator (1.1.0) colorator (1.1.0)
commonmarker (0.23.7) commonmarker (0.23.9)
concurrent-ruby (1.2.0) concurrent-ruby (1.2.0)
dnsruby (1.61.9) dnsruby (1.61.9)
simpleidn (~> 0.1) simpleidn (~> 0.1)
@ -231,9 +231,9 @@ GEM
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
minitest (5.17.0) minitest (5.17.0)
multipart-post (2.1.1) multipart-post (2.1.1)
nokogiri (1.13.10-arm64-darwin) nokogiri (1.14.3-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-linux) nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
octokit (4.22.0) octokit (4.22.0)
faraday (>= 0.9) faraday (>= 0.9)
@ -241,7 +241,7 @@ GEM
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (4.0.7) public_suffix (4.0.7)
racc (1.6.1) racc (1.6.2)
rb-fsevent (0.11.1) rb-fsevent (0.11.1)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)

View file

@ -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)

View file

@ -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**:

View file

@ -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
--- ---
@ -51,11 +52,15 @@ the room IDs of all affected rooms.
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>"
} }

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -1,6 +1,7 @@
--- ---
title: Contributing title: Contributing
parent: Development parent: Development
nav_order: 1
permalink: /development/contributing permalink: /development/contributing
--- ---

Some files were not shown because too many files have changed in this diff Show more