Merge branch 'main' of github.com:matrix-org/dendrite into gh-pages
This commit is contained in:
commit
8699464252
|
@ -1,3 +1,2 @@
|
|||
bin
|
||||
*.wasm
|
||||
.git
|
12
.github/workflows/dendrite.yml
vendored
12
.github/workflows/dendrite.yml
vendored
|
@ -67,6 +67,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install libolm
|
||||
run: sudo apt-get install libolm-dev libolm3
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
|
@ -101,6 +103,8 @@ jobs:
|
|||
--health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install libolm
|
||||
run: sudo apt-get install libolm-dev libolm3
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
|
@ -119,7 +123,7 @@ jobs:
|
|||
with:
|
||||
# Optional: pass GITHUB_TOKEN to avoid rate limiting.
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: go test -json -v ./... 2>&1 | gotestfmt
|
||||
- run: go test -json -v ./... 2>&1 | gotestfmt -hide all
|
||||
env:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_USER: postgres
|
||||
|
@ -232,6 +236,8 @@ jobs:
|
|||
--health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install libolm
|
||||
run: sudo apt-get install libolm-dev libolm3
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
|
@ -249,7 +255,7 @@ jobs:
|
|||
key: ${{ runner.os }}-go-stable-test-race-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-stable-test-race-
|
||||
- run: go test -race -json -v -coverpkg=./... -coverprofile=cover.out $(go list ./... | grep -v /cmd/dendrite*) 2>&1 | gotestfmt
|
||||
- run: go test -race -json -v -coverpkg=./... -coverprofile=cover.out $(go list ./... | grep -v /cmd/dendrite*) 2>&1 | gotestfmt -hide all
|
||||
env:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_USER: postgres
|
||||
|
@ -430,7 +436,7 @@ jobs:
|
|||
# Run Complement
|
||||
- run: |
|
||||
set -o pipefail &&
|
||||
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt
|
||||
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt -hide all
|
||||
shell: bash
|
||||
name: Run Complement Tests
|
||||
env:
|
||||
|
|
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
|
@ -57,8 +57,8 @@ jobs:
|
|||
id: docker_build_monolith
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache
|
||||
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache,mode=max
|
||||
context: .
|
||||
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
|
1
.github/workflows/helm.yml
vendored
1
.github/workflows/helm.yml
vendored
|
@ -38,3 +38,4 @@ jobs:
|
|||
with:
|
||||
config: helm/cr.yaml
|
||||
charts_dir: helm/
|
||||
mark_as_latest: false
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -75,3 +75,6 @@ docs/_site
|
|||
|
||||
media_store/
|
||||
build
|
||||
|
||||
# golang workspaces
|
||||
go.work*
|
81
CHANGES.md
81
CHANGES.md
|
@ -1,5 +1,86 @@
|
|||
# Changelog
|
||||
|
||||
## Dendrite 0.13.2 (2023-08-23)
|
||||
|
||||
### Fixes:
|
||||
|
||||
- Migrations in SQLite are now prepared on the correct context (transaction or database)
|
||||
- The `InputRoomEvent` stream now has a maximum age of 24h, which should help with slow start up times of NATS JetStream (contributed by [neilalexander](https://github.com/neilalexander))
|
||||
- Event size checks are more in line with Synapse
|
||||
- Requests to `/messages` have been optimized, possibly reducing database round trips
|
||||
- Re-add the revision of Dendrite when building from source (Note: This only works if git is installed)
|
||||
- Getting local members to notify has been optimized, which should significantly reduce memory allocation and cache usage
|
||||
- When getting queried about user profiles, we now return HTTP404 if the user/profiles does not exist
|
||||
- Background federated joins should now be fixed and not timeout after a short time
|
||||
- Database connections are now correctly re-used
|
||||
- Restored the old behavior of the `/purgeRoom` admin endpoint (does not evacuate the room before purging)
|
||||
- Don't expose information about the system when trying to download files that don't exist
|
||||
|
||||
### Features
|
||||
|
||||
- Further improvements and fixes for [MSC4014: Pseudonymous Identities](https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/pseudo-ids/proposals/4014-pseudonymous-identities.md)
|
||||
- Lookup correct prev events in the sync API
|
||||
- Populate `prev_sender` correctly in the sync API
|
||||
- Event federation should work better
|
||||
- Added new `dendrite_up` Prometheus metric, containing the version of Dendrite
|
||||
- Space summaries ([MSC2946](https://github.com/matrix-org/matrix-spec-proposals/pull/2946)) have been moved from MSC to being natively supported
|
||||
- For easier issue investigation, logs for application services now contain the application service ID (contributed by [maxberger](https://github.com/maxberger))
|
||||
- The default room version to use when creating rooms can now be configured using `room_server.default_room_version`
|
||||
|
||||
## 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)
|
||||
|
||||
### Features
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# base installs required dependencies and runs go mod download to cache dependencies
|
||||
#
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.20-alpine AS base
|
||||
RUN apk --update --no-cache add bash build-base curl
|
||||
RUN apk --update --no-cache add bash build-base curl git
|
||||
|
||||
#
|
||||
# build creates all needed binaries
|
||||
|
|
10
README.md
10
README.md
|
@ -13,7 +13,7 @@ It intends to provide an **efficient**, **reliable** and **scalable** alternativ
|
|||
|
||||
Dendrite is **beta** software, which means:
|
||||
|
||||
- Dendrite is 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 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 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.
|
||||
|
||||
|
@ -47,7 +47,7 @@ For a usable federating Dendrite deployment, you will also need:
|
|||
Also recommended are:
|
||||
|
||||
- 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.
|
||||
|
||||
|
@ -60,7 +60,7 @@ The following instructions are enough to get Dendrite started as a non-federatin
|
|||
```bash
|
||||
$ git clone https://github.com/matrix-org/dendrite
|
||||
$ cd dendrite
|
||||
$ ./build.sh
|
||||
$ go build -o bin/ ./cmd/...
|
||||
|
||||
# Generate a Matrix signing key for federation (required)
|
||||
$ ./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
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
@ -134,7 +134,6 @@ func TestAppserviceInternalAPI(t *testing.T) {
|
|||
}
|
||||
as.CreateHTTPClient(cfg.AppServiceAPI.DisableTLSValidation)
|
||||
cfg.AppServiceAPI.Derived.ApplicationServices = []config.ApplicationService{*as}
|
||||
|
||||
t.Cleanup(func() {
|
||||
ctx.ShutdownDendrite()
|
||||
ctx.WaitForShutdown()
|
||||
|
@ -144,6 +143,7 @@ func TestAppserviceInternalAPI(t *testing.T) {
|
|||
natsInstance := jetstream.NATSInstance{}
|
||||
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
|
||||
|
||||
|
@ -238,6 +238,7 @@ func TestAppserviceInternalAPI_UnixSocket_Simple(t *testing.T) {
|
|||
natsInstance := jetstream.NATSInstance{}
|
||||
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/nats-io/nats.go"
|
||||
|
||||
"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/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
|
@ -104,7 +105,7 @@ func (s *OutputRoomEventConsumer) onMessage(
|
|||
ctx context.Context, state *appserviceState, msgs []*nats.Msg,
|
||||
) bool {
|
||||
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 {
|
||||
// Only handle events we care about
|
||||
receivedType := api.OutputType(msg.Header.Get(jetstream.RoomEventType))
|
||||
|
@ -174,13 +175,15 @@ func (s *OutputRoomEventConsumer) onMessage(
|
|||
// endpoint. It will block for the backoff period if necessary.
|
||||
func (s *OutputRoomEventConsumer) sendEvents(
|
||||
ctx context.Context, state *appserviceState,
|
||||
events []*gomatrixserverlib.HeaderedEvent,
|
||||
events []*types.HeaderedEvent,
|
||||
txnID string,
|
||||
) error {
|
||||
// Create the transaction body.
|
||||
transaction, err := json.Marshal(
|
||||
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 {
|
||||
|
@ -189,7 +192,7 @@ func (s *OutputRoomEventConsumer) sendEvents(
|
|||
|
||||
// If txnID is not defined, generate one from the events.
|
||||
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.
|
||||
|
@ -231,11 +234,21 @@ func (s *appserviceState) backoffAndPause(err error) error {
|
|||
// 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
|
||||
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 {
|
||||
case appservice.URL == "":
|
||||
return false
|
||||
case appservice.IsInterestedInUserID(event.Sender()):
|
||||
case appservice.IsInterestedInUserID(user):
|
||||
return true
|
||||
case appservice.IsInterestedInRoomID(event.RoomID()):
|
||||
return true
|
||||
|
@ -269,7 +282,7 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
|
|||
|
||||
// appserviceJoinedAtEvent returns a boolean depending on whether a given
|
||||
// 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
|
||||
// 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
|
||||
|
|
|
@ -217,7 +217,7 @@ func (a *AppServiceQueryAPI) Locations(
|
|||
}
|
||||
|
||||
if err := requestDo[[]api.ASLocationResponse](as.HTTPClient, url+"?"+params.Encode(), &asLocations); err != nil {
|
||||
log.WithError(err).Error("unable to get 'locations' from application service")
|
||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'locations' from application service")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -252,7 +252,7 @@ func (a *AppServiceQueryAPI) User(
|
|||
}
|
||||
|
||||
if err := requestDo[[]api.ASUserResponse](as.HTTPClient, url+"?"+params.Encode(), &asUsers); err != nil {
|
||||
log.WithError(err).Error("unable to get 'user' from application service")
|
||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'user' from application service")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,7 @@ func (a *AppServiceQueryAPI) Protocols(
|
|||
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||
var proto api.ASProtocolResponse
|
||||
if err := requestDo[api.ASProtocolResponse](as.HTTPClient, as.RequestUrl()+api.ASProtocolPath+req.Protocol, &proto); err != nil {
|
||||
log.WithError(err).Error("unable to get 'protocol' from application service")
|
||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'protocol' from application service")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -320,7 +320,7 @@ func (a *AppServiceQueryAPI) Protocols(
|
|||
for _, p := range as.Protocols {
|
||||
var proto api.ASProtocolResponse
|
||||
if err := requestDo[api.ASProtocolResponse](as.HTTPClient, as.RequestUrl()+api.ASProtocolPath+p, &proto); err != nil {
|
||||
log.WithError(err).Error("unable to get 'protocol' from application service")
|
||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'protocol' from application service")
|
||||
continue
|
||||
}
|
||||
existing, ok := response[p]
|
||||
|
|
51
build.cmd
51
build.cmd
|
@ -1,51 +0,0 @@
|
|||
@echo off
|
||||
|
||||
:ENTRY_POINT
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
REM script base dir
|
||||
set SCRIPTDIR=%~dp0
|
||||
set PROJDIR=%SCRIPTDIR:~0,-1%
|
||||
|
||||
REM Put installed packages into ./bin
|
||||
set GOBIN=%PROJDIR%\bin
|
||||
|
||||
set FLAGS=
|
||||
|
||||
REM Check if sources are under Git control
|
||||
if not exist ".git" goto :CHECK_BIN
|
||||
|
||||
REM set BUILD=`git rev-parse --short HEAD \\ ""`
|
||||
FOR /F "tokens=*" %%X IN ('git rev-parse --short HEAD') DO (
|
||||
set BUILD=%%X
|
||||
)
|
||||
|
||||
REM set BRANCH=`(git symbolic-ref --short HEAD \ tr -d \/ ) \\ ""`
|
||||
FOR /F "tokens=*" %%X IN ('git symbolic-ref --short HEAD') DO (
|
||||
set BRANCHRAW=%%X
|
||||
set BRANCH=!BRANCHRAW:/=!
|
||||
)
|
||||
if "%BRANCH%" == "main" set BRANCH=
|
||||
|
||||
set FLAGS=-X github.com/matrix-org/dendrite/internal.branch=%BRANCH% -X github.com/matrix-org/dendrite/internal.build=%BUILD%
|
||||
|
||||
:CHECK_BIN
|
||||
if exist "bin" goto :ALL_SET
|
||||
mkdir "bin"
|
||||
|
||||
:ALL_SET
|
||||
set CGO_ENABLED=1
|
||||
for /D %%P in (cmd\*) do (
|
||||
go build -trimpath -ldflags "%FLAGS%" -v -o ".\bin" ".\%%P"
|
||||
)
|
||||
|
||||
set CGO_ENABLED=0
|
||||
set GOOS=js
|
||||
set GOARCH=wasm
|
||||
go build -trimpath -ldflags "%FLAGS%" -o bin\main.wasm .\cmd\dendritejs-pinecone
|
||||
|
||||
goto :DONE
|
||||
|
||||
:DONE
|
||||
echo Done
|
||||
endlocal
|
24
build.sh
24
build.sh
|
@ -1,24 +0,0 @@
|
|||
#!/bin/sh -eu
|
||||
|
||||
# Put installed packages into ./bin
|
||||
export GOBIN=$PWD/`dirname $0`/bin
|
||||
|
||||
if [ -d ".git" ]
|
||||
then
|
||||
export BUILD=`git rev-parse --short HEAD || ""`
|
||||
export BRANCH=`(git symbolic-ref --short HEAD | tr -d \/ ) || ""`
|
||||
if [ "$BRANCH" = main ]
|
||||
then
|
||||
export BRANCH=""
|
||||
fi
|
||||
|
||||
export FLAGS="-X github.com/matrix-org/dendrite/internal.branch=$BRANCH -X github.com/matrix-org/dendrite/internal.build=$BUILD"
|
||||
else
|
||||
export FLAGS=""
|
||||
fi
|
||||
|
||||
mkdir -p bin
|
||||
|
||||
CGO_ENABLED=1 go build -trimpath -ldflags "$FLAGS" -v -o "bin/" ./cmd/...
|
||||
|
||||
# CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs-pinecone
|
|
@ -1,4 +1,4 @@
|
|||
FROM docker.io/golang:1.19-alpine AS base
|
||||
FROM docker.io/golang:1.21-alpine AS base
|
||||
|
||||
#
|
||||
# Needs to be separate from the main Dockerfile for OpenShift,
|
||||
|
|
|
@ -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
|
||||
|
||||
## Dockerfiles
|
||||
## Dockerfile
|
||||
|
||||
The `Dockerfile` is a multistage file which can build all four Dendrite
|
||||
images depending on the supplied `--target`. From the root of the Dendrite
|
||||
The `Dockerfile` is a multistage file which can build Dendrite. From the root of the Dendrite
|
||||
repository, run:
|
||||
|
||||
```
|
||||
docker build . --target monolith -t matrixdotorg/dendrite-monolith
|
||||
docker build . --target demo-pinecone -t matrixdotorg/dendrite-demo-pinecone
|
||||
docker build . --target demo-yggdrasil -t matrixdotorg/dendrite-demo-yggdrasil
|
||||
docker build . -t matrixdotorg/dendrite-monolith
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -55,7 +52,7 @@ Create your config based on the [`dendrite-sample.yaml`](https://github.com/matr
|
|||
Then start the deployment:
|
||||
|
||||
```
|
||||
docker-compose -f docker-compose.monolith.yml up
|
||||
docker-compose -f docker-compose.yml up
|
||||
```
|
||||
|
||||
## Building the images
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
version: "3.4"
|
||||
services:
|
||||
postgres:
|
||||
hostname: postgres
|
||||
image: postgres:14
|
||||
restart: always
|
||||
volumes:
|
||||
- ./postgres/create_db.sh:/docker-entrypoint-initdb.d/20-create_db.sh
|
||||
# To persist your PostgreSQL databases outside of the Docker image,
|
||||
# to prevent data loss, modify the following ./path_to path:
|
||||
- ./path_to/postgresql:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: itsasecret
|
||||
POSTGRES_USER: dendrite
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U dendrite"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
|
||||
monolith:
|
||||
hostname: monolith
|
||||
image: matrixdotorg/dendrite-monolith:latest
|
||||
command: [
|
||||
"--tls-cert=server.crt",
|
||||
"--tls-key=server.key"
|
||||
]
|
||||
ports:
|
||||
- 8008:8008
|
||||
- 8448:8448
|
||||
volumes:
|
||||
- ./config:/etc/dendrite
|
||||
- ./media:/var/dendrite/media
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- internal
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
internal:
|
||||
attachable: true
|
52
build/docker/docker-compose.yml
Normal file
52
build/docker/docker-compose.yml
Normal file
|
@ -0,0 +1,52 @@
|
|||
version: "3.4"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
hostname: postgres
|
||||
image: postgres:15-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
# This will create a docker volume to persist the database files in.
|
||||
# If you prefer those files to be outside of docker, you'll need to change this.
|
||||
- dendrite_postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: itsasecret
|
||||
POSTGRES_USER: dendrite
|
||||
POSTGRES_DATABASE: dendrite
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U dendrite"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
|
||||
monolith:
|
||||
hostname: monolith
|
||||
image: matrixdotorg/dendrite-monolith:latest
|
||||
ports:
|
||||
- 8008:8008
|
||||
- 8448:8448
|
||||
volumes:
|
||||
- ./config:/etc/dendrite
|
||||
# The following volumes use docker volumes, change this
|
||||
# if you prefer to have those files outside of docker.
|
||||
- dendrite_media:/var/dendrite/media
|
||||
- dendrite_jetstream:/var/dendrite/jetstream
|
||||
- dendrite_search_index:/var/dendrite/searchindex
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- internal
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
internal:
|
||||
attachable: true
|
||||
|
||||
volumes:
|
||||
dendrite_postgres_data:
|
||||
dendrite_media:
|
||||
dendrite_jetstream:
|
||||
dendrite_search_index:
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
for db in userapi_accounts mediaapi syncapi roomserver keyserver federationapi appservice mscs; do
|
||||
createdb -U dendrite -O dendrite dendrite_$db
|
||||
done
|
|
@ -22,7 +22,10 @@ import (
|
|||
)
|
||||
|
||||
func TestMonolithStarts(t *testing.T) {
|
||||
monolith := DendriteMonolith{}
|
||||
monolith := DendriteMonolith{
|
||||
StorageDirectory: t.TempDir(),
|
||||
CacheDirectory: t.TempDir(),
|
||||
}
|
||||
monolith.Start()
|
||||
monolith.PublicKey()
|
||||
monolith.Stop()
|
||||
|
@ -60,7 +63,10 @@ func TestMonolithSetRelayServers(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
monolith := DendriteMonolith{}
|
||||
monolith := DendriteMonolith{
|
||||
StorageDirectory: t.TempDir(),
|
||||
CacheDirectory: t.TempDir(),
|
||||
}
|
||||
monolith.Start()
|
||||
|
||||
inputRelays := tc.relays
|
||||
|
|
|
@ -2,6 +2,7 @@ package clientapi
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
|
@ -23,12 +24,654 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
capi "github.com/matrix-org/dendrite/clientapi/api"
|
||||
"github.com/matrix-org/dendrite/test"
|
||||
"github.com/matrix-org/dendrite/test/testrig"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
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)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
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) {
|
||||
aliceAdmin := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
||||
bob := test.NewUser(t, test.WithAccountType(uapi.AccountTypeUser))
|
||||
|
@ -48,6 +691,7 @@ func TestAdminResetPassword(t *testing.T) {
|
|||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
// Needed for changing the password/login
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
// We mostly need the userAPI for this test, so nil for other APIs/caches etc.
|
||||
|
@ -133,17 +777,22 @@ func TestPurgeRoom(t *testing.T) {
|
|||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
natsInstance := jetstream.NATSInstance{}
|
||||
defer close()
|
||||
defer func() {
|
||||
// give components the time to process purge requests
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
close()
|
||||
}()
|
||||
|
||||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// this starts the JetStream consumers
|
||||
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
||||
rsAPI.SetFederationAPI(fsAPI, nil)
|
||||
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics)
|
||||
federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
|
||||
// Create the room
|
||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
||||
|
@ -209,12 +858,13 @@ func TestAdminEvacuateRoom(t *testing.T) {
|
|||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// this starts the JetStream consumers
|
||||
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
||||
rsAPI.SetFederationAPI(fsAPI, nil)
|
||||
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// Create the room
|
||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
|
||||
t.Fatalf("failed to send events: %v", err)
|
||||
|
@ -309,12 +959,13 @@ func TestAdminEvacuateUser(t *testing.T) {
|
|||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// this starts the JetStream consumers
|
||||
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, basepkg.CreateFederationClient(cfg, nil), rsAPI, caches, nil, true)
|
||||
rsAPI.SetFederationAPI(fsAPI, nil)
|
||||
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// Create the room
|
||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
|
||||
t.Fatalf("failed to send events: %v", err)
|
||||
|
@ -403,6 +1054,7 @@ func TestAdminMarkAsStale(t *testing.T) {
|
|||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
||||
|
|
|
@ -21,3 +21,11 @@ type ExtraPublicRoomsProvider interface {
|
|||
// Rooms returns the extra rooms. This is called on-demand by clients, so cache appropriately.
|
||||
Rooms() []fclient.PublicRoom
|
||||
}
|
||||
|
||||
type RegistrationToken struct {
|
||||
Token *string `json:"token"`
|
||||
UsesAllowed *int32 `json:"uses_allowed"`
|
||||
Pending *int32 `json:"pending"`
|
||||
Completed *int32 `json:"completed"`
|
||||
ExpiryTime *int64 `json:"expiry_time"`
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -58,7 +58,7 @@ func VerifyUserFromRequest(
|
|||
if err != nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.MissingToken(err.Error()),
|
||||
JSON: spec.MissingToken(err.Error()),
|
||||
}
|
||||
}
|
||||
var res api.QueryAccessTokenResponse
|
||||
|
@ -68,21 +68,23 @@ func VerifyUserFromRequest(
|
|||
}, &res)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryAccessToken failed")
|
||||
jsonErr := jsonerror.InternalServerError()
|
||||
return nil, &jsonErr
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if res.Err != "" {
|
||||
if strings.HasPrefix(strings.ToLower(res.Err), "forbidden:") { // TODO: use actual error and no string comparison
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(res.Err),
|
||||
JSON: spec.Forbidden(res.Err),
|
||||
}
|
||||
}
|
||||
}
|
||||
if res.Device == nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.UnknownToken("Unknown token"),
|
||||
JSON: spec.UnknownToken("Unknown token"),
|
||||
}
|
||||
}
|
||||
return res.Device, nil
|
||||
|
|
|
@ -21,9 +21,9 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -37,7 +37,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.U
|
|||
if err != nil {
|
||||
err := &util.JSONResponse{
|
||||
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
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.U
|
|||
if err := json.Unmarshal(reqBytes, &header); err != nil {
|
||||
err := &util.JSONResponse{
|
||||
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
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.U
|
|||
default:
|
||||
err := util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue("unhandled login type: " + header.Type),
|
||||
JSON: spec.InvalidParam("unhandled login type: " + header.Type),
|
||||
}
|
||||
return nil, nil, &err
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -107,13 +107,13 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
|||
Name string
|
||||
Body string
|
||||
|
||||
WantErrCode string
|
||||
WantErrCode spec.MatrixErrorCode
|
||||
}{
|
||||
{Name: "empty", WantErrCode: "M_BAD_JSON"},
|
||||
{Name: "empty", WantErrCode: spec.ErrorBadJSON},
|
||||
{
|
||||
Name: "badUnmarshal",
|
||||
Body: `badsyntaxJSON`,
|
||||
WantErrCode: "M_BAD_JSON",
|
||||
WantErrCode: spec.ErrorBadJSON,
|
||||
},
|
||||
{
|
||||
Name: "badPassword",
|
||||
|
@ -123,7 +123,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
|||
"password": "invalidpassword",
|
||||
"device_id": "adevice"
|
||||
}`,
|
||||
WantErrCode: "M_FORBIDDEN",
|
||||
WantErrCode: spec.ErrorForbidden,
|
||||
},
|
||||
{
|
||||
Name: "badToken",
|
||||
|
@ -132,7 +132,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
|||
"token": "invalidtoken",
|
||||
"device_id": "adevice"
|
||||
}`,
|
||||
WantErrCode: "M_FORBIDDEN",
|
||||
WantErrCode: spec.ErrorForbidden,
|
||||
},
|
||||
{
|
||||
Name: "badType",
|
||||
|
@ -140,7 +140,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
|||
"type": "m.login.invalid",
|
||||
"device_id": "adevice"
|
||||
}`,
|
||||
WantErrCode: "M_INVALID_ARGUMENT_VALUE",
|
||||
WantErrCode: spec.ErrorInvalidParam,
|
||||
},
|
||||
}
|
||||
for _, tst := range tsts {
|
||||
|
@ -157,7 +157,7 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
|||
if errRes == nil {
|
||||
cleanup(ctx, nil)
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -20,9 +20,9 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -48,13 +48,15 @@ func (t *LoginTypeToken) LoginFromJSON(ctx context.Context, reqBytes []byte) (*L
|
|||
var res uapi.QueryLoginTokenResponse
|
||||
if err := t.UserAPI.QueryLoginToken(ctx, &uapi.QueryLoginTokenRequest{Token: r.Token}, &res); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("UserAPI.QueryLoginToken failed")
|
||||
jsonErr := jsonerror.InternalServerError()
|
||||
return nil, nil, &jsonErr
|
||||
return nil, nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if res.Data == nil {
|
||||
return nil, nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("invalid login token"),
|
||||
JSON: spec.Forbidden("invalid login token"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -65,26 +65,26 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
|||
if username == "" {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.BadJSON("A username must be supplied."),
|
||||
JSON: spec.BadJSON("A username must be supplied."),
|
||||
}
|
||||
}
|
||||
if len(r.Password) == 0 {
|
||||
return nil, &util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.InvalidUsername(err.Error()),
|
||||
JSON: spec.InvalidUsername(err.Error()),
|
||||
}
|
||||
}
|
||||
if !t.Config.Matrix.IsLocalServerName(domain) {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.InvalidUsername("The server name is not known."),
|
||||
JSON: spec.InvalidUsername("The server name is not known."),
|
||||
}
|
||||
}
|
||||
// Squash username to all lowercase letters
|
||||
|
@ -97,7 +97,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
|||
if err != nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("Unable to fetch account by password."),
|
||||
JSON: spec.Unknown("Unable to fetch account by password."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
|||
if err != nil {
|
||||
return nil, &util.JSONResponse{
|
||||
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
|
||||
|
@ -120,7 +120,7 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
|||
if !res.Exists {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("The username or password was incorrect or the account does not exist."),
|
||||
JSON: spec.Forbidden("The username or password was incorrect or the account does not exist."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ import (
|
|||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
|
@ -178,8 +178,10 @@ func (u *UserInteractive) NewSession() *util.JSONResponse {
|
|||
sessionID, err := GenerateAccessToken()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("failed to generate session ID")
|
||||
res := jsonerror.InternalServerError()
|
||||
return &res
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
u.Lock()
|
||||
u.Sessions[sessionID] = []string{}
|
||||
|
@ -193,15 +195,19 @@ func (u *UserInteractive) ResponseWithChallenge(sessionID string, response inter
|
|||
mixedObjects := make(map[string]interface{})
|
||||
b, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
ise := jsonerror.InternalServerError()
|
||||
return &ise
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
_ = json.Unmarshal(b, &mixedObjects)
|
||||
challenge := u.challenge(sessionID)
|
||||
b, err = json.Marshal(challenge.JSON)
|
||||
if err != nil {
|
||||
ise := jsonerror.InternalServerError()
|
||||
return &ise
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
_ = json.Unmarshal(b, &mixedObjects)
|
||||
|
||||
|
@ -234,7 +240,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *
|
|||
if !ok {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Unknown auth.type: " + authType),
|
||||
JSON: spec.BadJSON("Unknown auth.type: " + authType),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +256,7 @@ func (u *UserInteractive) Verify(ctx context.Context, bodyBytes []byte, device *
|
|||
if !u.IsSingleStageFlow(authType) {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown("The auth.session is missing or unknown."),
|
||||
JSON: spec.Unknown("The auth.session is missing or unknown."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ func AddPublicRoutes(
|
|||
routers httputil.Routers,
|
||||
cfg *config.Dendrite,
|
||||
natsInstance *jetstream.NATSInstance,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
transactionsCache *transactions.Cache,
|
||||
|
|
|
@ -13,19 +13,13 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/roomserver"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
|
@ -37,6 +31,16 @@ import (
|
|||
"github.com/matrix-org/dendrite/test/testrig"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type userDevice struct {
|
||||
|
@ -116,6 +120,7 @@ func TestGetPutDevices(t *testing.T) {
|
|||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
||||
|
@ -164,6 +169,7 @@ func TestDeleteDevice(t *testing.T) {
|
|||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc.
|
||||
|
@ -268,6 +274,7 @@ func TestDeleteDevices(t *testing.T) {
|
|||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc.
|
||||
|
@ -909,20 +916,24 @@ func TestCapabilities(t *testing.T) {
|
|||
// construct the expected result
|
||||
versionsMap := map[gomatrixserverlib.RoomVersion]string{}
|
||||
for v, desc := range version.SupportedRoomVersions() {
|
||||
if desc.Stable {
|
||||
if desc.Stable() {
|
||||
versionsMap[v] = "stable"
|
||||
} else {
|
||||
versionsMap[v] = "unstable"
|
||||
}
|
||||
}
|
||||
|
||||
var tempRoomServerCfg config.RoomServer
|
||||
tempRoomServerCfg.Defaults(config.DefaultOpts{})
|
||||
defaultRoomVersion := tempRoomServerCfg.DefaultRoomVersion
|
||||
|
||||
expectedMap := map[string]interface{}{
|
||||
"capabilities": map[string]interface{}{
|
||||
"m.change_password": map[string]bool{
|
||||
"enabled": true,
|
||||
},
|
||||
"m.room_versions": map[string]interface{}{
|
||||
"default": version.DefaultRoomVersion(),
|
||||
"default": defaultRoomVersion,
|
||||
"available": versionsMap,
|
||||
},
|
||||
},
|
||||
|
@ -943,6 +954,7 @@ func TestCapabilities(t *testing.T) {
|
|||
|
||||
// Needed to create accounts
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||
|
@ -989,6 +1001,7 @@ func TestTurnserver(t *testing.T) {
|
|||
|
||||
// Needed to create accounts
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
//rsAPI.SetUserAPI(userAPI)
|
||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
||||
|
@ -1086,6 +1099,7 @@ func Test3PID(t *testing.T) {
|
|||
|
||||
// Needed to create accounts
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||
|
@ -1102,7 +1116,7 @@ func Test3PID(t *testing.T) {
|
|||
resp := threepid.GetValidatedResponse{}
|
||||
switch r.URL.Query().Get("client_secret") {
|
||||
case "fail":
|
||||
resp.ErrCode = "M_SESSION_NOT_VALIDATED"
|
||||
resp.ErrCode = string(spec.ErrorSessionNotValidated)
|
||||
case "fail2":
|
||||
resp.ErrCode = "some other error"
|
||||
case "fail3":
|
||||
|
@ -1261,6 +1275,7 @@ func TestPushRules(t *testing.T) {
|
|||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
||||
|
@ -1630,3 +1645,504 @@ func TestPushRules(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tests the `/keys` endpoints.
|
||||
// Note that this only tests the happy path.
|
||||
func TestKeys(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
|
||||
ctx := context.Background()
|
||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||
cfg.ClientAPI.RateLimiting.Enabled = false
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
natsInstance := jetstream.NATSInstance{}
|
||||
defer close()
|
||||
|
||||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||
|
||||
accessTokens := map[*test.User]userDevice{
|
||||
alice: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
|
||||
|
||||
// Start a TLSServer with our client mux
|
||||
srv := httptest.NewTLSServer(routers.Client)
|
||||
defer srv.Close()
|
||||
|
||||
cl, err := mautrix.NewClient(srv.URL, id.UserID(alice.ID), accessTokens[alice].accessToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Set the client so the self-signed certificate is trusted
|
||||
cl.Client = srv.Client()
|
||||
cl.DeviceID = id.DeviceID(accessTokens[alice].deviceID)
|
||||
|
||||
cs := crypto.NewMemoryStore(nil)
|
||||
oc := crypto.NewOlmMachine(cl, nil, cs, dummyStore{})
|
||||
if err = oc.Load(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// tests `/keys/upload`
|
||||
if err = oc.ShareKeys(ctx, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// tests `/keys/device_signing/upload`
|
||||
_, err = oc.GenerateAndUploadCrossSigningKeys(accessTokens[alice].password, "passphrase")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// tests `/keys/query`
|
||||
dev, err := oc.GetOrFetchDevice(ctx, id.UserID(alice.ID), id.DeviceID(accessTokens[alice].deviceID))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Validate that the keys returned from the server are what the client has stored
|
||||
oi := oc.OwnIdentity()
|
||||
if oi.SigningKey != dev.SigningKey {
|
||||
t.Fatalf("expected signing key '%s', got '%s'", oi.SigningKey, dev.SigningKey)
|
||||
}
|
||||
if oi.IdentityKey != dev.IdentityKey {
|
||||
t.Fatalf("expected identity '%s', got '%s'", oi.IdentityKey, dev.IdentityKey)
|
||||
}
|
||||
|
||||
// tests `/keys/signatures/upload`
|
||||
if err = oc.SignOwnMasterKey(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// tests `/keys/claim`
|
||||
otks := make(map[string]map[string]string)
|
||||
otks[alice.ID] = map[string]string{
|
||||
accessTokens[alice].deviceID: string(id.KeyAlgorithmSignedCurve25519),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(claimKeysRequest{OneTimeKeys: otks})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, srv.URL+"/_matrix/client/v3/keys/claim", bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !gjson.GetBytes(respBody, "one_time_keys."+alice.ID+"."+string(dev.DeviceID)).Exists() {
|
||||
t.Fatalf("expected one time keys for alice, but didn't find any: %s", string(respBody))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type claimKeysRequest struct {
|
||||
// The keys to be claimed. A map from user ID, to a map from device ID to algorithm name.
|
||||
OneTimeKeys map[string]map[string]string `json:"one_time_keys"`
|
||||
}
|
||||
|
||||
type dummyStore struct{}
|
||||
|
||||
func (d dummyStore) IsEncrypted(roomID id.RoomID) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d dummyStore) GetEncryptionEvent(roomID id.RoomID) *event.EncryptionEventContent {
|
||||
return &event.EncryptionEventContent{}
|
||||
}
|
||||
|
||||
func (d dummyStore) FindSharedRooms(userID id.UserID) []id.RoomID {
|
||||
return []id.RoomID{}
|
||||
}
|
||||
|
||||
func TestKeyBackup(t *testing.T) {
|
||||
alice := test.NewUser(t)
|
||||
|
||||
handleResponseCode := func(t *testing.T, rec *httptest.ResponseRecorder, expectedCode int) {
|
||||
t.Helper()
|
||||
if rec.Code != expectedCode {
|
||||
t.Fatalf("expected HTTP %d, but got %d: %s", expectedCode, rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
request func(t *testing.T) *http.Request
|
||||
validate func(t *testing.T, rec *httptest.ResponseRecorder)
|
||||
}{
|
||||
{
|
||||
name: "can not create backup with invalid JSON",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := strings.NewReader(`{"algorithm":"m.megolm_backup.v1"`) // missing closing braces
|
||||
return httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/room_keys/version", reqBody)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusBadRequest)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not create backup with missing auth_data", // as this would result in MarshalJSON errors when querying again
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := strings.NewReader(`{"algorithm":"m.megolm_backup.v1"}`)
|
||||
return httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/room_keys/version", reqBody)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusBadRequest)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can create backup",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := strings.NewReader(`{"algorithm":"m.megolm_backup.v1","auth_data":{"data":"random"}}`)
|
||||
return httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/room_keys/version", reqBody)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
wantVersion := "1"
|
||||
if gotVersion := gjson.GetBytes(rec.Body.Bytes(), "version").Str; gotVersion != wantVersion {
|
||||
t.Fatalf("expected version '%s', got '%s'", wantVersion, gotVersion)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not query backup for invalid version",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/room_keys/version/1337", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not query backup for invalid version string",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/room_keys/version/notanumber", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can query backup",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/room_keys/version", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
wantVersion := "1"
|
||||
if gotVersion := gjson.GetBytes(rec.Body.Bytes(), "version").Str; gotVersion != wantVersion {
|
||||
t.Fatalf("expected version '%s', got '%s'", wantVersion, gotVersion)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can query backup without returning rooms",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys", test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
if gotRooms := gjson.GetBytes(rec.Body.Bytes(), "rooms").Map(); len(gotRooms) > 0 {
|
||||
t.Fatalf("expected no rooms in version, but got %#v", gotRooms)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can query backup for invalid room",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!abc:test", test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
if gotSessions := gjson.GetBytes(rec.Body.Bytes(), "sessions").Map(); len(gotSessions) > 0 {
|
||||
t.Fatalf("expected no sessions in version, but got %#v", gotSessions)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not query backup for invalid session",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!abc:test/doesnotexist", test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not update backup with missing version",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys")
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusBadRequest)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not update backup with invalid data",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, "")
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys", reqBody, test.WithQueryParams(map[string]string{
|
||||
"version": "0",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusBadRequest)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not update backup with wrong version",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, map[string]interface{}{
|
||||
"rooms": map[string]interface{}{
|
||||
"!testroom:test": map[string]interface{}{
|
||||
"sessions": map[string]uapi.KeyBackupSession{},
|
||||
},
|
||||
},
|
||||
})
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys", reqBody, test.WithQueryParams(map[string]string{
|
||||
"version": "5",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusForbidden)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can update backup with correct version",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, map[string]interface{}{
|
||||
"rooms": map[string]interface{}{
|
||||
"!testroom:test": map[string]interface{}{
|
||||
"sessions": map[string]uapi.KeyBackupSession{
|
||||
"dummySession": {
|
||||
FirstMessageIndex: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys", reqBody, test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can update backup with correct version for specific room",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, map[string]interface{}{
|
||||
"sessions": map[string]uapi.KeyBackupSession{
|
||||
"dummySession": {
|
||||
FirstMessageIndex: 1,
|
||||
IsVerified: true,
|
||||
SessionData: json.RawMessage("{}"),
|
||||
},
|
||||
},
|
||||
})
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys/!testroom:test", reqBody, test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
t.Logf("%#v", rec.Body.String())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can update backup with correct version for specific room and session",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, uapi.KeyBackupSession{
|
||||
FirstMessageIndex: 1,
|
||||
SessionData: json.RawMessage("{}"),
|
||||
IsVerified: true,
|
||||
ForwardedCount: 0,
|
||||
})
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/keys/!testroom:test/dummySession", reqBody, test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can update backup by version",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, uapi.KeyBackupSession{
|
||||
FirstMessageIndex: 1,
|
||||
SessionData: json.RawMessage("{}"),
|
||||
IsVerified: true,
|
||||
ForwardedCount: 0,
|
||||
})
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/version/1", reqBody, test.WithQueryParams(map[string]string{"version": "1"}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
t.Logf("%#v", rec.Body.String())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not update backup by version for invalid version",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
reqBody := test.WithJSONBody(t, uapi.KeyBackupSession{
|
||||
FirstMessageIndex: 1,
|
||||
SessionData: json.RawMessage("{}"),
|
||||
IsVerified: true,
|
||||
ForwardedCount: 0,
|
||||
})
|
||||
req := test.NewRequest(t, http.MethodPut, "/_matrix/client/v3/room_keys/version/2", reqBody, test.WithQueryParams(map[string]string{"version": "1"}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can query backup sessions",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys", test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
if gotRooms := gjson.GetBytes(rec.Body.Bytes(), "rooms").Map(); len(gotRooms) != 1 {
|
||||
t.Fatalf("expected one room in response, but got %#v", rec.Body.String())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can query backup sessions by room",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!testroom:test", test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
if gotRooms := gjson.GetBytes(rec.Body.Bytes(), "sessions").Map(); len(gotRooms) != 1 {
|
||||
t.Fatalf("expected one session in response, but got %#v", rec.Body.String())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can query backup sessions by room and sessionID",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/room_keys/keys/!testroom:test/dummySession", test.WithQueryParams(map[string]string{
|
||||
"version": "1",
|
||||
}))
|
||||
return req
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
if !gjson.GetBytes(rec.Body.Bytes(), "is_verified").Bool() {
|
||||
t.Fatalf("expected session to be verified, but wasn't: %#v", rec.Body.String())
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not delete invalid version backup",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/2", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can delete version backup",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/1", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deleting the same backup version twice doesn't error",
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/1", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deleting an empty version doesn't work", // make sure we can't delete an empty backup version. Handled at the router level
|
||||
request: func(t *testing.T) *http.Request {
|
||||
return httptest.NewRequest(http.MethodDelete, "/_matrix/client/v3/room_keys/version/", nil)
|
||||
},
|
||||
validate: func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
handleResponseCode(t, rec, http.StatusNotFound)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
||||
cfg.ClientAPI.RateLimiting.Enabled = false
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
natsInstance := jetstream.NATSInstance{}
|
||||
defer close()
|
||||
|
||||
routers := httputil.NewRouters()
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||
|
||||
accessTokens := map[*test.User]userDevice{
|
||||
alice: {},
|
||||
}
|
||||
createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := tc.request(t)
|
||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
||||
routers.Client.ServeHTTP(rec, req)
|
||||
tc.validate(t, rec)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"net/http"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -32,8 +32,10 @@ func UnmarshalJSONRequest(req *http.Request, iface interface{}) *util.JSONRespon
|
|||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("io.ReadAll failed")
|
||||
resp := jsonerror.InternalServerError()
|
||||
return &resp
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return UnmarshalJSON(body, iface)
|
||||
|
@ -43,7 +45,7 @@ func UnmarshalJSON(body []byte, iface interface{}) *util.JSONResponse {
|
|||
if !utf8.Valid(body) {
|
||||
return &util.JSONResponse{
|
||||
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.
|
||||
return &util.JSONResponse{
|
||||
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
|
||||
|
|
|
@ -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.",
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright 2017 Vector Creations Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package jsonerror
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLimitExceeded(t *testing.T) {
|
||||
e := LimitExceeded("too fast", 5000)
|
||||
jsonBytes, err := json.Marshal(&e)
|
||||
if err != nil {
|
||||
t.Fatalf("TestLimitExceeded: Failed to marshal LimitExceeded error. %s", err.Error())
|
||||
}
|
||||
want := `{"errcode":"M_LIMIT_EXCEEDED","error":"too fast","retry_after_ms":5000}`
|
||||
if string(jsonBytes) != want {
|
||||
t.Errorf("TestLimitExceeded: want %s, got %s", want, string(jsonBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbidden(t *testing.T) {
|
||||
e := Forbidden("you shall not pass")
|
||||
jsonBytes, err := json.Marshal(&e)
|
||||
if err != nil {
|
||||
t.Fatalf("TestForbidden: Failed to marshal Forbidden error. %s", err.Error())
|
||||
}
|
||||
want := `{"errcode":"M_FORBIDDEN","error":"you shall not pass"}`
|
||||
if string(jsonBytes) != want {
|
||||
t.Errorf("TestForbidden: want %s, got %s", want, string(jsonBytes))
|
||||
}
|
||||
}
|
|
@ -21,11 +21,11 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/internal/eventutil"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
@ -38,7 +38,7 @@ func GetAccountData(
|
|||
if userID != device.UserID {
|
||||
return util.JSONResponse{
|
||||
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{
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("Content not JSON"),
|
||||
JSON: spec.NotJSON("Content not JSON"),
|
||||
}
|
||||
}
|
||||
|
||||
if dataType == "m.fully_read" || dataType == "m.push_rules" {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
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) {
|
||||
return util.JSONResponse{
|
||||
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,
|
||||
syncProducer *producers.SyncAPIProducer, device *api.Device, roomID string,
|
||||
) 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
|
||||
resErr := checkMemberInRoom(req.Context(), rsAPI, device.UserID, roomID)
|
||||
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
@ -157,7 +168,10 @@ func SaveReadMarker(
|
|||
if r.FullyRead != "" {
|
||||
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
|
||||
if err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
dataReq := api.InputAccountDataRequest{
|
||||
|
|
|
@ -3,113 +3,328 @@ package routing
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
"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"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"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 {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
res := &roomserverAPI.PerformAdminEvacuateRoomResponse{}
|
||||
if err := rsAPI.PerformAdminEvacuateRoom(
|
||||
req.Context(),
|
||||
&roomserverAPI.PerformAdminEvacuateRoomRequest{
|
||||
RoomID: vars["roomID"],
|
||||
},
|
||||
res,
|
||||
); err != nil {
|
||||
|
||||
affected, err := rsAPI.PerformAdminEvacuateRoom(req.Context(), vars["roomID"])
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
case eventutil.ErrRoomNoExists:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: spec.NotFound(err.Error()),
|
||||
}
|
||||
default:
|
||||
logrus.WithError(err).WithField("roomID", vars["roomID"]).Error("Failed to evacuate room")
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
if err := res.Error; err != nil {
|
||||
return err.JSONResponse()
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: map[string]interface{}{
|
||||
"affected": res.Affected,
|
||||
"affected": affected,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func AdminEvacuateUser(req *http.Request, cfg *config.ClientAPI, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||
func AdminEvacuateUser(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
userID := vars["userID"]
|
||||
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
affected, err := rsAPI.PerformAdminEvacuateUser(req.Context(), vars["userID"])
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("userID", vars["userID"]).Error("Failed to evacuate user")
|
||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("User ID must belong to this server."),
|
||||
}
|
||||
}
|
||||
res := &roomserverAPI.PerformAdminEvacuateUserResponse{}
|
||||
if err := rsAPI.PerformAdminEvacuateUser(
|
||||
req.Context(),
|
||||
&roomserverAPI.PerformAdminEvacuateUserRequest{
|
||||
UserID: userID,
|
||||
},
|
||||
res,
|
||||
); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if err := res.Error; err != nil {
|
||||
return err.JSONResponse()
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: map[string]interface{}{
|
||||
"affected": res.Affected,
|
||||
"affected": affected,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func AdminPurgeRoom(req *http.Request, cfg *config.ClientAPI, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||
func AdminPurgeRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
roomID := vars["roomID"]
|
||||
|
||||
res := &roomserverAPI.PerformAdminPurgeRoomResponse{}
|
||||
if err := rsAPI.PerformAdminPurgeRoom(
|
||||
context.Background(),
|
||||
&roomserverAPI.PerformAdminPurgeRoomRequest{
|
||||
RoomID: roomID,
|
||||
},
|
||||
res,
|
||||
); err != nil {
|
||||
if err = rsAPI.PerformAdminPurgeRoom(context.Background(), vars["roomID"]); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
if err := res.Error; err != nil {
|
||||
return err.JSONResponse()
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: res,
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +332,7 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
|||
if req.Body == nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown("Missing request body"),
|
||||
JSON: spec.Unknown("Missing request body"),
|
||||
}
|
||||
}
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
|
@ -130,7 +345,7 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
||||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
}
|
||||
accAvailableResp := &api.QueryAccountAvailabilityResponse{}
|
||||
|
@ -140,28 +355,29 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
|||
}, accAvailableResp); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalAPIError(req.Context(), err),
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if accAvailableResp.Available {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.Unknown("User does not exist"),
|
||||
JSON: spec.Unknown("User does not exist"),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
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 == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("Expecting non-empty password."),
|
||||
JSON: spec.MissingParam("Expecting non-empty password."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,13 +389,13 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.De
|
|||
Localpart: localpart,
|
||||
ServerName: serverName,
|
||||
Password: request.Password,
|
||||
LogoutDevices: true,
|
||||
LogoutDevices: request.LogoutDevices,
|
||||
}
|
||||
updateRes := &api.PerformPasswordUpdateResponse{}
|
||||
if err := userAPI.PerformPasswordUpdate(req.Context(), updateReq, updateRes); err != nil {
|
||||
return util.JSONResponse{
|
||||
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{
|
||||
|
@ -196,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)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("failed to publish nats message")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
|
@ -218,7 +437,7 @@ func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.Clien
|
|||
if cfg.Matrix.IsLocalServerName(domain) {
|
||||
return util.JSONResponse{
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +448,7 @@ func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.Clien
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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{
|
||||
|
@ -238,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))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
|
@ -247,33 +466,32 @@ func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *api.De
|
|||
if !ok {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("Expecting room ID."),
|
||||
JSON: spec.MissingParam("Expecting room ID."),
|
||||
}
|
||||
}
|
||||
serverName, ok := vars["serverName"]
|
||||
if !ok {
|
||||
return util.JSONResponse{
|
||||
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(),
|
||||
&roomserverAPI.PerformAdminDownloadStateRequest{
|
||||
UserID: device.UserID,
|
||||
RoomID: roomID,
|
||||
ServerName: spec.ServerName(serverName),
|
||||
},
|
||||
res,
|
||||
); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if err := res.Error; err != nil {
|
||||
return err.JSONResponse()
|
||||
if err = rsAPI.PerformAdminDownloadState(req.Context(), roomID, device.UserID, spec.ServerName(serverName)); err != nil {
|
||||
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: spec.NotFound(err.Error()),
|
||||
}
|
||||
}
|
||||
logrus.WithError(err).WithFields(logrus.Fields{
|
||||
"userID": device.UserID,
|
||||
"serverName": serverName,
|
||||
"roomID": roomID,
|
||||
}).Error("failed to download state")
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: map[string]interface{}{},
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
@ -51,7 +51,7 @@ func GetAdminWhois(
|
|||
if !allowed {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
|
|
|
@ -15,15 +15,14 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/roomserver/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"
|
||||
)
|
||||
|
||||
|
@ -48,26 +47,37 @@ func GetAliases(
|
|||
visibility := gomatrixserverlib.HistoryVisibilityInvited
|
||||
if historyVisEvent, ok := stateRes.StateEvents[stateTuple]; ok {
|
||||
var err error
|
||||
visibility, err = historyVisEvent.HistoryVisibility()
|
||||
if err != nil {
|
||||
var content gomatrixserverlib.HistoryVisibilityContent
|
||||
if err = json.Unmarshal(historyVisEvent.Content(), &content); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("historyVisEvent.HistoryVisibility failed")
|
||||
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"),
|
||||
}
|
||||
}
|
||||
queryReq := api.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: device.UserID,
|
||||
UserID: *deviceUserID,
|
||||
}
|
||||
var queryRes api.QueryMembershipForUserResponse
|
||||
if err := rsAPI.QueryMembershipForUser(req.Context(), &queryReq, &queryRes); err != nil {
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
@ -24,10 +25,10 @@ import (
|
|||
|
||||
// GetCapabilities returns information about the server's supported feature set
|
||||
// and other relevant capabilities to an authenticated user.
|
||||
func GetCapabilities() util.JSONResponse {
|
||||
func GetCapabilities(rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||
versionsMap := map[gomatrixserverlib.RoomVersion]string{}
|
||||
for v, desc := range version.SupportedRoomVersions() {
|
||||
if desc.Stable {
|
||||
if desc.Stable() {
|
||||
versionsMap[v] = "stable"
|
||||
} else {
|
||||
versionsMap[v] = "unstable"
|
||||
|
@ -40,7 +41,7 @@ func GetCapabilities() util.JSONResponse {
|
|||
"enabled": true,
|
||||
},
|
||||
"m.room_versions": map[string]interface{}{
|
||||
"default": version.DefaultRoomVersion(),
|
||||
"default": rsAPI.DefaultRoomVersion(),
|
||||
"available": versionsMap,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,12 +26,9 @@ import (
|
|||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
roomserverVersion "github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"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/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
@ -40,33 +37,19 @@ import (
|
|||
|
||||
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type createRoomRequest struct {
|
||||
Invite []string `json:"invite"`
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
Topic string `json:"topic"`
|
||||
Preset string `json:"preset"`
|
||||
CreationContent json.RawMessage `json:"creation_content"`
|
||||
InitialState []fledglingEvent `json:"initial_state"`
|
||||
RoomAliasName string `json:"room_alias_name"`
|
||||
GuestCanJoin bool `json:"guest_can_join"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
||||
IsDirect bool `json:"is_direct"`
|
||||
Invite []string `json:"invite"`
|
||||
Name string `json:"name"`
|
||||
Visibility string `json:"visibility"`
|
||||
Topic string `json:"topic"`
|
||||
Preset string `json:"preset"`
|
||||
CreationContent json.RawMessage `json:"creation_content"`
|
||||
InitialState []gomatrixserverlib.FledglingEvent `json:"initial_state"`
|
||||
RoomAliasName string `json:"room_alias_name"`
|
||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
||||
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 {
|
||||
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
|
||||
|
@ -74,28 +57,23 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
|||
if strings.ContainsAny(r.RoomAliasName, whitespace+":") {
|
||||
return &util.JSONResponse{
|
||||
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 {
|
||||
// TODO: We should put user ID parsing code into gomatrixserverlib and use that instead
|
||||
// (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 {
|
||||
if _, err := spec.NewUserID(userID, true); err != nil {
|
||||
return &util.JSONResponse{
|
||||
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 {
|
||||
case presetPrivateChat, presetTrustedPrivateChat, presetPublicChat, "":
|
||||
case spec.PresetPrivateChat, spec.PresetTrustedPrivateChat, spec.PresetPublicChat, "":
|
||||
default:
|
||||
return &util.JSONResponse{
|
||||
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'"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +85,7 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
|||
if err != nil {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("malformed creation_content"),
|
||||
JSON: spec.BadJSON("malformed creation_content"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +94,7 @@ func (r createRoomRequest) Validate() *util.JSONResponse {
|
|||
if err != nil {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("malformed creation_content"),
|
||||
JSON: spec.BadJSON("malformed creation_content"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,13 +107,6 @@ type createRoomResponse struct {
|
|||
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
|
||||
func CreateRoom(
|
||||
req *http.Request, device *api.Device,
|
||||
|
@ -143,456 +114,124 @@ func CreateRoom(
|
|||
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) util.JSONResponse {
|
||||
var r createRoomRequest
|
||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||
var createRequest createRoomRequest
|
||||
resErr := httputil.UnmarshalJSONRequest(req, &createRequest)
|
||||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
if resErr = r.Validate(); resErr != nil {
|
||||
if resErr = createRequest.Validate(); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
evTime, err := httputil.ParseTSParam(req)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
// nolint: gocyclo
|
||||
func createRoom(
|
||||
ctx context.Context,
|
||||
r createRoomRequest, device *api.Device,
|
||||
createRequest createRoomRequest, device *api.Device,
|
||||
cfg *config.ClientAPI,
|
||||
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
evTime time.Time,
|
||||
) util.JSONResponse {
|
||||
_, userDomain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||
return jsonerror.InternalServerError()
|
||||
util.GetLogger(ctx).WithError(err).Error("invalid userID")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !cfg.Matrix.IsLocalServerName(userDomain) {
|
||||
if !cfg.Matrix.IsLocalServerName(userID.Domain()) {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
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
|
||||
|
||||
roomVersion := roomserverVersion.DefaultRoomVersion()
|
||||
if r.RoomVersion != "" {
|
||||
candidateVersion := gomatrixserverlib.RoomVersion(r.RoomVersion)
|
||||
roomVersion := rsAPI.DefaultRoomVersion()
|
||||
if createRequest.RoomVersion != "" {
|
||||
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
||||
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
||||
if roomVersionError != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UnsupportedRoomVersion(roomVersionError.Error()),
|
||||
JSON: spec.UnsupportedRoomVersion(roomVersionError.Error()),
|
||||
}
|
||||
}
|
||||
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{
|
||||
"userID": userID,
|
||||
"roomID": roomID,
|
||||
"userID": userID.String(),
|
||||
"roomID": roomID.String(),
|
||||
"roomVersion": roomVersion,
|
||||
}).Info("Creating new room")
|
||||
|
||||
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
|
||||
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
createContent := map[string]interface{}{}
|
||||
if len(r.CreationContent) > 0 {
|
||||
if err = json.Unmarshal(r.CreationContent, &createContent); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("invalid create content"),
|
||||
}
|
||||
}
|
||||
}
|
||||
createContent["creator"] = userID
|
||||
createContent["room_version"] = roomVersion
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(userID)
|
||||
joinRuleContent := gomatrixserverlib.JoinRuleContent{
|
||||
JoinRule: spec.Invite,
|
||||
}
|
||||
historyVisibilityContent := gomatrixserverlib.HistoryVisibilityContent{
|
||||
HistoryVisibility: historyVisibilityShared,
|
||||
}
|
||||
|
||||
if r.PowerLevelContentOverride != nil {
|
||||
// Merge powerLevelContentOverride fields by unmarshalling it atop the defaults
|
||||
err = json.Unmarshal(r.PowerLevelContentOverride, &powerLevelContent)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for power_level_content_override failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("malformed power_level_content_override"),
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
switch r.Preset {
|
||||
case presetPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
case presetTrustedPrivateChat:
|
||||
joinRuleContent.JoinRule = spec.Invite
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
for _, invitee := range r.Invite {
|
||||
powerLevelContent.Users[invitee] = 100
|
||||
}
|
||||
case presetPublicChat:
|
||||
joinRuleContent.JoinRule = spec.Public
|
||||
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||
userDisplayName := profile.DisplayName
|
||||
userAvatarURL := profile.AvatarURL
|
||||
|
||||
keyID := cfg.Matrix.KeyID
|
||||
privateKey := cfg.Matrix.PrivateKey
|
||||
|
||||
req := roomserverAPI.PerformCreateRoomRequest{
|
||||
InvitedUsers: createRequest.Invite,
|
||||
RoomName: createRequest.Name,
|
||||
Visibility: createRequest.Visibility,
|
||||
Topic: createRequest.Topic,
|
||||
StatePreset: createRequest.Preset,
|
||||
CreationContent: createRequest.CreationContent,
|
||||
InitialState: createRequest.InitialState,
|
||||
RoomAliasName: createRequest.RoomAliasName,
|
||||
RoomVersion: roomVersion,
|
||||
PowerLevelContentOverride: createRequest.PowerLevelContentOverride,
|
||||
IsDirect: createRequest.IsDirect,
|
||||
|
||||
UserDisplayName: userDisplayName,
|
||||
UserAvatarURL: userAvatarURL,
|
||||
KeyID: keyID,
|
||||
PrivateKey: privateKey,
|
||||
EventTime: evTime,
|
||||
}
|
||||
|
||||
createEvent := fledglingEvent{
|
||||
Type: spec.MRoomCreate,
|
||||
Content: createContent,
|
||||
}
|
||||
powerLevelEvent := fledglingEvent{
|
||||
Type: spec.MRoomPowerLevels,
|
||||
Content: powerLevelContent,
|
||||
}
|
||||
joinRuleEvent := fledglingEvent{
|
||||
Type: spec.MRoomJoinRules,
|
||||
Content: joinRuleContent,
|
||||
}
|
||||
historyVisibilityEvent := fledglingEvent{
|
||||
Type: spec.MRoomHistoryVisibility,
|
||||
Content: historyVisibilityContent,
|
||||
}
|
||||
membershipEvent := fledglingEvent{
|
||||
Type: spec.MRoomMember,
|
||||
StateKey: userID,
|
||||
Content: gomatrixserverlib.MemberContent{
|
||||
Membership: spec.Join,
|
||||
DisplayName: profile.DisplayName,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
},
|
||||
}
|
||||
|
||||
var nameEvent *fledglingEvent
|
||||
var topicEvent *fledglingEvent
|
||||
var guestAccessEvent *fledglingEvent
|
||||
var aliasEvent *fledglingEvent
|
||||
|
||||
if r.Name != "" {
|
||||
nameEvent = &fledglingEvent{
|
||||
Type: spec.MRoomName,
|
||||
Content: eventutil.NameContent{
|
||||
Name: r.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if r.Topic != "" {
|
||||
topicEvent = &fledglingEvent{
|
||||
Type: spec.MRoomTopic,
|
||||
Content: eventutil.TopicContent{
|
||||
Topic: r.Topic,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if r.GuestCanJoin {
|
||||
guestAccessEvent = &fledglingEvent{
|
||||
Type: spec.MRoomGuestAccess,
|
||||
Content: eventutil.GuestAccessContent{
|
||||
GuestAccess: "can_join",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var roomAlias string
|
||||
if r.RoomAliasName != "" {
|
||||
roomAlias = fmt.Sprintf("#%s:%s", r.RoomAliasName, userDomain)
|
||||
// check it's free TODO: This races but is better than nothing
|
||||
hasAliasReq := roomserverAPI.GetRoomIDForAliasRequest{
|
||||
Alias: roomAlias,
|
||||
IncludeAppservices: false,
|
||||
}
|
||||
|
||||
var aliasResp roomserverAPI.GetRoomIDForAliasResponse
|
||||
err = rsAPI.GetRoomIDForAlias(ctx, &hasAliasReq, &aliasResp)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("aliasAPI.GetRoomIDForAlias failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
if aliasResp.RoomID != "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.RoomInUse("Room ID already exists."),
|
||||
}
|
||||
}
|
||||
|
||||
aliasEvent = &fledglingEvent{
|
||||
Type: spec.MRoomCanonicalAlias,
|
||||
Content: eventutil.CanonicalAlias{
|
||||
Alias: roomAlias,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var initialStateEvents []fledglingEvent
|
||||
for i := range r.InitialState {
|
||||
if r.InitialState[i].StateKey != "" {
|
||||
initialStateEvents = append(initialStateEvents, r.InitialState[i])
|
||||
continue
|
||||
}
|
||||
|
||||
switch r.InitialState[i].Type {
|
||||
case spec.MRoomCreate:
|
||||
continue
|
||||
|
||||
case spec.MRoomPowerLevels:
|
||||
powerLevelEvent = r.InitialState[i]
|
||||
|
||||
case spec.MRoomJoinRules:
|
||||
joinRuleEvent = r.InitialState[i]
|
||||
|
||||
case spec.MRoomHistoryVisibility:
|
||||
historyVisibilityEvent = r.InitialState[i]
|
||||
|
||||
case spec.MRoomGuestAccess:
|
||||
guestAccessEvent = &r.InitialState[i]
|
||||
|
||||
case spec.MRoomName:
|
||||
nameEvent = &r.InitialState[i]
|
||||
|
||||
case spec.MRoomTopic:
|
||||
topicEvent = &r.InitialState[i]
|
||||
|
||||
default:
|
||||
initialStateEvents = append(initialStateEvents, r.InitialState[i])
|
||||
}
|
||||
}
|
||||
|
||||
// send events into the room in order of:
|
||||
// 1- m.room.create
|
||||
// 2- room creator join member
|
||||
// 3- m.room.power_levels
|
||||
// 4- m.room.join_rules
|
||||
// 5- m.room.history_visibility
|
||||
// 6- m.room.canonical_alias (opt)
|
||||
// 7- m.room.guest_access (opt)
|
||||
// 8- other initial state items
|
||||
// 9- m.room.name (opt)
|
||||
// 10- m.room.topic (opt)
|
||||
// 11- invite events (opt) - with is_direct flag if applicable TODO
|
||||
// 12- 3pid invite events (opt) TODO
|
||||
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
||||
// depending on if those events were in "initial_state" or not. This made it
|
||||
// harder to reason about, hence sticking to a strict static ordering.
|
||||
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
||||
eventsToMake := []fledglingEvent{
|
||||
createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
||||
}
|
||||
if guestAccessEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *guestAccessEvent)
|
||||
}
|
||||
eventsToMake = append(eventsToMake, initialStateEvents...)
|
||||
if nameEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *nameEvent)
|
||||
}
|
||||
if topicEvent != nil {
|
||||
eventsToMake = append(eventsToMake, *topicEvent)
|
||||
}
|
||||
if aliasEvent != nil {
|
||||
// TODO: bit of a chicken and egg problem here as the alias doesn't exist and cannot until we have made the room.
|
||||
// This means we might fail creating the alias but say the canonical alias is something that doesn't exist.
|
||||
eventsToMake = append(eventsToMake, *aliasEvent)
|
||||
}
|
||||
|
||||
// TODO: invite events
|
||||
// TODO: 3pid invite events
|
||||
|
||||
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 = builder.AddAuthEventsAndBuild(userDomain, &authEvents, evTime, roomVersion, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
if err = gomatrixserverlib.Allowed(ev, &authEvents); err != nil {
|
||||
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 []fclient.InviteV2StrippedState
|
||||
for _, event := range builtEvents {
|
||||
// Chosen events from the spec:
|
||||
// https://spec.matrix.org/v1.3/client-server-api/#stripped-state
|
||||
switch event.Type() {
|
||||
case spec.MRoomCreate:
|
||||
fallthrough
|
||||
case spec.MRoomName:
|
||||
fallthrough
|
||||
case spec.MRoomAvatar:
|
||||
fallthrough
|
||||
case spec.MRoomTopic:
|
||||
fallthrough
|
||||
case spec.MRoomCanonicalAlias:
|
||||
fallthrough
|
||||
case spec.MRoomEncryption:
|
||||
fallthrough
|
||||
case spec.MRoomMember:
|
||||
fallthrough
|
||||
case spec.MRoomJoinRules:
|
||||
ev := event.Event
|
||||
globalStrippedState = append(
|
||||
globalStrippedState,
|
||||
fclient.NewInviteV2StrippedState(ev),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Process the invites.
|
||||
for _, invitee := range r.Invite {
|
||||
// Build the invite event.
|
||||
inviteEvent, err := buildMembershipEvent(
|
||||
ctx, invitee, "", profileAPI, device, spec.Invite,
|
||||
roomID, r.IsDirect, cfg, evTime, rsAPI, asAPI,
|
||||
)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||
continue
|
||||
}
|
||||
inviteStrippedState := append(
|
||||
globalStrippedState,
|
||||
fclient.NewInviteV2StrippedState(inviteEvent.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{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
}
|
||||
}
|
||||
if inviteRes.Error != nil {
|
||||
return inviteRes.Error.JSONResponse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Visibility == "public" {
|
||||
// expose this room in the published room list
|
||||
var pubRes roomserverAPI.PerformPublishResponse
|
||||
if err := rsAPI.PerformPublish(ctx, &roomserverAPI.PerformPublishRequest{
|
||||
RoomID: roomID,
|
||||
Visibility: "public",
|
||||
}, &pubRes); err != nil {
|
||||
return jsonerror.InternalAPIError(ctx, err)
|
||||
}
|
||||
if pubRes.Error != nil {
|
||||
// treat as non-fatal since the room is already made by this point
|
||||
util.GetLogger(ctx).WithError(pubRes.Error).Error("failed to visibility:public")
|
||||
}
|
||||
roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req)
|
||||
if createRes != nil {
|
||||
return *createRes
|
||||
}
|
||||
|
||||
response := createRoomResponse{
|
||||
RoomID: roomID,
|
||||
RoomID: roomID.String(),
|
||||
RoomAlias: roomAlias,
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -24,7 +24,7 @@ func Deactivate(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
||||
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,10 @@ func Deactivate(
|
|||
localpart, serverName, err := gomatrixserverlib.SplitID('@', login.Username())
|
||||
if err != nil {
|
||||
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
|
||||
|
@ -46,7 +49,10 @@ func Deactivate(
|
|||
}, &res)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformAccountDeactivation failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -22,9 +22,9 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"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/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
@ -60,7 +60,10 @@ func GetDeviceByID(
|
|||
}, &queryRes)
|
||||
if err != nil {
|
||||
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
|
||||
for _, device := range queryRes.Devices {
|
||||
|
@ -72,7 +75,7 @@ func GetDeviceByID(
|
|||
if targetDevice == nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Unknown device"),
|
||||
JSON: spec.NotFound("Unknown device"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +100,10 @@ func GetDevicesByLocalpart(
|
|||
}, &queryRes)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("QueryDevices failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
res := devicesJSON{}
|
||||
|
@ -139,12 +145,15 @@ func UpdateDeviceByID(
|
|||
}, &performRes)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceUpdate failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !performRes.DeviceExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.Forbidden("device does not exist"),
|
||||
JSON: spec.Forbidden("device does not exist"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +183,7 @@ func DeleteDeviceById(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
||||
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +193,7 @@ func DeleteDeviceById(
|
|||
if dev != deviceID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("session and device mismatch"),
|
||||
JSON: spec.Forbidden("session and device mismatch"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +215,10 @@ func DeleteDeviceById(
|
|||
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
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
|
||||
|
@ -214,7 +226,7 @@ func DeleteDeviceById(
|
|||
if login.Username() != localpart && login.Username() != device.UserID {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("Cannot delete another user's device"),
|
||||
JSON: spec.Forbidden("Cannot delete another user's device"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +236,10 @@ func DeleteDeviceById(
|
|||
DeviceIDs: []string{deviceID},
|
||||
}, &res); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformDeviceDeletion failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
deleteOK = true
|
||||
|
@ -245,7 +260,7 @@ func DeleteDevices(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
||||
JSON: spec.BadJSON("The request body could not be read: " + err.Error()),
|
||||
}
|
||||
}
|
||||
defer req.Body.Close() // nolint:errcheck
|
||||
|
@ -259,14 +274,17 @@ func DeleteDevices(
|
|||
if login.Username() != device.UserID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("unable to delete devices for other user"),
|
||||
JSON: spec.Forbidden("unable to delete devices for other user"),
|
||||
}
|
||||
}
|
||||
|
||||
payload := devicesDeleteJSON{}
|
||||
if err = json.Unmarshal(bodyBytes, &payload); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("unable to unmarshal device deletion request")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
var res api.PerformDeviceDeletionResponse
|
||||
|
@ -275,7 +293,10 @@ func DeleteDevices(
|
|||
DeviceIDs: payload.Devices,
|
||||
}, &res); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("userAPI.PerformDeviceDeletion failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
|
@ -47,7 +46,7 @@ func (r *roomDirectoryResponse) fillServers(servers []spec.ServerName) {
|
|||
func DirectoryRoom(
|
||||
req *http.Request,
|
||||
roomAlias string,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
cfg *config.ClientAPI,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
fedSenderAPI federationAPI.ClientFederationAPI,
|
||||
|
@ -56,7 +55,7 @@ func DirectoryRoom(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +69,10 @@ func DirectoryRoom(
|
|||
queryRes := &roomserverAPI.GetRoomIDForAliasResponse{}
|
||||
if err = rsAPI.GetRoomIDForAlias(req.Context(), queryReq, queryRes); err != nil {
|
||||
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
|
||||
|
@ -84,7 +86,10 @@ func DirectoryRoom(
|
|||
// TODO: Return 502 if the remote server errored.
|
||||
// TODO: Return 504 if the remote server timed out.
|
||||
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.fillServers(fedRes.Servers)
|
||||
|
@ -93,7 +98,7 @@ func DirectoryRoom(
|
|||
if res.RoomID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound(
|
||||
JSON: spec.NotFound(
|
||||
fmt.Sprintf("Room alias %s not found", roomAlias),
|
||||
),
|
||||
}
|
||||
|
@ -103,7 +108,10 @@ func DirectoryRoom(
|
|||
var joinedHostsRes federationAPI.QueryJoinedHostServerNamesInRoomResponse
|
||||
if err = fedSenderAPI.QueryJoinedHostServerNamesInRoom(req.Context(), &joinedHostsReq, &joinedHostsRes); err != nil {
|
||||
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)
|
||||
}
|
||||
|
@ -126,14 +134,14 @@ func SetLocalAlias(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("Alias must be on local homeserver"),
|
||||
JSON: spec.Forbidden("Alias must be on local homeserver"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +154,7 @@ func SetLocalAlias(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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 {
|
||||
|
@ -158,7 +166,7 @@ func SetLocalAlias(
|
|||
if namespace.Exclusive && namespace.RegexpObject.MatchString(alias) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.ASExclusive("Alias is reserved by an application service"),
|
||||
JSON: spec.ASExclusive("Alias is reserved by an application service"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,21 +181,50 @@ func SetLocalAlias(
|
|||
return *resErr
|
||||
}
|
||||
|
||||
queryReq := roomserverAPI.SetRoomAliasRequest{
|
||||
UserID: device.UserID,
|
||||
RoomID: r.RoomID,
|
||||
Alias: alias,
|
||||
}
|
||||
var queryRes roomserverAPI.SetRoomAliasResponse
|
||||
if err := rsAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||
return jsonerror.InternalServerError()
|
||||
roomID, err := spec.NewRoomID(r.RoomID)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("invalid room ID"),
|
||||
}
|
||||
}
|
||||
|
||||
if queryRes.AliasExists {
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
|
||||
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *roomID, *userID)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("QuerySenderIDForUser failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
} else if senderID == nil {
|
||||
util.GetLogger(req.Context()).WithField("roomID", *roomID).WithField("userID", *userID).Error("Sender ID not found")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
|
||||
aliasAlreadyExists, err := rsAPI.SetRoomAlias(req.Context(), *senderID, *roomID, alias)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if aliasAlreadyExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusConflict,
|
||||
JSON: jsonerror.Unknown("The alias " + alias + " already exists."),
|
||||
JSON: spec.Unknown("The alias " + alias + " already exists."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,27 +241,91 @@ func RemoveLocalAlias(
|
|||
alias string,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
) util.JSONResponse {
|
||||
queryReq := roomserverAPI.RemoveRoomAliasRequest{
|
||||
Alias: alias,
|
||||
UserID: device.UserID,
|
||||
}
|
||||
var queryRes roomserverAPI.RemoveRoomAliasResponse
|
||||
if err := rsAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
if !queryRes.Found {
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("The alias does not exist."),
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{Err: "UserID for device is invalid"},
|
||||
}
|
||||
}
|
||||
|
||||
if !queryRes.Removed {
|
||||
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."),
|
||||
}
|
||||
}
|
||||
|
||||
// This seems like the kind of auth check that should be done in the roomserver, but
|
||||
// if this check fails (user is not in the room), then there will be no SenderID for the user
|
||||
// for pseudo-ID rooms - it will just return "". However, we can't use lack of a sender ID
|
||||
// as meaning they are not in the room, since lacking a sender ID could be caused by other bugs.
|
||||
// TODO: maybe have QuerySenderIDForUser return richer errors?
|
||||
var queryResp roomserverAPI.QueryMembershipForUserResponse
|
||||
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: validRoomID.String(),
|
||||
UserID: *userID,
|
||||
}, &queryResp)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.QueryMembershipForUser failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
if !queryResp.IsInRoom {
|
||||
return util.JSONResponse{
|
||||
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."),
|
||||
}
|
||||
}
|
||||
|
||||
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."),
|
||||
}
|
||||
}
|
||||
// TODO: how to handle this case? missing user/room keys seem to be a whole new class of errors
|
||||
if deviceSenderID == nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
|
||||
aliasFound, aliasRemoved, err := rsAPI.RemoveRoomAlias(req.Context(), *deviceSenderID, alias)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
|
||||
if !aliasFound {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: spec.NotFound("The alias does not exist."),
|
||||
}
|
||||
}
|
||||
|
||||
if !aliasRemoved {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden("You do not have permission to remove this alias."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +350,10 @@ func GetVisibility(
|
|||
}, &res)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("QueryPublishedRooms failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
var v roomVisibility
|
||||
|
@ -271,7 +375,30 @@ func SetVisibility(
|
|||
req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, dev *userapi.Device,
|
||||
roomID string,
|
||||
) 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 || senderID == 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 {
|
||||
return *resErr
|
||||
}
|
||||
|
@ -284,18 +411,21 @@ func SetVisibility(
|
|||
}},
|
||||
}
|
||||
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 {
|
||||
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
|
||||
power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].Event)
|
||||
if power.UserLevel(dev.UserID) < power.EventLevel(spec.MRoomCanonicalAlias, true) {
|
||||
power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU)
|
||||
if power.UserLevel(*senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) {
|
||||
return util.JSONResponse{
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,16 +434,15 @@ func SetVisibility(
|
|||
return *reqErr
|
||||
}
|
||||
|
||||
var publishRes roomserverAPI.PerformPublishResponse
|
||||
if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
|
||||
if err = rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
|
||||
RoomID: roomID,
|
||||
Visibility: v.Visibility,
|
||||
}, &publishRes); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if publishRes.Error != nil {
|
||||
util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed")
|
||||
return publishRes.Error.JSONResponse()
|
||||
}); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -329,7 +458,7 @@ func SetVisibilityAS(
|
|||
if dev.AccountType != userapi.AccountTypeAppService {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("Only appservice may use this endpoint"),
|
||||
JSON: spec.Forbidden("Only appservice may use this endpoint"),
|
||||
}
|
||||
}
|
||||
var v roomVisibility
|
||||
|
@ -342,18 +471,17 @@ func SetVisibilityAS(
|
|||
return *reqErr
|
||||
}
|
||||
}
|
||||
var publishRes roomserverAPI.PerformPublishResponse
|
||||
if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
|
||||
RoomID: roomID,
|
||||
Visibility: v.Visibility,
|
||||
NetworkID: networkID,
|
||||
AppserviceID: dev.AppserviceID,
|
||||
}, &publishRes); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if publishRes.Error != nil {
|
||||
util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed")
|
||||
return publishRes.Error.JSONResponse()
|
||||
}); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("failed to publish room")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -29,7 +29,6 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
)
|
||||
|
@ -57,7 +56,7 @@ type filter struct {
|
|||
func GetPostPublicRooms(
|
||||
req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
cfg *config.ClientAPI,
|
||||
) util.JSONResponse {
|
||||
var request PublicRoomReq
|
||||
|
@ -68,7 +67,7 @@ func GetPostPublicRooms(
|
|||
if request.IncludeAllNetworks && request.NetworkID != "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidParam("include_all_networks and third_party_instance_id can not be used together"),
|
||||
JSON: spec.InvalidParam("include_all_networks and third_party_instance_id can not be used together"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +81,10 @@ func GetPostPublicRooms(
|
|||
)
|
||||
if err != nil {
|
||||
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{
|
||||
Code: http.StatusOK,
|
||||
|
@ -93,7 +95,10 @@ func GetPostPublicRooms(
|
|||
response, err := publicRooms(req.Context(), request, rsAPI, extRoomsProvider)
|
||||
if err != nil {
|
||||
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{
|
||||
Code: http.StatusOK,
|
||||
|
@ -173,7 +178,7 @@ func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSO
|
|||
if httpReq.Method != "GET" && httpReq.Method != "POST" {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusMethodNotAllowed,
|
||||
JSON: jsonerror.NotFound("Bad method"),
|
||||
JSON: spec.NotFound("Bad method"),
|
||||
}
|
||||
}
|
||||
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")
|
||||
return &util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.BadJSON("limit param is not a number"),
|
||||
JSON: spec.BadJSON("limit param is not a number"),
|
||||
}
|
||||
}
|
||||
request.Limit = int64(limit)
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
|
||||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
type getJoinedRoomsResponse struct {
|
||||
|
@ -33,20 +33,36 @@ func GetJoinedRooms(
|
|||
device *userapi.Device,
|
||||
rsAPI api.ClientRoomserverAPI,
|
||||
) util.JSONResponse {
|
||||
var res api.QueryRoomsForUserResponse
|
||||
err := rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
|
||||
UserID: device.UserID,
|
||||
WantMembership: "join",
|
||||
}, &res)
|
||||
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("Invalid device user ID")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
|
||||
rooms, err := rsAPI.QueryRoomsForUser(req.Context(), *deviceUserID, "join")
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
if res.RoomIDs == nil {
|
||||
res.RoomIDs = []string{}
|
||||
|
||||
var roomIDStrs []string
|
||||
if rooms == nil {
|
||||
roomIDStrs = []string{}
|
||||
} else {
|
||||
roomIDStrs = make([]string, len(rooms))
|
||||
for i, roomID := range rooms {
|
||||
roomIDStrs[i] = roomID.String()
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: getJoinedRoomsResponse{res.RoomIDs},
|
||||
JSON: getJoinedRoomsResponse{roomIDStrs},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,14 +15,16 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
@ -41,7 +43,6 @@ func JoinRoomByIDOrAlias(
|
|||
IsGuest: device.AccountType == api.AccountTypeGuest,
|
||||
Content: map[string]interface{}{},
|
||||
}
|
||||
joinRes := roomserverAPI.PerformJoinResponse{}
|
||||
|
||||
// Check to see if any ?server_name= query parameters were
|
||||
// given in the request.
|
||||
|
@ -72,7 +73,7 @@ func JoinRoomByIDOrAlias(
|
|||
util.GetLogger(req.Context()).Error("Unable to query user profile, no profile found.")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("Unable to query user profile, no profile found."),
|
||||
JSON: spec.Unknown("Unable to query user profile, no profile found."),
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
@ -81,37 +82,65 @@ func JoinRoomByIDOrAlias(
|
|||
done := make(chan util.JSONResponse, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
if err := rsAPI.PerformJoin(req.Context(), &joinReq, &joinRes); err != nil {
|
||||
done <- jsonerror.InternalAPIError(req.Context(), err)
|
||||
} else if joinRes.Error != nil {
|
||||
if joinRes.Error.Code == roomserverAPI.PerformErrorNotAllowed && device.AccountType == api.AccountTypeGuest {
|
||||
done <- util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.GuestAccessForbidden(joinRes.Error.Msg),
|
||||
}
|
||||
} else {
|
||||
done <- joinRes.Error.JSONResponse()
|
||||
}
|
||||
} else {
|
||||
done <- util.JSONResponse{
|
||||
roomID, _, err := rsAPI.PerformJoin(req.Context(), &joinReq)
|
||||
var response util.JSONResponse
|
||||
|
||||
switch e := err.(type) {
|
||||
case nil: // success case
|
||||
response = util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
// TODO: Put the response struct somewhere internal.
|
||||
JSON: struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}{joinRes.RoomID},
|
||||
}{roomID},
|
||||
}
|
||||
case roomserverAPI.ErrInvalidID:
|
||||
response = util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: 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
|
||||
// timeout, at which point we'll just return a 200 to placate clients.
|
||||
timer := time.NewTimer(time.Second * 20)
|
||||
select {
|
||||
case <-time.After(time.Second * 20):
|
||||
case <-timer.C:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusAccepted,
|
||||
JSON: jsonerror.Unknown("The room join will continue in the background."),
|
||||
JSON: spec.Unknown("The room join will continue in the background."),
|
||||
}
|
||||
case result := <-done:
|
||||
// Stop and drain the timer
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
"github.com/matrix-org/dendrite/roomserver"
|
||||
|
@ -34,9 +35,9 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
|||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
natsInstance := jetstream.NATSInstance{}
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil) // creates the rs.Inputer etc
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||
rsAPI.SetFederationAPI(nil, nil) // creates the rs.Inputer etc
|
||||
|
||||
// Create the users in the userapi
|
||||
for _, u := range []*test.User{alice, bob, charlie} {
|
||||
|
@ -63,10 +64,9 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
|||
IsDirect: true,
|
||||
Topic: "testing",
|
||||
Visibility: "public",
|
||||
Preset: presetPublicChat,
|
||||
Preset: spec.PresetPublicChat,
|
||||
RoomAliasName: "alias",
|
||||
Invite: []string{bob.ID},
|
||||
GuestCanJoin: false,
|
||||
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
||||
crResp, ok := resp.JSON.(createRoomResponse)
|
||||
if !ok {
|
||||
|
@ -75,13 +75,12 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
|||
|
||||
// create a room with guest access enabled and invite Charlie
|
||||
resp = createRoom(ctx, createRoomRequest{
|
||||
Name: "testing",
|
||||
IsDirect: true,
|
||||
Topic: "testing",
|
||||
Visibility: "public",
|
||||
Preset: presetPublicChat,
|
||||
Invite: []string{charlie.ID},
|
||||
GuestCanJoin: true,
|
||||
Name: "testing",
|
||||
IsDirect: true,
|
||||
Topic: "testing",
|
||||
Visibility: "public",
|
||||
Preset: spec.PresetPublicChat,
|
||||
Invite: []string{charlie.ID},
|
||||
}, aliceDev, &cfg.ClientAPI, userAPI, rsAPI, asAPI, time.Now())
|
||||
crRespWithGuestAccess, ok := resp.JSON.(createRoomResponse)
|
||||
if !ok {
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -61,28 +61,26 @@ func CreateKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
|
|||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
var performKeyBackupResp userapi.PerformKeyBackupResponse
|
||||
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
if len(kb.AuthData) == 0 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.BadJSON("missing auth_data"),
|
||||
}
|
||||
}
|
||||
version, err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
UserID: device.UserID,
|
||||
Version: "",
|
||||
AuthData: kb.AuthData,
|
||||
Algorithm: kb.Algorithm,
|
||||
}, &performKeyBackupResp); err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
if performKeyBackupResp.Error != "" {
|
||||
if performKeyBackupResp.BadInput {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error),
|
||||
}
|
||||
}
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
|
||||
})
|
||||
if err != nil {
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", err))
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: keyBackupVersionCreateResponse{
|
||||
Version: performKeyBackupResp.Version,
|
||||
Version: version,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -90,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.
|
||||
// Implements GET /_matrix/client/r0/room_keys/version and GET /_matrix/client/r0/room_keys/version/{version}
|
||||
func KeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse {
|
||||
var queryResp userapi.QueryKeyBackupResponse
|
||||
if err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
|
||||
queryResp, err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
|
||||
UserID: device.UserID,
|
||||
Version: version,
|
||||
}, &queryResp); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if queryResp.Error != "" {
|
||||
return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", queryResp.Error))
|
||||
})
|
||||
if err != nil {
|
||||
return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", err))
|
||||
}
|
||||
if !queryResp.Exists {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("version not found"),
|
||||
JSON: spec.NotFound("version not found"),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
@ -126,31 +121,29 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse
|
|||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
var performKeyBackupResp userapi.PerformKeyBackupResponse
|
||||
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
performKeyBackupResp, err := userAPI.UpdateBackupKeyAuthData(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
UserID: device.UserID,
|
||||
Version: version,
|
||||
AuthData: kb.AuthData,
|
||||
Algorithm: kb.Algorithm,
|
||||
}, &performKeyBackupResp); err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
if performKeyBackupResp.Error != "" {
|
||||
if performKeyBackupResp.BadInput {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error),
|
||||
}
|
||||
})
|
||||
switch e := err.(type) {
|
||||
case spec.ErrRoomKeysVersion:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: e,
|
||||
}
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
|
||||
case nil:
|
||||
default:
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", e))
|
||||
}
|
||||
|
||||
if !performKeyBackupResp.Exists {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("backup version not found"),
|
||||
JSON: spec.NotFound("backup version not found"),
|
||||
}
|
||||
}
|
||||
// Unclear what the 200 body should be
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: keyBackupVersionCreateResponse{
|
||||
|
@ -162,35 +155,19 @@ func ModifyKeyBackupVersionAuthData(req *http.Request, userAPI userapi.ClientUse
|
|||
// Delete a version of key backup. Version must not be empty. If the key backup was previously deleted, will return 200 OK.
|
||||
// Implements DELETE /_matrix/client/r0/room_keys/version/{version}
|
||||
func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string) util.JSONResponse {
|
||||
var performKeyBackupResp userapi.PerformKeyBackupResponse
|
||||
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
UserID: device.UserID,
|
||||
Version: version,
|
||||
DeleteBackup: true,
|
||||
}, &performKeyBackupResp); err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
exists, err := userAPI.DeleteKeyBackup(req.Context(), device.UserID, version)
|
||||
if err != nil {
|
||||
return util.ErrorResponse(fmt.Errorf("DeleteKeyBackup: %s", err))
|
||||
}
|
||||
if performKeyBackupResp.Error != "" {
|
||||
if performKeyBackupResp.BadInput {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error),
|
||||
}
|
||||
}
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
|
||||
}
|
||||
if !performKeyBackupResp.Exists {
|
||||
if !exists {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("backup version not found"),
|
||||
JSON: spec.NotFound("backup version not found"),
|
||||
}
|
||||
}
|
||||
// Unclear what the 200 body should be
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: keyBackupVersionCreateResponse{
|
||||
Version: performKeyBackupResp.Version,
|
||||
},
|
||||
JSON: struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,27 +175,26 @@ func DeleteKeyBackupVersion(req *http.Request, userAPI userapi.ClientUserAPI, de
|
|||
func UploadBackupKeys(
|
||||
req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version string, keys *keyBackupSessionRequest,
|
||||
) util.JSONResponse {
|
||||
var performKeyBackupResp userapi.PerformKeyBackupResponse
|
||||
if err := userAPI.PerformKeyBackup(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
performKeyBackupResp, err := userAPI.UpdateBackupKeyAuthData(req.Context(), &userapi.PerformKeyBackupRequest{
|
||||
UserID: device.UserID,
|
||||
Version: version,
|
||||
Keys: *keys,
|
||||
}, &performKeyBackupResp); err != nil && performKeyBackupResp.Error == "" {
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
if performKeyBackupResp.Error != "" {
|
||||
if performKeyBackupResp.BadInput {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue(performKeyBackupResp.Error),
|
||||
}
|
||||
})
|
||||
|
||||
switch e := err.(type) {
|
||||
case spec.ErrRoomKeysVersion:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: e,
|
||||
}
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %s", performKeyBackupResp.Error))
|
||||
case nil:
|
||||
default:
|
||||
return util.ErrorResponse(fmt.Errorf("PerformKeyBackup: %w", e))
|
||||
}
|
||||
if !performKeyBackupResp.Exists {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("backup version not found"),
|
||||
JSON: spec.NotFound("backup version not found"),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
@ -234,23 +210,20 @@ func UploadBackupKeys(
|
|||
func GetBackupKeys(
|
||||
req *http.Request, userAPI userapi.ClientUserAPI, device *userapi.Device, version, roomID, sessionID string,
|
||||
) util.JSONResponse {
|
||||
var queryResp userapi.QueryKeyBackupResponse
|
||||
if err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
|
||||
queryResp, err := userAPI.QueryKeyBackup(req.Context(), &userapi.QueryKeyBackupRequest{
|
||||
UserID: device.UserID,
|
||||
Version: version,
|
||||
ReturnKeys: true,
|
||||
KeysForRoomID: roomID,
|
||||
KeysForSessionID: sessionID,
|
||||
}, &queryResp); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if queryResp.Error != "" {
|
||||
return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %s", queryResp.Error))
|
||||
})
|
||||
if err != nil {
|
||||
return util.ErrorResponse(fmt.Errorf("QueryKeyBackup: %w", err))
|
||||
}
|
||||
if !queryResp.Exists {
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("version not found"),
|
||||
JSON: spec.NotFound("version not found"),
|
||||
}
|
||||
}
|
||||
if sessionID != "" {
|
||||
|
@ -267,17 +240,20 @@ func GetBackupKeys(
|
|||
}
|
||||
} else if roomID != "" {
|
||||
roomData, ok := queryResp.Keys[roomID]
|
||||
if ok {
|
||||
// wrap response in "sessions"
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: struct {
|
||||
Sessions map[string]userapi.KeyBackupSession `json:"sessions"`
|
||||
}{
|
||||
Sessions: roomData,
|
||||
},
|
||||
}
|
||||
if !ok {
|
||||
// If no keys are found, then an object with an empty sessions property will be returned
|
||||
roomData = make(map[string]userapi.KeyBackupSession)
|
||||
}
|
||||
// wrap response in "sessions"
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: struct {
|
||||
Sessions map[string]userapi.KeyBackupSession `json:"sessions"`
|
||||
}{
|
||||
Sessions: roomData,
|
||||
},
|
||||
}
|
||||
|
||||
} else {
|
||||
// response is the same as the upload request
|
||||
var resp keyBackupSessionRequest
|
||||
|
@ -298,6 +274,6 @@ func GetBackupKeys(
|
|||
}
|
||||
return util.JSONResponse{
|
||||
Code: 404,
|
||||
JSON: jsonerror.NotFound("keys not found"),
|
||||
JSON: spec.NotFound("keys not found"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"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/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -71,31 +71,29 @@ func UploadCrossSigningDeviceKeys(
|
|||
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
|
||||
|
||||
uploadReq.UserID = device.UserID
|
||||
if err := keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes)
|
||||
|
||||
if err := uploadRes.Error; err != nil {
|
||||
switch {
|
||||
case err.IsInvalidSignature:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidSignature(err.Error()),
|
||||
JSON: spec.InvalidSignature(err.Error()),
|
||||
}
|
||||
case err.IsMissingParam:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingParam(err.Error()),
|
||||
JSON: spec.MissingParam(err.Error()),
|
||||
}
|
||||
case err.IsInvalidParam:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidParam(err.Error()),
|
||||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown(err.Error()),
|
||||
JSON: spec.Unknown(err.Error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,31 +113,29 @@ func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.Clie
|
|||
}
|
||||
|
||||
uploadReq.UserID = device.UserID
|
||||
if err := keyserverAPI.PerformUploadDeviceSignatures(req.Context(), uploadReq, uploadRes); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
keyserverAPI.PerformUploadDeviceSignatures(req.Context(), uploadReq, uploadRes)
|
||||
|
||||
if err := uploadRes.Error; err != nil {
|
||||
switch {
|
||||
case err.IsInvalidSignature:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidSignature(err.Error()),
|
||||
JSON: spec.InvalidSignature(err.Error()),
|
||||
}
|
||||
case err.IsMissingParam:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingParam(err.Error()),
|
||||
JSON: spec.MissingParam(err.Error()),
|
||||
}
|
||||
case err.IsInvalidParam:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidParam(err.Error()),
|
||||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown(err.Error()),
|
||||
JSON: spec.Unknown(err.Error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
type uploadKeysRequest struct {
|
||||
|
@ -67,7 +67,10 @@ func UploadKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *api.Device)
|
|||
}
|
||||
if uploadRes.Error != nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
queryRes := api.QueryKeysResponse{}
|
||||
if err := keyAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{
|
||||
keyAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{
|
||||
UserID: device.UserID,
|
||||
UserToDevices: r.DeviceKeys,
|
||||
Timeout: r.GetTimeout(),
|
||||
// TODO: Token?
|
||||
}, &queryRes); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
}, &queryRes)
|
||||
return util.JSONResponse{
|
||||
Code: 200,
|
||||
JSON: map[string]interface{}{
|
||||
|
@ -152,15 +153,16 @@ func ClaimKeys(req *http.Request, keyAPI api.ClientKeyAPI) util.JSONResponse {
|
|||
return *resErr
|
||||
}
|
||||
claimRes := api.PerformClaimKeysResponse{}
|
||||
if err := keyAPI.PerformClaimKeys(req.Context(), &api.PerformClaimKeysRequest{
|
||||
keyAPI.PerformClaimKeys(req.Context(), &api.PerformClaimKeysRequest{
|
||||
OneTimeKeys: r.OneTimeKeys,
|
||||
Timeout: r.GetTimeout(),
|
||||
}, &claimRes); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
}, &claimRes)
|
||||
if claimRes.Error != nil {
|
||||
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{
|
||||
Code: 200,
|
||||
|
|
|
@ -17,9 +17,9 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -29,10 +29,18 @@ func LeaveRoomByID(
|
|||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
roomID string,
|
||||
) 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.
|
||||
leaveReq := roomserverAPI.PerformLeaveRequest{
|
||||
RoomID: roomID,
|
||||
UserID: device.UserID,
|
||||
Leaver: *userID,
|
||||
}
|
||||
leaveRes := roomserverAPI.PerformLeaveResponse{}
|
||||
|
||||
|
@ -41,12 +49,12 @@ func LeaveRoomByID(
|
|||
if leaveRes.Code != 0 {
|
||||
return util.JSONResponse{
|
||||
Code: leaveRes.Code,
|
||||
JSON: jsonerror.LeaveServerNoticeError(),
|
||||
JSON: spec.LeaveServerNoticeError(),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.Unknown(err.Error()),
|
||||
JSON: spec.Unknown(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -72,7 +72,7 @@ func Login(
|
|||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusMethodNotAllowed,
|
||||
JSON: jsonerror.NotFound("Bad method"),
|
||||
JSON: spec.NotFound("Bad method"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,13 +83,19 @@ func completeAuth(
|
|||
token, err := auth.GenerateAccessToken()
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
|
@ -105,7 +111,7 @@ func completeAuth(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
||||
JSON: spec.Unknown("failed to create device: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ func TestLogin(t *testing.T) {
|
|||
routers := httputil.NewRouters()
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
// Needed for /login
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -33,7 +33,10 @@ func Logout(
|
|||
}, &performRes)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -53,7 +56,10 @@ func LogoutAll(
|
|||
}, &performRes)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -16,23 +16,25 @@ package routing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"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/internal/eventutil"
|
||||
"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"
|
||||
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"
|
||||
)
|
||||
|
@ -50,11 +52,33 @@ func SendBan(
|
|||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
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 || senderID == 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 {
|
||||
return *errRes
|
||||
}
|
||||
|
@ -63,11 +87,11 @@ func SendBan(
|
|||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
allowedToBan := pl.UserLevel(device.UserID) >= pl.Ban
|
||||
allowedToBan := pl.UserLevel(*senderID) >= pl.Ban
|
||||
if !allowedToBan {
|
||||
return util.JSONResponse{
|
||||
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."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,14 +108,17 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic
|
|||
)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
serverName := device.UserDomain()
|
||||
if err = roomserverAPI.SendEvents(
|
||||
ctx, rsAPI,
|
||||
roomserverAPI.KindNew,
|
||||
[]*gomatrixserverlib.HeaderedEvent{event},
|
||||
[]*types.HeaderedEvent{event},
|
||||
device.UserDomain(),
|
||||
serverName,
|
||||
serverName,
|
||||
|
@ -99,7 +126,10 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic
|
|||
false,
|
||||
); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -120,11 +150,33 @@ func SendKick(
|
|||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
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 || senderID == 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 {
|
||||
return *errRes
|
||||
}
|
||||
|
@ -133,18 +185,25 @@ func SendKick(
|
|||
if errRes != nil {
|
||||
return *errRes
|
||||
}
|
||||
allowedToKick := pl.UserLevel(device.UserID) >= pl.Kick
|
||||
allowedToKick := pl.UserLevel(*senderID) >= pl.Kick
|
||||
if !allowedToKick {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to kick this user, power level too low."),
|
||||
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
|
||||
err := rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: body.UserID,
|
||||
UserID: *bodyUserID,
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
|
@ -153,7 +212,7 @@ func SendKick(
|
|||
if queryRes.Membership != spec.Join && queryRes.Membership != spec.Invite {
|
||||
return util.JSONResponse{
|
||||
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?
|
||||
|
@ -172,19 +231,34 @@ func SendUnban(
|
|||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
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 {
|
||||
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
|
||||
err := rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: body.UserID,
|
||||
UserID: *bodyUserID,
|
||||
}, &queryRes)
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
|
@ -194,7 +268,7 @@ func SendUnban(
|
|||
if queryRes.Membership != spec.Ban {
|
||||
return util.JSONResponse{
|
||||
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?
|
||||
|
@ -231,11 +305,19 @@ func SendInvite(
|
|||
if body.UserID == "" {
|
||||
return util.JSONResponse{
|
||||
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 {
|
||||
return *errRes
|
||||
}
|
||||
|
@ -255,30 +337,77 @@ func sendInvite(
|
|||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI, evTime time.Time,
|
||||
) (util.JSONResponse, error) {
|
||||
event, err := buildMembershipEvent(
|
||||
ctx, userID, reason, profileAPI, device, spec.Invite,
|
||||
roomID, false, cfg, evTime, rsAPI, asAPI,
|
||||
)
|
||||
validRoomID, err := spec.NewRoomID(roomID)
|
||||
if err != nil {
|
||||
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{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("RoomID is invalid"),
|
||||
}, err
|
||||
}
|
||||
if inviteRes.Error != nil {
|
||||
return inviteRes.Error.JSONResponse(), inviteRes.Error
|
||||
inviter, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}, err
|
||||
}
|
||||
invitee, err := spec.NewUserID(userID, true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("UserID is invalid"),
|
||||
}, err
|
||||
}
|
||||
profile, err := loadProfile(ctx, userID, cfg, profileAPI, asAPI)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}, err
|
||||
}
|
||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}, err
|
||||
}
|
||||
err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
||||
InviteInput: roomserverAPI.InviteInput{
|
||||
RoomID: *validRoomID,
|
||||
Inviter: *inviter,
|
||||
Invitee: *invitee,
|
||||
DisplayName: profile.DisplayName,
|
||||
AvatarURL: profile.AvatarURL,
|
||||
Reason: reason,
|
||||
IsDirect: false,
|
||||
KeyID: identity.KeyID,
|
||||
PrivateKey: identity.PrivateKey,
|
||||
EventTime: evTime,
|
||||
},
|
||||
InviteRoomState: nil, // ask the roomserver to draw up invite room state for us
|
||||
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{
|
||||
|
@ -287,6 +416,42 @@ func sendInvite(
|
|||
}, 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(
|
||||
ctx context.Context,
|
||||
targetUserID, reason string, profileAPI userapi.ClientUserAPI,
|
||||
|
@ -294,37 +459,45 @@ func buildMembershipEvent(
|
|||
membership, roomID string, isDirect bool,
|
||||
cfg *config.ClientAPI, evTime time.Time,
|
||||
rsAPI roomserverAPI.ClientRoomserverAPI, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
) (*gomatrixserverlib.HeaderedEvent, error) {
|
||||
) (*types.HeaderedEvent, error) {
|
||||
profile, err := loadProfile(ctx, targetUserID, cfg, profileAPI, asAPI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: device.UserID,
|
||||
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 {
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validRoomID, err := spec.NewRoomID(roomID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if senderID == nil {
|
||||
return nil, fmt.Errorf("no sender ID for %s in %s", *userID, *validRoomID)
|
||||
}
|
||||
|
||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
||||
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
|
||||
} else if targetSenderID == nil {
|
||||
return nil, fmt.Errorf("no sender ID for %s in %s", *targetID, *validRoomID)
|
||||
}
|
||||
|
||||
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eventutil.QueryAndBuildEvent(ctx, &builder, cfg.Matrix, identity, evTime, rsAPI, nil)
|
||||
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
|
||||
|
@ -364,7 +537,7 @@ func extractRequestData(req *http.Request) (body *threepid.MembershipRequest, ev
|
|||
if err != nil {
|
||||
resErr = &util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
||||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -386,36 +559,43 @@ func checkAndProcessThreepid(
|
|||
req.Context(), device, body, cfg, rsAPI, profileAPI,
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
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{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
JSON: spec.BadJSON(e.Error()),
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
default:
|
||||
util.GetLogger(req.Context()).WithError(err).Error("threepid.CheckAndProcessInvite failed")
|
||||
er := jsonerror.InternalServerError()
|
||||
return inviteStored, &er
|
||||
return inviteStored, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
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
|
||||
err := rsAPI.QueryMembershipForUser(ctx, &roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
|
@ -423,13 +603,15 @@ func checkMemberInRoom(ctx context.Context, rsAPI roomserverAPI.ClientRoomserver
|
|||
}, &membershipRes)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("QueryMembershipForUser: could not query membership for user")
|
||||
e := jsonerror.InternalServerError()
|
||||
return &e
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !membershipRes.IsInRoom {
|
||||
return &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("user does not belong to room"),
|
||||
JSON: spec.Forbidden("user does not belong to room"),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -441,26 +623,38 @@ func SendForget(
|
|||
) util.JSONResponse {
|
||||
ctx := req.Context()
|
||||
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
|
||||
membershipReq := roomserverAPI.QueryMembershipForUserRequest{
|
||||
RoomID: roomID,
|
||||
UserID: device.UserID,
|
||||
UserID: *deviceUserID,
|
||||
}
|
||||
err := rsAPI.QueryMembershipForUser(ctx, &membershipReq, &membershipRes)
|
||||
err = rsAPI.QueryMembershipForUser(ctx, &membershipReq, &membershipRes)
|
||||
if err != nil {
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("room does not exist"),
|
||||
JSON: spec.Forbidden("room does not exist"),
|
||||
}
|
||||
}
|
||||
if membershipRes.IsInRoom {
|
||||
return util.JSONResponse{
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,7 +665,10 @@ func SendForget(
|
|||
response := roomserverAPI.PerformForgetResponse{}
|
||||
if err := rsAPI.PerformForget(ctx, &request, &response); err != nil {
|
||||
logger.WithError(err).Error("PerformForget: unable to forget room")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
|
@ -487,14 +684,14 @@ func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI,
|
|||
if plEvent == nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to perform this action, no power_levels event in this room."),
|
||||
JSON: spec.Forbidden("You don't have permission to perform this action, no power_levels event in this room."),
|
||||
}
|
||||
}
|
||||
pl, err := plEvent.PowerLevels()
|
||||
if err != nil {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("You don't have permission to perform this action, the power_levels event for this room is malformed so auth checks cannot be performed."),
|
||||
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
|
||||
|
|
|
@ -18,9 +18,9 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -35,7 +35,10 @@ func GetNotifications(
|
|||
limit, err = strconv.ParseInt(limitStr, 10, 64)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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{
|
||||
Localpart: localpart,
|
||||
|
@ -54,7 +60,10 @@ func GetNotifications(
|
|||
}, &queryRes)
|
||||
if err != nil {
|
||||
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))
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -17,9 +17,9 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -43,7 +43,7 @@ func CreateOpenIDToken(
|
|||
if userID != device.UserID {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
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{
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"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/sirupsen/logrus"
|
||||
)
|
||||
|
@ -90,7 +90,10 @@ func Password(
|
|||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// Ask the user API to perform the password change.
|
||||
|
@ -102,11 +105,17 @@ func Password(
|
|||
passwordRes := &api.PerformPasswordUpdateResponse{}
|
||||
if err := userAPI.PerformPasswordUpdate(req.Context(), passwordReq, passwordRes); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformPasswordUpdate failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !passwordRes.PasswordUpdated {
|
||||
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
|
||||
|
@ -120,7 +129,10 @@ func Password(
|
|||
logoutRes := &api.PerformDeviceDeletionResponse{}
|
||||
if err := userAPI.PerformDeviceDeletion(req.Context(), logoutReq, logoutRes); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformDeviceDeletion failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
pushersReq := &api.PerformPusherDeletionRequest{
|
||||
|
@ -130,7 +142,10 @@ func Password(
|
|||
}
|
||||
if err := userAPI.PerformPusherDeletion(req.Context(), pushersReq, &struct{}{}); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformPusherDeletion failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func PeekRoomByIDOrAlias(
|
||||
|
@ -41,8 +43,6 @@ func PeekRoomByIDOrAlias(
|
|||
UserID: device.UserID,
|
||||
DeviceID: device.ID,
|
||||
}
|
||||
peekRes := roomserverAPI.PerformPeekResponse{}
|
||||
|
||||
// Check to see if any ?server_name= query parameters were
|
||||
// given in the request.
|
||||
if serverNames, ok := req.URL.Query()["server_name"]; ok {
|
||||
|
@ -55,11 +55,30 @@ func PeekRoomByIDOrAlias(
|
|||
}
|
||||
|
||||
// Ask the roomserver to perform the peek.
|
||||
if err := rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes); err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
if peekRes.Error != nil {
|
||||
return peekRes.Error.JSONResponse()
|
||||
roomID, err := rsAPI.PerformPeek(req.Context(), &peekReq)
|
||||
switch e := err.(type) {
|
||||
case roomserverAPI.ErrInvalidID:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: 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 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.
|
||||
JSON: struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}{peekRes.RoomID},
|
||||
}{roomID},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,18 +104,20 @@ func UnpeekRoomByID(
|
|||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||
roomID string,
|
||||
) util.JSONResponse {
|
||||
unpeekReq := roomserverAPI.PerformUnpeekRequest{
|
||||
RoomID: roomID,
|
||||
UserID: device.UserID,
|
||||
DeviceID: device.ID,
|
||||
}
|
||||
unpeekRes := roomserverAPI.PerformUnpeekResponse{}
|
||||
|
||||
if err := rsAPI.PerformUnpeek(req.Context(), &unpeekReq, &unpeekRes); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
if unpeekRes.Error != nil {
|
||||
return unpeekRes.Error.JSONResponse()
|
||||
err := rsAPI.PerformUnpeek(req.Context(), roomID, device.UserID, device.ID)
|
||||
switch e := err.(type) {
|
||||
case roomserverAPI.ErrInvalidID:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.Unknown(e.Error()),
|
||||
}
|
||||
case nil:
|
||||
default:
|
||||
logrus.WithError(err).WithField("roomID", roomID).Errorf("Failed to un-peek room")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"time"
|
||||
|
||||
"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/setup/config"
|
||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
|
@ -54,7 +53,7 @@ func SetPresence(
|
|||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
|
@ -67,7 +66,7 @@ func SetPresence(
|
|||
if !ok {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
|
@ -75,7 +74,7 @@ func SetPresence(
|
|||
log.WithError(err).Errorf("failed to update presence")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +99,7 @@ func GetPresence(
|
|||
log.WithError(err).Errorf("unable to get presence")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +118,7 @@ func GetPresence(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.InternalServerError(),
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ package routing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -26,12 +27,11 @@ import (
|
|||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"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/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
@ -41,19 +41,22 @@ func GetProfile(
|
|||
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
) util.JSONResponse {
|
||||
profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
|
||||
if err != nil {
|
||||
if err == appserviceAPI.ErrProfileNotExists {
|
||||
return util.JSONResponse{
|
||||
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")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -69,7 +72,7 @@ func GetProfile(
|
|||
func GetAvatarURL(
|
||||
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
) util.JSONResponse {
|
||||
profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation)
|
||||
p, ok := profile.JSON.(eventutil.UserProfile)
|
||||
|
@ -94,7 +97,7 @@ func SetAvatarURL(
|
|||
if userID != device.UserID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
||||
JSON: spec.Forbidden("userID does not match the current user"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,23 +105,20 @@ func SetAvatarURL(
|
|||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
if r.AvatarURL == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("'avatar_url' must be supplied."),
|
||||
}
|
||||
}
|
||||
|
||||
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
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) {
|
||||
return util.JSONResponse{
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,14 +126,17 @@ func SetAvatarURL(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
||||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
profile, changed, err := profileAPI.SetAvatarURL(req.Context(), localpart, domain, r.AvatarURL)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
// No need to build new membership events, since nothing changed
|
||||
if !changed {
|
||||
|
@ -143,7 +146,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 {
|
||||
return response
|
||||
}
|
||||
|
@ -158,7 +161,7 @@ func SetAvatarURL(
|
|||
func GetDisplayName(
|
||||
req *http.Request, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string, asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
) util.JSONResponse {
|
||||
profile := GetProfile(req, profileAPI, cfg, userID, asAPI, federation)
|
||||
p, ok := profile.JSON.(eventutil.UserProfile)
|
||||
|
@ -183,7 +186,7 @@ func SetDisplayName(
|
|||
if userID != device.UserID {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
||||
JSON: spec.Forbidden("userID does not match the current user"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,23 +194,20 @@ func SetDisplayName(
|
|||
if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
if r.DisplayName == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("'displayname' must be supplied."),
|
||||
}
|
||||
}
|
||||
|
||||
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
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) {
|
||||
return util.JSONResponse{
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,14 +215,17 @@ func SetDisplayName(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
||||
JSON: spec.InvalidParam(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
profile, changed, err := profileAPI.SetDisplayName(req.Context(), localpart, domain, r.DisplayName)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
// No need to build new membership events, since nothing changed
|
||||
if !changed {
|
||||
|
@ -232,7 +235,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 {
|
||||
return response
|
||||
}
|
||||
|
@ -246,42 +249,63 @@ func SetDisplayName(
|
|||
func updateProfile(
|
||||
ctx context.Context, rsAPI api.ClientRoomserverAPI, device *userapi.Device,
|
||||
profile *authtypes.Profile,
|
||||
userID string, cfg *config.ClientAPI, evTime time.Time,
|
||||
userID string, evTime time.Time,
|
||||
) (util.JSONResponse, error) {
|
||||
var res api.QueryRoomsForUserResponse
|
||||
err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||
UserID: device.UserID,
|
||||
WantMembership: "join",
|
||||
}, &res)
|
||||
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}, err
|
||||
}
|
||||
|
||||
rooms, err := rsAPI.QueryRoomsForUser(ctx, *deviceUserID, "join")
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("QueryRoomsForUser failed")
|
||||
return jsonerror.InternalServerError(), err
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}, err
|
||||
}
|
||||
|
||||
roomIDStrs := make([]string, len(rooms))
|
||||
for i, room := range rooms {
|
||||
roomIDStrs[i] = room.String()
|
||||
}
|
||||
|
||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
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(
|
||||
ctx, device, res.RoomIDs, *profile, userID, cfg, evTime, rsAPI,
|
||||
ctx, roomIDStrs, *profile, userID, evTime, rsAPI,
|
||||
)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case gomatrixserverlib.BadJSONError:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
JSON: spec.BadJSON(e.Error()),
|
||||
}, e
|
||||
default:
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
@ -294,7 +318,7 @@ func getProfile(
|
|||
ctx context.Context, profileAPI userapi.ProfileAPI, cfg *config.ClientAPI,
|
||||
userID string,
|
||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
) (*authtypes.Profile, error) {
|
||||
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||
if err != nil {
|
||||
|
@ -330,25 +354,33 @@ func getProfile(
|
|||
|
||||
func buildMembershipEvents(
|
||||
ctx context.Context,
|
||||
device *userapi.Device,
|
||||
roomIDs []string,
|
||||
newProfile authtypes.Profile, userID string, cfg *config.ClientAPI,
|
||||
newProfile authtypes.Profile, userID string,
|
||||
evTime time.Time, rsAPI api.ClientRoomserverAPI,
|
||||
) ([]*gomatrixserverlib.HeaderedEvent, error) {
|
||||
evs := []*gomatrixserverlib.HeaderedEvent{}
|
||||
) ([]*types.HeaderedEvent, error) {
|
||||
evs := []*types.HeaderedEvent{}
|
||||
|
||||
fullUserID, err := spec.NewUserID(userID, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, roomID := range roomIDs {
|
||||
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||
if err := rsAPI.QueryRoomVersionForRoom(ctx, &verReq, &verRes); err != nil {
|
||||
validRoomID, err := spec.NewRoomID(roomID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: userID,
|
||||
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if senderID == nil {
|
||||
return nil, fmt.Errorf("sender ID not found for %s in %s", *fullUserID, *validRoomID)
|
||||
}
|
||||
senderIDString := string(*senderID)
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
SenderID: senderIDString,
|
||||
RoomID: roomID,
|
||||
Type: "m.room.member",
|
||||
StateKey: &userID,
|
||||
StateKey: &senderIDString,
|
||||
}
|
||||
|
||||
content := gomatrixserverlib.MemberContent{
|
||||
|
@ -358,21 +390,26 @@ func buildMembershipEvents(
|
|||
content.DisplayName = newProfile.DisplayName
|
||||
content.AvatarURL = newProfile.AvatarURL
|
||||
|
||||
if err := builder.SetContent(content); err != nil {
|
||||
if err = proto.SetContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
||||
user, err := spec.NewUserID(userID, true)
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"net/url"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -34,7 +34,10 @@ func GetPushers(
|
|||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
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{
|
||||
Localpart: localpart,
|
||||
|
@ -42,7 +45,10 @@ func GetPushers(
|
|||
}, &queryRes)
|
||||
if err != nil {
|
||||
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 {
|
||||
queryRes.Pushers[i].SessionID = 0
|
||||
|
@ -63,7 +69,10 @@ func SetPusher(
|
|||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("SplitID failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
body := userapi.PerformPusherSetRequest{}
|
||||
if resErr := httputil.UnmarshalJSONRequest(req, &body); resErr != nil {
|
||||
|
@ -99,7 +108,10 @@ func SetPusher(
|
|||
err = userAPI.PerformPusherSet(req.Context(), &body, &struct{}{})
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("PerformPusherSet failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -111,6 +123,6 @@ func SetPusher(
|
|||
func invalidParam(msg string) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidParam(msg),
|
||||
JSON: spec.InvalidParam(msg),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,27 +7,30 @@ import (
|
|||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
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
|
||||
switch eerr.ErrCode {
|
||||
case "M_INVALID_ARGUMENT_VALUE":
|
||||
case spec.ErrorInvalidParam:
|
||||
status = http.StatusBadRequest
|
||||
case "M_NOT_FOUND":
|
||||
case spec.ErrorNotFound:
|
||||
status = http.StatusNotFound
|
||||
default:
|
||||
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...)
|
||||
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 {
|
||||
|
@ -48,7 +51,7 @@ func GetPushRulesByScope(ctx context.Context, scope string, device *userapi.Devi
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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{
|
||||
Code: http.StatusOK,
|
||||
|
@ -63,12 +66,12 @@ func GetPushRulesByKind(ctx context.Context, scope, kind string, device *userapi
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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))
|
||||
// Even if rulesPtr is not nil, there may not be any rules for this kind
|
||||
if rulesPtr == nil || (rulesPtr != nil && len(*rulesPtr) == 0) {
|
||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
|
@ -83,15 +86,15 @@ func GetPushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, device
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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))
|
||||
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)
|
||||
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{
|
||||
Code: http.StatusOK,
|
||||
|
@ -104,14 +107,14 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
|
|||
if err := json.NewDecoder(body).Decode(&newRule); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(err.Error()),
|
||||
JSON: spec.BadJSON(err.Error()),
|
||||
}
|
||||
}
|
||||
newRule.RuleID = ruleID
|
||||
|
||||
errs := pushrules.ValidateRule(pushrules.Kind(kind), &newRule)
|
||||
if len(errs) > 0 {
|
||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue(errs[0].Error()), "rule sanity check failed: %v", errs)
|
||||
return errorResponse(ctx, spec.InvalidParam(errs[0].Error()), "rule sanity check failed: %v", errs)
|
||||
}
|
||||
|
||||
ruleSets, err := userAPI.QueryPushRules(ctx, device.UserID)
|
||||
|
@ -120,12 +123,12 @@ func PutPushRuleByRuleID(ctx context.Context, scope, kind, ruleID, afterRuleID,
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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))
|
||||
if rulesPtr == nil {
|
||||
// while this should be impossible (ValidateRule would already return an error), better keep it around
|
||||
return errorResponse(ctx, jsonerror.InvalidArgumentValue("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||
return errorResponse(ctx, spec.InvalidParam("invalid push rules kind"), "pushRuleSetKindPointer failed")
|
||||
}
|
||||
i := pushRuleIndexByID(*rulesPtr, ruleID)
|
||||
if i >= 0 && afterRuleID == "" && beforeRuleID == "" {
|
||||
|
@ -172,15 +175,15 @@ func DeletePushRuleByRuleID(ctx context.Context, scope, kind, ruleID string, dev
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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))
|
||||
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)
|
||||
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:]...)
|
||||
|
@ -203,15 +206,15 @@ func GetPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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))
|
||||
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)
|
||||
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{
|
||||
Code: http.StatusOK,
|
||||
|
@ -226,7 +229,7 @@ func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
|
|||
if err := json.NewDecoder(body).Decode(&newPartialRule); err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(err.Error()),
|
||||
JSON: spec.BadJSON(err.Error()),
|
||||
}
|
||||
}
|
||||
if newPartialRule.Actions == nil {
|
||||
|
@ -249,15 +252,15 @@ func PutPushRuleAttrByRuleID(ctx context.Context, scope, kind, ruleID, attr stri
|
|||
}
|
||||
ruleSet := pushRuleSetByScope(ruleSets, pushrules.Scope(scope))
|
||||
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))
|
||||
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)
|
||||
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)) {
|
||||
|
@ -313,7 +316,7 @@ func pushRuleAttrGetter(attr string) (func(*pushrules.Rule) interface{}, error)
|
|||
case "enabled":
|
||||
return func(rule *pushrules.Rule) interface{} { return rule.Enabled }, nil
|
||||
default:
|
||||
return nil, jsonerror.InvalidArgumentValue("invalid push rule attribute")
|
||||
return nil, spec.InvalidParam("invalid push rule attribute")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,7 +327,7 @@ func pushRuleAttrSetter(attr string) (func(dest, src *pushrules.Rule), error) {
|
|||
case "enabled":
|
||||
return func(dest, src *pushrules.Rule) { dest.Enabled = src.Enabled }, nil
|
||||
default:
|
||||
return nil, jsonerror.InvalidArgumentValue("invalid push rule attribute")
|
||||
return nil, spec.InvalidParam("invalid push rule attribute")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,10 +341,10 @@ func findPushRuleInsertionIndex(rules []*pushrules.Rule, afterID, beforeID strin
|
|||
}
|
||||
}
|
||||
if i == len(rules) {
|
||||
return 0, jsonerror.NotFound("after: rule ID not found")
|
||||
return 0, spec.NotFound("after: rule ID not found")
|
||||
}
|
||||
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
|
||||
// not-found from is-last-entry. Now we move to the earliest
|
||||
|
@ -356,10 +359,10 @@ func findPushRuleInsertionIndex(rules []*pushrules.Rule, afterID, beforeID strin
|
|||
}
|
||||
}
|
||||
if i == len(rules) {
|
||||
return 0, jsonerror.NotFound("before: rule ID not found")
|
||||
return 0, spec.NotFound("before: rule ID not found")
|
||||
}
|
||||
if rules[i].Default {
|
||||
return 0, jsonerror.NotFound("before: rule ID must not be a default rule")
|
||||
return 0, spec.NotFound("before: rule ID must not be a default rule")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
|
@ -49,7 +48,10 @@ func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *prod
|
|||
case "m.fully_read":
|
||||
data, err := json.Marshal(fullyReadEvent{EventID: eventID})
|
||||
if err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
dataReq := api.InputAccountDataRequest{
|
||||
|
|
|
@ -16,6 +16,7 @@ package routing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -24,10 +25,10 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
|
||||
"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/transactions"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
@ -46,11 +47,43 @@ func SendRedaction(
|
|||
txnID *string,
|
||||
txnCache *transactions.Cache,
|
||||
) 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 {
|
||||
return *resErr
|
||||
}
|
||||
|
||||
// if user is member of room, and sender ID is nil, then this user doesn't have a pseudo ID for some reason,
|
||||
// which is unexpected.
|
||||
if senderID == nil {
|
||||
util.GetLogger(req.Context()).WithField("userID", *deviceUserID).WithField("roomID", roomID).Error("missing sender ID for user, despite having membership")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
|
||||
if txnID != nil {
|
||||
// Try to fetch response from transactionsCache
|
||||
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
||||
|
@ -62,20 +95,20 @@ func SendRedaction(
|
|||
if ev == nil {
|
||||
return util.JSONResponse{
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
// 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
|
||||
allowedToRedact := ev.Sender() == device.UserID
|
||||
allowedToRedact := ev.SenderID() == *senderID
|
||||
if !allowedToRedact {
|
||||
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||
EventType: spec.MRoomPowerLevels,
|
||||
|
@ -84,24 +117,24 @@ func SendRedaction(
|
|||
if plEvent == nil {
|
||||
return util.JSONResponse{
|
||||
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()
|
||||
if err != nil {
|
||||
pl, plErr := plEvent.PowerLevels()
|
||||
if plErr != nil {
|
||||
return util.JSONResponse{
|
||||
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.",
|
||||
),
|
||||
}
|
||||
}
|
||||
allowedToRedact = pl.UserLevel(device.UserID) >= pl.Redact
|
||||
allowedToRedact = pl.UserLevel(*senderID) >= pl.Redact
|
||||
}
|
||||
if !allowedToRedact {
|
||||
return util.JSONResponse{
|
||||
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."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,35 +145,44 @@ func SendRedaction(
|
|||
}
|
||||
|
||||
// create the new event and set all the fields we can
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: device.UserID,
|
||||
RoomID: roomID,
|
||||
Type: spec.MRoomRedaction,
|
||||
Redacts: eventID,
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
SenderID: string(*senderID),
|
||||
RoomID: roomID,
|
||||
Type: spec.MRoomRedaction,
|
||||
Redacts: eventID,
|
||||
}
|
||||
err := builder.SetContent(r)
|
||||
err = proto.SetContent(r)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("builder.SetContent failed")
|
||||
return jsonerror.InternalServerError()
|
||||
util.GetLogger(req.Context()).WithError(err).Error("proto.SetContent failed")
|
||||
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 {
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
||||
e, err := eventutil.QueryAndBuildEvent(req.Context(), &builder, cfg.Matrix, identity, time.Now(), rsAPI, &queryRes)
|
||||
if err == eventutil.ErrRoomNoExists {
|
||||
e, err := eventutil.QueryAndBuildEvent(req.Context(), &proto, &identity, time.Now(), rsAPI, &queryRes)
|
||||
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
JSON: spec.NotFound("Room does not exist"),
|
||||
}
|
||||
}
|
||||
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")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
res := util.JSONResponse{
|
||||
|
|
|
@ -46,7 +46,6 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
@ -165,7 +164,7 @@ func (d *sessionsDict) addCompletedSessionStage(sessionID string, stage authtype
|
|||
return
|
||||
}
|
||||
}
|
||||
d.sessions[sessionID] = append(sessions.sessions[sessionID], stage)
|
||||
d.sessions[sessionID] = append(d.sessions[sessionID], stage)
|
||||
}
|
||||
|
||||
func (d *sessionsDict) addDeviceToDelete(sessionID, deviceID string) {
|
||||
|
@ -428,7 +427,7 @@ func validateApplicationService(
|
|||
if matchedApplicationService == nil {
|
||||
return "", &util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.UnknownToken("Supplied access_token does not match any known application service"),
|
||||
JSON: spec.UnknownToken("Supplied access_token does not match any known application service"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,7 +438,7 @@ func validateApplicationService(
|
|||
// If we didn't find any matches, return M_EXCLUSIVE
|
||||
return "", &util.JSONResponse{
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
@ -448,7 +447,7 @@ func validateApplicationService(
|
|||
if UsernameMatchesMultipleExclusiveNamespaces(cfg, userID) {
|
||||
return "", &util.JSONResponse{
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
@ -474,7 +473,7 @@ func Register(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.NotJSON("Unable to read request body"),
|
||||
JSON: spec.NotJSON("Unable to read request body"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,7 +517,7 @@ func Register(
|
|||
if _, err = strconv.ParseInt(r.Username, 10, 64); err == nil {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
|
@ -529,7 +528,10 @@ func Register(
|
|||
nres := &userapi.QueryNumericLocalpartResponse{}
|
||||
if err = userAPI.QueryNumericLocalpart(req.Context(), nreq, nres); err != nil {
|
||||
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)
|
||||
}
|
||||
|
@ -552,7 +554,7 @@ func Register(
|
|||
// type is not known or specified)
|
||||
return util.JSONResponse{
|
||||
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:
|
||||
// Spec-compliant case (neither the access_token nor the login type are
|
||||
|
@ -590,7 +592,7 @@ func handleGuestRegistration(
|
|||
if !registrationEnabled || !guestsEnabled {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(
|
||||
JSON: spec.Forbidden(
|
||||
fmt.Sprintf("Guest registration is disabled on %q", r.ServerName),
|
||||
),
|
||||
}
|
||||
|
@ -604,7 +606,7 @@ func handleGuestRegistration(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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{
|
||||
|
@ -616,7 +618,7 @@ func handleGuestRegistration(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
|
@ -632,7 +634,7 @@ func handleGuestRegistration(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
||||
JSON: spec.Unknown("failed to create device: " + err.Error()),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
@ -682,7 +684,7 @@ func handleRegistrationFlow(
|
|||
if !registrationEnabled && r.Auth.Type != authtypes.LoginTypeSharedSecret {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(
|
||||
JSON: spec.Forbidden(
|
||||
fmt.Sprintf("Registration is disabled on %q", r.ServerName),
|
||||
),
|
||||
}
|
||||
|
@ -696,7 +698,7 @@ func handleRegistrationFlow(
|
|||
UsernameMatchesExclusiveNamespaces(cfg, r.Username) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.ASExclusive("This username is reserved by an application service."),
|
||||
JSON: spec.ASExclusive("This username is reserved by an application service."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -706,15 +708,15 @@ func handleRegistrationFlow(
|
|||
err := validateRecaptcha(cfg, r.Auth.Response, req.RemoteAddr)
|
||||
switch err {
|
||||
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:
|
||||
return util.JSONResponse{Code: http.StatusBadRequest, JSON: jsonerror.BadJSON(err.Error())}
|
||||
return util.JSONResponse{Code: http.StatusBadRequest, JSON: spec.BadJSON(err.Error())}
|
||||
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:
|
||||
default:
|
||||
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
|
||||
|
@ -732,7 +734,7 @@ func handleRegistrationFlow(
|
|||
default:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotImplemented,
|
||||
JSON: jsonerror.Unknown("unknown/unimplemented auth type"),
|
||||
JSON: spec.Unknown("unknown/unimplemented auth type"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -764,7 +766,7 @@ func handleApplicationServiceRegistration(
|
|||
if tokenErr != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusUnauthorized,
|
||||
JSON: jsonerror.MissingToken(tokenErr.Error()),
|
||||
JSON: spec.MissingToken(tokenErr.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -834,14 +836,14 @@ func completeRegistration(
|
|||
if username == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("Missing username"),
|
||||
JSON: spec.MissingParam("Missing username"),
|
||||
}
|
||||
}
|
||||
// Blank passwords are only allowed by registered application services
|
||||
if password == "" && appserviceID == "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MissingArgument("Missing password"),
|
||||
JSON: spec.MissingParam("Missing password"),
|
||||
}
|
||||
}
|
||||
var accRes userapi.PerformAccountCreationResponse
|
||||
|
@ -857,12 +859,12 @@ func completeRegistration(
|
|||
if _, ok := err.(*userapi.ErrorConflict); ok { // user already exists
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UserInUse("Desired user ID is already taken."),
|
||||
JSON: spec.UserInUse("Desired user ID is already taken."),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("failed to create account: " + err.Error()),
|
||||
JSON: spec.Unknown("failed to create account: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -884,7 +886,7 @@ func completeRegistration(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("Failed to generate access token"),
|
||||
JSON: spec.Unknown("Failed to generate access token"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -893,7 +895,7 @@ func completeRegistration(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("failed to set display name: " + err.Error()),
|
||||
JSON: spec.Unknown("failed to set display name: " + err.Error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -911,7 +913,7 @@ func completeRegistration(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
||||
JSON: spec.Unknown("failed to create device: " + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1006,7 +1008,7 @@ func RegisterAvailable(
|
|||
if v.ServerName == domain && !v.AllowRegistration {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(
|
||||
JSON: spec.Forbidden(
|
||||
fmt.Sprintf("Registration is not allowed on %q", string(v.ServerName)),
|
||||
),
|
||||
}
|
||||
|
@ -1023,7 +1025,7 @@ func RegisterAvailable(
|
|||
if appservice.OwnsNamespaceCoveringUserId(userID) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UserInUse("Desired user ID is reserved by an application service."),
|
||||
JSON: spec.UserInUse("Desired user ID is reserved by an application service."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1036,14 +1038,14 @@ func RegisterAvailable(
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: jsonerror.Unknown("failed to check availability:" + err.Error()),
|
||||
JSON: spec.Unknown("failed to check availability:" + err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
if !res.Available {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UserInUse("Desired User ID is already taken."),
|
||||
JSON: spec.UserInUse("Desired User ID is already taken."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1060,7 +1062,7 @@ func handleSharedSecretRegistration(cfg *config.ClientAPI, userAPI userapi.Clien
|
|||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
|
@ -1070,7 +1072,7 @@ func handleSharedSecretRegistration(cfg *config.ClientAPI, userAPI userapi.Clien
|
|||
if !valid {
|
||||
return util.JSONResponse{
|
||||
Code: 403,
|
||||
JSON: jsonerror.Forbidden("bad mac"),
|
||||
JSON: spec.Forbidden("bad mac"),
|
||||
}
|
||||
}
|
||||
// downcase capitals
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
"time"
|
||||
|
||||
"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/caching"
|
||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||
|
@ -39,6 +38,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/test/testrig"
|
||||
"github.com/matrix-org/dendrite/userapi"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -306,7 +306,7 @@ func Test_register(t *testing.T) {
|
|||
guestsDisabled: true,
|
||||
wantResponse: util.JSONResponse{
|
||||
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",
|
||||
wantResponse: util.JSONResponse{
|
||||
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,
|
||||
wantResponse: util.JSONResponse{
|
||||
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",
|
||||
wantResponse: util.JSONResponse{
|
||||
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",
|
||||
wantResponse: util.JSONResponse{
|
||||
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,
|
||||
wantResponse: util.JSONResponse{
|
||||
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,
|
||||
wantResponse: util.JSONResponse{
|
||||
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`,
|
||||
wantResponse: util.JSONResponse{
|
||||
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,
|
||||
loginType: authtypes.LoginTypeRecaptcha,
|
||||
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{}},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -415,6 +415,7 @@ func Test_register(t *testing.T) {
|
|||
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@ -484,7 +485,7 @@ func Test_register(t *testing.T) {
|
|||
if !reflect.DeepEqual(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) {
|
||||
t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantResponse)
|
||||
}
|
||||
|
@ -541,7 +542,12 @@ func Test_register(t *testing.T) {
|
|||
resp = Register(req, userAPI, &cfg.ClientAPI)
|
||||
|
||||
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) {
|
||||
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
||||
}
|
||||
|
@ -589,6 +595,7 @@ func TestRegisterUserWithDisplayName(t *testing.T) {
|
|||
natsInstance := jetstream.NATSInstance{}
|
||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
deviceName, deviceID := "deviceName", "deviceID"
|
||||
expectedDisplayName := "DisplayName"
|
||||
|
@ -629,6 +636,7 @@ func TestRegisterAdminUsingSharedSecret(t *testing.T) {
|
|||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||
rsAPI.SetFederationAPI(nil, nil)
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||
|
||||
expectedDisplayName := "rabbit"
|
||||
|
|
180
clientapi/routing/room_hierarchy.go
Normal file
180
clientapi/routing/room_hierarchy.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// 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 routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// For storing pagination information for room hierarchies
|
||||
type RoomHierarchyPaginationCache struct {
|
||||
cache map[string]roomserverAPI.RoomHierarchyWalker
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new, empty, pagination cache.
|
||||
func NewRoomHierarchyPaginationCache() RoomHierarchyPaginationCache {
|
||||
return RoomHierarchyPaginationCache{
|
||||
cache: map[string]roomserverAPI.RoomHierarchyWalker{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get a cached page, or nil if there is no associated page in the cache.
|
||||
func (c *RoomHierarchyPaginationCache) Get(token string) *roomserverAPI.RoomHierarchyWalker {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
line, ok := c.cache[token]
|
||||
if ok {
|
||||
return &line
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add a cache line to the pagination cache.
|
||||
func (c *RoomHierarchyPaginationCache) AddLine(line roomserverAPI.RoomHierarchyWalker) string {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
token := uuid.NewString()
|
||||
c.cache[token] = line
|
||||
return token
|
||||
}
|
||||
|
||||
// Query the hierarchy of a room/space
|
||||
//
|
||||
// Implements /_matrix/client/v1/rooms/{roomID}/hierarchy
|
||||
func QueryRoomHierarchy(req *http.Request, device *userapi.Device, roomIDStr string, rsAPI roomserverAPI.ClientRoomserverAPI, paginationCache *RoomHierarchyPaginationCache) util.JSONResponse {
|
||||
parsedRoomID, err := spec.NewRoomID(roomIDStr)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: spec.InvalidParam("room is unknown/forbidden"),
|
||||
}
|
||||
}
|
||||
roomID := *parsedRoomID
|
||||
|
||||
suggestedOnly := false // Defaults to false (spec-defined)
|
||||
switch req.URL.Query().Get("suggested_only") {
|
||||
case "true":
|
||||
suggestedOnly = true
|
||||
case "false":
|
||||
case "": // Empty string is returned when query param is not set
|
||||
default:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("query parameter 'suggested_only', if set, must be 'true' or 'false'"),
|
||||
}
|
||||
}
|
||||
|
||||
limit := 1000 // Default to 1000
|
||||
limitStr := req.URL.Query().Get("limit")
|
||||
if limitStr != "" {
|
||||
var maybeLimit int
|
||||
maybeLimit, err = strconv.Atoi(limitStr)
|
||||
if err != nil || maybeLimit < 0 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("query parameter 'limit', if set, must be a positive integer"),
|
||||
}
|
||||
}
|
||||
limit = maybeLimit
|
||||
if limit > 1000 {
|
||||
limit = 1000 // Maximum limit of 1000
|
||||
}
|
||||
}
|
||||
|
||||
maxDepth := -1 // '-1' representing no maximum depth
|
||||
maxDepthStr := req.URL.Query().Get("max_depth")
|
||||
if maxDepthStr != "" {
|
||||
var maybeMaxDepth int
|
||||
maybeMaxDepth, err = strconv.Atoi(maxDepthStr)
|
||||
if err != nil || maybeMaxDepth < 0 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("query parameter 'max_depth', if set, must be a positive integer"),
|
||||
}
|
||||
}
|
||||
maxDepth = maybeMaxDepth
|
||||
}
|
||||
|
||||
from := req.URL.Query().Get("from")
|
||||
|
||||
var walker roomserverAPI.RoomHierarchyWalker
|
||||
if from == "" { // No pagination token provided, so start new hierarchy walker
|
||||
walker = roomserverAPI.NewRoomHierarchyWalker(types.NewDeviceNotServerName(*device), roomID, suggestedOnly, maxDepth)
|
||||
} else { // Attempt to resume cached walker
|
||||
cachedWalker := paginationCache.Get(from)
|
||||
|
||||
if cachedWalker == nil || cachedWalker.SuggestedOnly != suggestedOnly || cachedWalker.MaxDepth != maxDepth {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("pagination not found for provided token ('from') with given 'max_depth', 'suggested_only' and room ID"),
|
||||
}
|
||||
}
|
||||
|
||||
walker = *cachedWalker
|
||||
}
|
||||
|
||||
discoveredRooms, nextWalker, err := rsAPI.QueryNextRoomHierarchyPage(req.Context(), walker, limit)
|
||||
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case roomserverAPI.ErrRoomUnknownOrNotAllowed:
|
||||
util.GetLogger(req.Context()).WithError(err).Debugln("room unknown/forbidden when handling CS room hierarchy request")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden("room is unknown/forbidden"),
|
||||
}
|
||||
default:
|
||||
log.WithError(err).Errorf("failed to fetch next page of room hierarchy (CS API)")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextBatch := ""
|
||||
// nextWalker will be nil if there's no more rooms left to walk
|
||||
if nextWalker != nil {
|
||||
nextBatch = paginationCache.AddLine(*nextWalker)
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: RoomHierarchyClientResponse{
|
||||
Rooms: discoveredRooms,
|
||||
NextBatch: nextBatch,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Success response for /_matrix/client/v1/rooms/{roomID}/hierarchy
|
||||
type RoomHierarchyClientResponse struct {
|
||||
Rooms []fclient.RoomHierarchyRoom `json:"rooms"`
|
||||
NextBatch string `json:"next_batch,omitempty"`
|
||||
}
|
|
@ -19,10 +19,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/userapi/api"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -39,14 +39,17 @@ func GetTags(
|
|||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("obtainSavedTags failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -71,7 +74,7 @@ func PutTag(
|
|||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
|
@ -93,7 +99,10 @@ func PutTag(
|
|||
|
||||
if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -118,14 +127,17 @@ func DeleteTag(
|
|||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
|
@ -141,7 +153,10 @@ func DeleteTag(
|
|||
|
||||
if err = saveTagData(req, userID, roomID, userAPI, tagContent); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("saveTagData failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -20,20 +20,21 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/matrix-org/dendrite/setup/base"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"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"
|
||||
"github.com/matrix-org/dendrite/clientapi/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||
clientutil "github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||
"github.com/matrix-org/dendrite/internal/httputil"
|
||||
|
@ -43,6 +44,19 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
)
|
||||
|
||||
type WellKnownClientHomeserver struct {
|
||||
BaseUrl string `json:"base_url"`
|
||||
}
|
||||
|
||||
type WellKnownSlidingSyncProxy struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type WellKnownClientResponse struct {
|
||||
Homeserver WellKnownClientHomeserver `json:"m.homeserver"`
|
||||
SlidingSyncProxy *WellKnownSlidingSyncProxy `json:"org.matrix.msc3575.proxy,omitempty"`
|
||||
}
|
||||
|
||||
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
||||
// to clients which need to make outbound HTTP requests.
|
||||
//
|
||||
|
@ -56,7 +70,7 @@ func Setup(
|
|||
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||
userAPI userapi.ClientUserAPI,
|
||||
userDirectoryProvider userapi.QuerySearchProfilesAPI,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
syncProducer *producers.SyncAPIProducer,
|
||||
transactionsCache *transactions.Cache,
|
||||
federationSender federationAPI.ClientFederationAPI,
|
||||
|
@ -85,22 +99,32 @@ func Setup(
|
|||
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 != "" {
|
||||
logrus.Infof("Setting m.homeserver base_url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownClientName)
|
||||
if cfg.Matrix.WellKnownSlidingSyncProxy != "" {
|
||||
logrus.Infof("Setting org.matrix.msc3575.proxy url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownSlidingSyncProxy)
|
||||
}
|
||||
wkMux.Handle("/client", httputil.MakeExternalAPI("wellknown", func(r *http.Request) util.JSONResponse {
|
||||
response := WellKnownClientResponse{
|
||||
Homeserver: WellKnownClientHomeserver{cfg.Matrix.WellKnownClientName},
|
||||
}
|
||||
if cfg.Matrix.WellKnownSlidingSyncProxy != "" {
|
||||
response.SlidingSyncProxy = &WellKnownSlidingSyncProxy{
|
||||
Url: cfg.Matrix.WellKnownSlidingSyncProxy,
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: struct {
|
||||
HomeserverName struct {
|
||||
BaseUrl string `json:"base_url"`
|
||||
} `json:"m.homeserver"`
|
||||
}{
|
||||
HomeserverName: struct {
|
||||
BaseUrl string `json:"base_url"`
|
||||
}{
|
||||
BaseUrl: cfg.Matrix.WellKnownClientName,
|
||||
},
|
||||
},
|
||||
JSON: response,
|
||||
}
|
||||
})).Methods(http.MethodGet, http.MethodOptions)
|
||||
}
|
||||
|
@ -148,11 +172,41 @@ func Setup(
|
|||
}
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusMethodNotAllowed,
|
||||
JSON: jsonerror.NotFound("unknown method"),
|
||||
JSON: spec.NotFound("unknown method"),
|
||||
}
|
||||
}),
|
||||
).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}",
|
||||
httputil.MakeAdminAPI("admin_evacuate_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
|
@ -162,13 +216,13 @@ func Setup(
|
|||
|
||||
dendriteAdminRouter.Handle("/admin/evacuateUser/{userID}",
|
||||
httputil.MakeAdminAPI("admin_evacuate_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return AdminEvacuateUser(req, cfg, rsAPI)
|
||||
return AdminEvacuateUser(req, rsAPI)
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
dendriteAdminRouter.Handle("/admin/purgeRoom/{roomID}",
|
||||
httputil.MakeAdminAPI("admin_purge_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return AdminPurgeRoom(req, cfg, device, rsAPI)
|
||||
return AdminPurgeRoom(req, rsAPI)
|
||||
}),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
|
@ -180,7 +234,7 @@ func Setup(
|
|||
|
||||
dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}",
|
||||
httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return AdminDownloadState(req, cfg, device, rsAPI)
|
||||
return AdminDownloadState(req, device, rsAPI)
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
||||
|
@ -249,6 +303,8 @@ func Setup(
|
|||
// Note that 'apiversion' is chosen because it must not collide with a variable used in any of the routing!
|
||||
v3mux := publicAPIMux.PathPrefix("/{apiversion:(?:r0|v3)}/").Subrouter()
|
||||
|
||||
v1mux := publicAPIMux.PathPrefix("/v1/").Subrouter()
|
||||
|
||||
unstableMux := publicAPIMux.PathPrefix("/unstable").Subrouter()
|
||||
|
||||
v3mux.Handle("/createRoom",
|
||||
|
@ -265,9 +321,17 @@ func Setup(
|
|||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return JoinRoomByIDOrAlias(
|
||||
req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
|
||||
)
|
||||
// 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(
|
||||
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()),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
|
@ -301,9 +365,17 @@ func Setup(
|
|||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return JoinRoomByIDOrAlias(
|
||||
req, device, rsAPI, userAPI, vars["roomID"],
|
||||
)
|
||||
// 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(
|
||||
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()),
|
||||
).Methods(http.MethodPost, http.MethodOptions)
|
||||
v3mux.Handle("/rooms/{roomID}/leave",
|
||||
|
@ -450,6 +522,19 @@ func Setup(
|
|||
}, httputil.WithAllowGuests()),
|
||||
).Methods(http.MethodPut, http.MethodOptions)
|
||||
|
||||
// Defined outside of handler to persist between calls
|
||||
// TODO: clear based on some criteria
|
||||
roomHierarchyPaginationCache := NewRoomHierarchyPaginationCache()
|
||||
v1mux.Handle("/rooms/{roomID}/hierarchy",
|
||||
httputil.MakeAuthAPI("spaces", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
return QueryRoomHierarchy(req, device, vars["roomID"], rsAPI, &roomHierarchyPaginationCache)
|
||||
}, httputil.WithAllowGuests()),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
||||
v3mux.Handle("/register", httputil.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
|
||||
if r := rateLimits.Limit(req, nil); r != nil {
|
||||
return *r
|
||||
|
@ -659,7 +744,7 @@ func Setup(
|
|||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue("missing trailing slash"),
|
||||
JSON: spec.InvalidParam("missing trailing slash"),
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
@ -674,7 +759,7 @@ func Setup(
|
|||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
|
@ -693,7 +778,7 @@ func Setup(
|
|||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue("missing trailing slash after scope"),
|
||||
JSON: spec.InvalidParam("missing trailing slash after scope"),
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
@ -702,7 +787,7 @@ func Setup(
|
|||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
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)
|
||||
|
@ -721,7 +806,7 @@ func Setup(
|
|||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue("missing trailing slash after kind"),
|
||||
JSON: spec.InvalidParam("missing trailing slash after kind"),
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
@ -730,7 +815,7 @@ func Setup(
|
|||
httputil.MakeAuthAPI("push_rules", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidArgumentValue("rule ID must be specified"),
|
||||
JSON: spec.InvalidParam("rule ID must be specified"),
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodPut)
|
||||
|
@ -939,7 +1024,7 @@ func Setup(
|
|||
// TODO: Allow people to peek into rooms.
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.GuestAccessForbidden("Guest access not implemented"),
|
||||
JSON: spec.GuestAccessForbidden("Guest access not implemented"),
|
||||
}
|
||||
}),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
@ -1186,7 +1271,7 @@ func Setup(
|
|||
if r := rateLimits.Limit(req, device); r != nil {
|
||||
return *r
|
||||
}
|
||||
return GetCapabilities()
|
||||
return GetCapabilities(rsAPI)
|
||||
}, httputil.WithAllowGuests()),
|
||||
).Methods(http.MethodGet, http.MethodOptions)
|
||||
|
||||
|
@ -1244,7 +1329,7 @@ func Setup(
|
|||
if version == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue("version must be specified"),
|
||||
JSON: spec.InvalidParam("version must be specified"),
|
||||
}
|
||||
}
|
||||
var reqBody keyBackupSessionRequest
|
||||
|
@ -1265,7 +1350,7 @@ func Setup(
|
|||
if version == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue("version must be specified"),
|
||||
JSON: spec.InvalidParam("version must be specified"),
|
||||
}
|
||||
}
|
||||
roomID := vars["roomID"]
|
||||
|
@ -1297,7 +1382,7 @@ func Setup(
|
|||
if version == "" {
|
||||
return util.JSONResponse{
|
||||
Code: 400,
|
||||
JSON: jsonerror.InvalidArgumentValue("version must be specified"),
|
||||
JSON: spec.InvalidParam("version must be specified"),
|
||||
}
|
||||
}
|
||||
var reqBody userapi.KeyBackupSession
|
||||
|
|
|
@ -23,19 +23,19 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/internal/transactions"
|
||||
"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/syncapi/synctypes"
|
||||
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"
|
||||
|
||||
"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/transactions"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
)
|
||||
|
||||
// http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||
|
@ -68,6 +68,8 @@ var sendEventDuration = prometheus.NewHistogramVec(
|
|||
// /rooms/{roomID}/send/{eventType}
|
||||
// /rooms/{roomID}/send/{eventType}/{txnID}
|
||||
// /rooms/{roomID}/state/{eventType}/{stateKey}
|
||||
//
|
||||
// nolint: gocyclo
|
||||
func SendEvent(
|
||||
req *http.Request,
|
||||
device *userapi.Device,
|
||||
|
@ -76,12 +78,11 @@ func SendEvent(
|
|||
rsAPI api.ClientRoomserverAPI,
|
||||
txnCache *transactions.Cache,
|
||||
) util.JSONResponse {
|
||||
verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID}
|
||||
verRes := api.QueryRoomVersionForRoomResponse{}
|
||||
if err := rsAPI.QueryRoomVersionForRoom(req.Context(), &verReq, &verRes); err != nil {
|
||||
roomVersion, err := rsAPI.QueryRoomVersionForRoom(req.Context(), roomID)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.UnsupportedRoomVersion(err.Error()),
|
||||
JSON: spec.UnsupportedRoomVersion(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +93,30 @@ func SendEvent(
|
|||
}
|
||||
}
|
||||
|
||||
// Translate user ID state keys to room keys in pseudo ID rooms
|
||||
if roomVersion == gomatrixserverlib.RoomVersionPseudoIDs && stateKey != nil {
|
||||
parsedRoomID, innerErr := spec.NewRoomID(roomID)
|
||||
if innerErr != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("invalid room ID"),
|
||||
}
|
||||
}
|
||||
|
||||
newStateKey, innerErr := synctypes.FromClientStateKey(*parsedRoomID, *stateKey, func(roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
|
||||
return rsAPI.QuerySenderIDForUser(req.Context(), roomID, userID)
|
||||
})
|
||||
if innerErr != nil {
|
||||
// TODO: work out better logic for failure cases (e.g. sender ID not found)
|
||||
util.GetLogger(req.Context()).WithError(innerErr).Error("synctypes.FromClientStateKey failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
stateKey = newStateKey
|
||||
}
|
||||
|
||||
// create a mutex for the specific user in the specific room
|
||||
// this avoids a situation where events that are received in quick succession are sent to the roomserver in a jumbled order
|
||||
userID := device.UserID
|
||||
|
@ -122,15 +147,26 @@ func SendEvent(
|
|||
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)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
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 {
|
||||
return *resErr
|
||||
}
|
||||
|
@ -145,12 +181,15 @@ func SendEvent(
|
|||
if !aliasReq.Valid() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.InvalidParam("Request contains invalid aliases."),
|
||||
JSON: spec.InvalidParam("Request contains invalid aliases."),
|
||||
}
|
||||
}
|
||||
aliasRes := &api.GetAliasesForRoomIDResponse{}
|
||||
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
|
||||
requestAliases := append(aliasReq.AltAliases, aliasReq.Alias)
|
||||
|
@ -165,7 +204,7 @@ func SendEvent(
|
|||
if aliasReq.Alias != "" && found < len(requestAliases) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadAlias("No matching alias found."),
|
||||
JSON: spec.BadAlias("No matching alias found."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,8 +223,8 @@ func SendEvent(
|
|||
if err := api.SendEvents(
|
||||
req.Context(), rsAPI,
|
||||
api.KindNew,
|
||||
[]*gomatrixserverlib.HeaderedEvent{
|
||||
e.Headered(verRes.RoomVersion),
|
||||
[]*types.HeaderedEvent{
|
||||
&types.HeaderedEvent{PDU: e},
|
||||
},
|
||||
device.UserDomain(),
|
||||
domain,
|
||||
|
@ -194,13 +233,16 @@ func SendEvent(
|
|||
false,
|
||||
); err != nil {
|
||||
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)
|
||||
util.GetLogger(req.Context()).WithFields(logrus.Fields{
|
||||
"event_id": e.EventID(),
|
||||
"room_id": roomID,
|
||||
"room_version": verRes.RoomVersion,
|
||||
"room_version": roomVersion,
|
||||
}).Info("Sent event to roomserver")
|
||||
|
||||
res := util.JSONResponse{
|
||||
|
@ -220,6 +262,30 @@ func SendEvent(
|
|||
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
|
||||
} else if senderID == nil {
|
||||
return fmt.Errorf("sender ID not found for %s in %s", uID, *validRoomID)
|
||||
}
|
||||
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
|
||||
// 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 {
|
||||
|
@ -256,72 +322,108 @@ func generateSendEvent(
|
|||
r map[string]interface{},
|
||||
device *userapi.Device,
|
||||
roomID, eventType string, stateKey *string,
|
||||
cfg *config.ClientAPI,
|
||||
rsAPI api.ClientRoomserverAPI,
|
||||
evTime time.Time,
|
||||
) (*gomatrixserverlib.Event, *util.JSONResponse) {
|
||||
) (gomatrixserverlib.PDU, *util.JSONResponse) {
|
||||
// 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.StatusInternalServerError,
|
||||
JSON: spec.NotFound("internal server error"),
|
||||
}
|
||||
} else if senderID == nil {
|
||||
// TODO: is it always the case that lack of a sender ID means they're not joined?
|
||||
// And should this logic be deferred to the roomserver somehow?
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden("not joined to room"),
|
||||
}
|
||||
}
|
||||
|
||||
// create the new event and set all the fields we can
|
||||
builder := gomatrixserverlib.EventBuilder{
|
||||
Sender: userID,
|
||||
proto := gomatrixserverlib.ProtoEvent{
|
||||
SenderID: string(*senderID),
|
||||
RoomID: roomID,
|
||||
Type: eventType,
|
||||
StateKey: stateKey,
|
||||
}
|
||||
err := builder.SetContent(r)
|
||||
err = proto.SetContent(r)
|
||||
if err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return nil, &resErr
|
||||
util.GetLogger(ctx).WithError(err).Error("proto.SetContent failed")
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
||||
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *fullUserID)
|
||||
if err != nil {
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return nil, &resErr
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
var queryRes api.QueryLatestEventsAndStateResponse
|
||||
e, err := eventutil.QueryAndBuildEvent(ctx, &builder, cfg.Matrix, identity, evTime, rsAPI, &queryRes)
|
||||
if err == eventutil.ErrRoomNoExists {
|
||||
e, err := eventutil.QueryAndBuildEvent(ctx, &proto, &identity, evTime, rsAPI, &queryRes)
|
||||
switch specificErr := err.(type) {
|
||||
case nil:
|
||||
case eventutil.ErrRoomNoExists:
|
||||
return nil, &util.JSONResponse{
|
||||
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{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
JSON: spec.BadJSON(specificErr.Error()),
|
||||
}
|
||||
} else if e, ok := err.(gomatrixserverlib.EventValidationError); ok {
|
||||
if e.Code == gomatrixserverlib.EventValidationTooLarge {
|
||||
case gomatrixserverlib.EventValidationError:
|
||||
if specificErr.Code == gomatrixserverlib.EventValidationTooLarge {
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusRequestEntityTooLarge,
|
||||
JSON: jsonerror.BadJSON(e.Error()),
|
||||
JSON: spec.BadJSON(specificErr.Error()),
|
||||
}
|
||||
}
|
||||
return nil, &util.JSONResponse{
|
||||
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")
|
||||
resErr := jsonerror.InternalServerError()
|
||||
return nil, &resErr
|
||||
return nil, &util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
stateEvents[i] = queryRes.StateEvents[i].Event
|
||||
stateEvents[i] = queryRes.StateEvents[i].PDU
|
||||
}
|
||||
provider := gomatrixserverlib.NewAuthEvents(stateEvents)
|
||||
if err = gomatrixserverlib.Allowed(e.Event, &provider); err != nil {
|
||||
provider := gomatrixserverlib.NewAuthEvents(gomatrixserverlib.ToPDUs(stateEvents))
|
||||
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{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
||||
JSON: spec.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,16 +434,16 @@ func generateSendEvent(
|
|||
util.GetLogger(ctx).WithError(err).Error("Cannot unmarshal the event content.")
|
||||
return nil, &util.JSONResponse{
|
||||
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() {
|
||||
return nil, &util.JSONResponse{
|
||||
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
|
||||
}
|
||||
|
|
275
clientapi/routing/sendevent_test.go
Normal file
275
clientapi/routing/sendevent_test.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
rsapi "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// Mock roomserver API for testing
|
||||
//
|
||||
// Currently pretty specialised for the pseudo ID test, so will need
|
||||
// editing if future (other) sendevent tests are using this.
|
||||
type sendEventTestRoomserverAPI struct {
|
||||
rsapi.ClientRoomserverAPI
|
||||
t *testing.T
|
||||
roomIDStr string
|
||||
roomVersion gomatrixserverlib.RoomVersion
|
||||
roomState []*types.HeaderedEvent
|
||||
|
||||
// userID -> room key
|
||||
senderMapping map[string]ed25519.PrivateKey
|
||||
|
||||
savedInputRoomEvents []rsapi.InputRoomEvent
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) QueryRoomVersionForRoom(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) {
|
||||
if roomID == s.roomIDStr {
|
||||
return s.roomVersion, nil
|
||||
} else {
|
||||
s.t.Logf("room version queried for %s", roomID)
|
||||
return "", fmt.Errorf("unknown room")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) QueryCurrentState(ctx context.Context, req *rsapi.QueryCurrentStateRequest, res *rsapi.QueryCurrentStateResponse) error {
|
||||
res.StateEvents = map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{}
|
||||
for _, stateKeyTuple := range req.StateTuples {
|
||||
for _, stateEv := range s.roomState {
|
||||
if stateEv.Type() == stateKeyTuple.EventType && stateEv.StateKey() != nil && *stateEv.StateKey() == stateKeyTuple.StateKey {
|
||||
res.StateEvents[stateKeyTuple] = stateEv
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) QueryLatestEventsAndState(ctx context.Context, req *rsapi.QueryLatestEventsAndStateRequest, res *rsapi.QueryLatestEventsAndStateResponse) error {
|
||||
if req.RoomID == s.roomIDStr {
|
||||
res.RoomExists = true
|
||||
res.RoomVersion = s.roomVersion
|
||||
|
||||
res.StateEvents = make([]*types.HeaderedEvent, len(s.roomState))
|
||||
copy(res.StateEvents, s.roomState)
|
||||
|
||||
res.LatestEvents = []string{}
|
||||
res.Depth = 1
|
||||
return nil
|
||||
} else {
|
||||
s.t.Logf("room event/state queried for %s", req.RoomID)
|
||||
return fmt.Errorf("unknown room")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) QuerySenderIDForUser(
|
||||
ctx context.Context,
|
||||
roomID spec.RoomID,
|
||||
userID spec.UserID,
|
||||
) (*spec.SenderID, error) {
|
||||
if roomID.String() == s.roomIDStr {
|
||||
if s.roomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||
roomKey, ok := s.senderMapping[userID.String()]
|
||||
if ok {
|
||||
sender := spec.SenderIDFromPseudoIDKey(roomKey)
|
||||
return &sender, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
senderID := spec.SenderIDFromUserID(userID)
|
||||
return &senderID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("room not found")
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) QueryUserIDForSender(
|
||||
ctx context.Context,
|
||||
roomID spec.RoomID,
|
||||
senderID spec.SenderID,
|
||||
) (*spec.UserID, error) {
|
||||
if roomID.String() == s.roomIDStr {
|
||||
if s.roomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||
for uID, roomKey := range s.senderMapping {
|
||||
if string(spec.SenderIDFromPseudoIDKey(roomKey)) == string(senderID) {
|
||||
parsedUserID, err := spec.NewUserID(uID, true)
|
||||
if err != nil {
|
||||
s.t.Fatalf("Mock QueryUserIDForSender failed: %s", err)
|
||||
}
|
||||
return parsedUserID, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
userID := senderID.ToUserID()
|
||||
if userID == nil {
|
||||
return nil, fmt.Errorf("bad sender ID")
|
||||
}
|
||||
return userID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("room not found")
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) SigningIdentityFor(ctx context.Context, roomID spec.RoomID, sender spec.UserID) (fclient.SigningIdentity, error) {
|
||||
if s.roomIDStr == roomID.String() {
|
||||
if s.roomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||
roomKey, ok := s.senderMapping[sender.String()]
|
||||
if !ok {
|
||||
s.t.Logf("SigningIdentityFor used with unknown user ID: %v", sender.String())
|
||||
return fclient.SigningIdentity{}, fmt.Errorf("could not get signing identity for %v", sender.String())
|
||||
}
|
||||
return fclient.SigningIdentity{PrivateKey: roomKey}, nil
|
||||
} else {
|
||||
return fclient.SigningIdentity{PrivateKey: ed25519.NewKeyFromSeed(make([]byte, 32))}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return fclient.SigningIdentity{}, fmt.Errorf("room not found")
|
||||
}
|
||||
|
||||
func (s *sendEventTestRoomserverAPI) InputRoomEvents(ctx context.Context, req *rsapi.InputRoomEventsRequest, res *rsapi.InputRoomEventsResponse) {
|
||||
s.savedInputRoomEvents = req.InputRoomEvents
|
||||
}
|
||||
|
||||
// Test that user ID state keys are translated correctly
|
||||
func Test_SendEvent_PseudoIDStateKeys(t *testing.T) {
|
||||
nonpseudoIDRoomVersion := gomatrixserverlib.RoomVersionV10
|
||||
pseudoIDRoomVersion := gomatrixserverlib.RoomVersionPseudoIDs
|
||||
|
||||
senderKeySeed := make([]byte, 32)
|
||||
senderUserID := "@testuser:domain"
|
||||
senderPrivKey := ed25519.NewKeyFromSeed(senderKeySeed)
|
||||
senderPseudoID := string(spec.SenderIDFromPseudoIDKey(senderPrivKey))
|
||||
|
||||
eventType := "com.example.test"
|
||||
roomIDStr := "!id:domain"
|
||||
|
||||
device := &uapi.Device{
|
||||
UserID: senderUserID,
|
||||
}
|
||||
|
||||
t.Run("user ID state key are not translated to room key in non-pseudo ID room", func(t *testing.T) {
|
||||
eventsJSON := []string{
|
||||
fmt.Sprintf(`{"type":"m.room.create","state_key":"","room_id":"%v","sender":"%v","content":{"creator":"%v","room_version":"%v"}}`, roomIDStr, senderUserID, senderUserID, nonpseudoIDRoomVersion),
|
||||
fmt.Sprintf(`{"type":"m.room.member","state_key":"%v","room_id":"%v","sender":"%v","content":{"membership":"join"}}`, senderUserID, roomIDStr, senderUserID),
|
||||
}
|
||||
|
||||
roomState, err := createEvents(eventsJSON, nonpseudoIDRoomVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare state events: %s", err.Error())
|
||||
}
|
||||
|
||||
rsAPI := &sendEventTestRoomserverAPI{
|
||||
t: t,
|
||||
roomIDStr: roomIDStr,
|
||||
roomVersion: nonpseudoIDRoomVersion,
|
||||
roomState: roomState,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://domain", io.NopCloser(strings.NewReader("{}")))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make new request: %s", err.Error())
|
||||
}
|
||||
|
||||
cfg := &config.ClientAPI{}
|
||||
|
||||
resp := SendEvent(req, device, roomIDStr, eventType, nil, &senderUserID, cfg, rsAPI, nil)
|
||||
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Fatalf("non-200 HTTP code returned: %v\nfull response: %v", resp.Code, resp)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(rsAPI.savedInputRoomEvents), 1)
|
||||
|
||||
ev := rsAPI.savedInputRoomEvents[0]
|
||||
stateKey := ev.Event.StateKey()
|
||||
if stateKey == nil {
|
||||
t.Fatalf("submitted InputRoomEvent has nil state key, when it should be %v", senderUserID)
|
||||
}
|
||||
if *stateKey != senderUserID {
|
||||
t.Fatalf("expected submitted InputRoomEvent to have user ID state key\nfound: %v\nexpected: %v", *stateKey, senderUserID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("user ID state key are translated to room key in pseudo ID room", func(t *testing.T) {
|
||||
eventsJSON := []string{
|
||||
fmt.Sprintf(`{"type":"m.room.create","state_key":"","room_id":"%v","sender":"%v","content":{"creator":"%v","room_version":"%v"}}`, roomIDStr, senderPseudoID, senderPseudoID, pseudoIDRoomVersion),
|
||||
fmt.Sprintf(`{"type":"m.room.member","state_key":"%v","room_id":"%v","sender":"%v","content":{"membership":"join"}}`, senderPseudoID, roomIDStr, senderPseudoID),
|
||||
}
|
||||
|
||||
roomState, err := createEvents(eventsJSON, pseudoIDRoomVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to prepare state events: %s", err.Error())
|
||||
}
|
||||
|
||||
rsAPI := &sendEventTestRoomserverAPI{
|
||||
t: t,
|
||||
roomIDStr: roomIDStr,
|
||||
roomVersion: pseudoIDRoomVersion,
|
||||
senderMapping: map[string]ed25519.PrivateKey{
|
||||
senderUserID: senderPrivKey,
|
||||
},
|
||||
roomState: roomState,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://domain", io.NopCloser(strings.NewReader("{}")))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make new request: %s", err.Error())
|
||||
}
|
||||
|
||||
cfg := &config.ClientAPI{}
|
||||
|
||||
resp := SendEvent(req, device, roomIDStr, eventType, nil, &senderUserID, cfg, rsAPI, nil)
|
||||
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Fatalf("non-200 HTTP code returned: %v\nfull response: %v", resp.Code, resp)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(rsAPI.savedInputRoomEvents), 1)
|
||||
|
||||
ev := rsAPI.savedInputRoomEvents[0]
|
||||
stateKey := ev.Event.StateKey()
|
||||
if stateKey == nil {
|
||||
t.Fatalf("submitted InputRoomEvent has nil state key, when it should be %v", senderPseudoID)
|
||||
}
|
||||
if *stateKey != senderPseudoID {
|
||||
t.Fatalf("expected submitted InputRoomEvent to have pseudo ID state key\nfound: %v\nexpected: %v", *stateKey, senderPseudoID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createEvents(eventsJSON []string, roomVer gomatrixserverlib.RoomVersion) ([]*types.HeaderedEvent, error) {
|
||||
events := make([]*types.HeaderedEvent, len(eventsJSON))
|
||||
|
||||
roomVerImpl, err := gomatrixserverlib.GetRoomVersion(roomVer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no roomver impl: %s", err.Error())
|
||||
}
|
||||
|
||||
for i, eventJSON := range eventsJSON {
|
||||
pdu, evErr := roomVerImpl.NewEventFromTrustedJSON([]byte(eventJSON), false)
|
||||
if evErr != nil {
|
||||
return nil, fmt.Errorf("failed to make event: %s", err.Error())
|
||||
}
|
||||
ev := types.HeaderedEvent{PDU: pdu}
|
||||
events[i] = &ev
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
|
@ -19,10 +19,10 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
"github.com/matrix-org/dendrite/internal/transactions"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
||||
|
@ -53,7 +53,10 @@ func SendToDevice(
|
|||
req.Context(), device.UserID, userID, deviceID, eventType, message,
|
||||
); err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("eduProducer.SendToDevice failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
type typingContentJSON struct {
|
||||
|
@ -39,12 +39,20 @@ func SendTyping(
|
|||
if device.UserID != userID {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
resErr := checkMemberInRoom(req.Context(), rsAPI, userID, roomID)
|
||||
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
||||
if resErr != nil {
|
||||
return *resErr
|
||||
}
|
||||
|
@ -58,7 +66,10 @@ func SendTyping(
|
|||
|
||||
if err := syncProducer.SendTyping(req.Context(), userID, roomID, r.Typing, r.Timeout); err != nil {
|
||||
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{
|
||||
|
|
|
@ -22,22 +22,21 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/tokens"
|
||||
"github.com/matrix-org/util"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/internal/transactions"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
// Unspecced server notice request
|
||||
|
@ -52,6 +51,7 @@ type sendServerNoticeRequest struct {
|
|||
StateKey string `json:"state_key,omitempty"`
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
// SendServerNotice sends a message to a specific user. It can only be invoked by an admin.
|
||||
func SendServerNotice(
|
||||
req *http.Request,
|
||||
|
@ -68,7 +68,7 @@ func SendServerNotice(
|
|||
if device.AccountType != userapi.AccountTypeAdmin {
|
||||
return util.JSONResponse{
|
||||
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,38 +90,46 @@ func SendServerNotice(
|
|||
if !r.valid() {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.BadJSON("Invalid request"),
|
||||
JSON: spec.BadJSON("Invalid request"),
|
||||
}
|
||||
}
|
||||
|
||||
userID, err := spec.NewUserID(r.UserID, true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: spec.InvalidParam("invalid user ID"),
|
||||
}
|
||||
}
|
||||
|
||||
// get rooms for specified user
|
||||
allUserRooms := []string{}
|
||||
userRooms := api.QueryRoomsForUserResponse{}
|
||||
allUserRooms := []spec.RoomID{}
|
||||
// Get rooms the user is either joined, invited or has left.
|
||||
for _, membership := range []string{"join", "invite", "leave"} {
|
||||
if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||
UserID: r.UserID,
|
||||
WantMembership: membership,
|
||||
}, &userRooms); err != nil {
|
||||
userRooms, queryErr := rsAPI.QueryRoomsForUser(ctx, *userID, membership)
|
||||
if queryErr != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
allUserRooms = append(allUserRooms, userRooms.RoomIDs...)
|
||||
allUserRooms = append(allUserRooms, userRooms...)
|
||||
}
|
||||
|
||||
// get rooms of the sender
|
||||
senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName)
|
||||
senderRooms := api.QueryRoomsForUserResponse{}
|
||||
if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||
UserID: senderUserID,
|
||||
WantMembership: "join",
|
||||
}, &senderRooms); err != nil {
|
||||
senderUserID, err := spec.NewUserID(fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName), true)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
senderRooms, err := rsAPI.QueryRoomsForUser(ctx, *senderUserID, "join")
|
||||
if err != nil {
|
||||
return util.ErrorResponse(err)
|
||||
}
|
||||
|
||||
// check if we have rooms in common
|
||||
commonRooms := []string{}
|
||||
commonRooms := []spec.RoomID{}
|
||||
for _, userRoomID := range allUserRooms {
|
||||
for _, senderRoomID := range senderRooms.RoomIDs {
|
||||
for _, senderRoomID := range senderRooms {
|
||||
if userRoomID == senderRoomID {
|
||||
commonRooms = append(commonRooms, senderRoomID)
|
||||
}
|
||||
|
@ -134,12 +142,12 @@ func SendServerNotice(
|
|||
|
||||
var (
|
||||
roomID string
|
||||
roomVersion = version.DefaultRoomVersion()
|
||||
roomVersion = rsAPI.DefaultRoomVersion()
|
||||
)
|
||||
|
||||
// create a new room for the user
|
||||
if len(commonRooms) == 0 {
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID)
|
||||
powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID.String())
|
||||
powerLevelContent.Users[r.UserID] = -10 // taken from Synapse
|
||||
pl, err := json.Marshal(powerLevelContent)
|
||||
if err != nil {
|
||||
|
@ -155,9 +163,8 @@ func SendServerNotice(
|
|||
Invite: []string{r.UserID},
|
||||
Name: cfgNotices.RoomName,
|
||||
Visibility: "private",
|
||||
Preset: presetPrivateChat,
|
||||
Preset: spec.PresetPrivateChat,
|
||||
CreationContent: cc,
|
||||
GuestCanJoin: false,
|
||||
RoomVersion: roomVersion,
|
||||
PowerLevelContentOverride: pl,
|
||||
}
|
||||
|
@ -176,7 +183,10 @@ func SendServerNotice(
|
|||
}}
|
||||
if err = saveTagData(req, r.UserID, roomID, userAPI, serverAlertTag); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("saveTagData failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -185,12 +195,23 @@ func SendServerNotice(
|
|||
}
|
||||
} else {
|
||||
// we've found a room in common, check the membership
|
||||
roomID = commonRooms[0]
|
||||
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].String()
|
||||
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 {
|
||||
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 {
|
||||
// re-invite the user
|
||||
|
@ -207,7 +228,7 @@ func SendServerNotice(
|
|||
"body": r.Content.Body,
|
||||
"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 {
|
||||
logrus.Errorf("failed to send message: %+v", resErr)
|
||||
return *resErr
|
||||
|
@ -228,8 +249,8 @@ func SendServerNotice(
|
|||
if err := api.SendEvents(
|
||||
ctx, rsAPI,
|
||||
api.KindNew,
|
||||
[]*gomatrixserverlib.HeaderedEvent{
|
||||
e.Headered(roomVersion),
|
||||
[]*types.HeaderedEvent{
|
||||
{PDU: e},
|
||||
},
|
||||
device.UserDomain(),
|
||||
cfgClient.Matrix.ServerName,
|
||||
|
@ -238,7 +259,10 @@ func SendServerNotice(
|
|||
false,
|
||||
); err != nil {
|
||||
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{
|
||||
"event_id": e.EventID(),
|
||||
|
@ -333,7 +357,7 @@ func getSenderDevice(
|
|||
if len(deviceRes.Devices) > 0 {
|
||||
// If there were changes to the profile, create a new membership event
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/syncapi/synctypes"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
@ -56,12 +56,15 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
|||
StateToFetch: []gomatrixserverlib.StateKeyTuple{},
|
||||
}, &stateRes); err != nil {
|
||||
util.GetLogger(ctx).WithError(err).Error("queryAPI.QueryLatestEventsAndState failed")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !stateRes.RoomExists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden("room does not exist"),
|
||||
JSON: spec.Forbidden("room does not exist"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +76,10 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
|||
content := map[string]string{}
|
||||
if err := json.Unmarshal(ev.Content(), &content); err != nil {
|
||||
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 {
|
||||
worldReadable = visibility == "world_readable"
|
||||
|
@ -93,20 +99,31 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
|||
if !worldReadable {
|
||||
// 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.
|
||||
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,
|
||||
UserID: device.UserID,
|
||||
UserID: *userID,
|
||||
}, &membershipRes)
|
||||
if err != nil {
|
||||
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.
|
||||
// We won't tell the user about a room they have never joined.
|
||||
if !membershipRes.HasBeenInRoom {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
|
@ -133,7 +150,9 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
|||
for _, ev := range stateRes.StateEvents {
|
||||
stateEvents = append(
|
||||
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 {
|
||||
|
@ -147,12 +166,34 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
|||
}, &stateAfterRes)
|
||||
if err != nil {
|
||||
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 {
|
||||
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,
|
||||
synctypes.HeaderedToClientEvent(ev, synctypes.FormatAll),
|
||||
synctypes.ToClientEvent(ev, synctypes.FormatAll, sender, sk),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +217,37 @@ func OnIncomingStateTypeRequest(
|
|||
var worldReadable bool
|
||||
var wantLatestState bool
|
||||
|
||||
roomVer, err := rsAPI.QueryRoomVersionForRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden(fmt.Sprintf("Unknown room %q or user %q has never joined this room", roomID, device.UserID)),
|
||||
}
|
||||
}
|
||||
|
||||
// Translate user ID state keys to room keys in pseudo ID rooms
|
||||
if roomVer == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||
parsedRoomID, err := spec.NewRoomID(roomID)
|
||||
if err != nil {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: spec.InvalidParam("invalid room ID"),
|
||||
}
|
||||
}
|
||||
newStateKey, err := synctypes.FromClientStateKey(*parsedRoomID, stateKey, func(roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
|
||||
return rsAPI.QuerySenderIDForUser(ctx, roomID, userID)
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: work out better logic for failure cases (e.g. sender ID not found)
|
||||
util.GetLogger(ctx).WithError(err).Error("synctypes.FromClientStateKey failed")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.Unknown("internal server error"),
|
||||
}
|
||||
}
|
||||
stateKey = *newStateKey
|
||||
}
|
||||
|
||||
// Always fetch visibility so that we can work out whether to show
|
||||
// the latest events or the last event from when the user was joined.
|
||||
// Then include the requested event type and state key, assuming it
|
||||
|
@ -202,7 +274,10 @@ func OnIncomingStateTypeRequest(
|
|||
StateToFetch: stateToFetch,
|
||||
}, &stateRes); err != nil {
|
||||
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
|
||||
|
@ -213,7 +288,10 @@ func OnIncomingStateTypeRequest(
|
|||
content := map[string]string{}
|
||||
if err := json.Unmarshal(ev.Content(), &content); err != nil {
|
||||
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 {
|
||||
worldReadable = visibility == "world_readable"
|
||||
|
@ -231,22 +309,33 @@ func OnIncomingStateTypeRequest(
|
|||
// membershipRes will only be populated if the room is not world-readable.
|
||||
var membershipRes api.QueryMembershipForUserResponse
|
||||
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
|
||||
// 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,
|
||||
UserID: device.UserID,
|
||||
UserID: *userID,
|
||||
}, &membershipRes)
|
||||
if err != nil {
|
||||
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.
|
||||
// We won't tell the user about a room they have never joined.
|
||||
if !membershipRes.HasBeenInRoom || membershipRes.Membership == spec.Ban {
|
||||
return util.JSONResponse{
|
||||
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
|
||||
|
@ -266,7 +355,7 @@ func OnIncomingStateTypeRequest(
|
|||
"state_at_event": !wantLatestState,
|
||||
}).Info("Fetching state")
|
||||
|
||||
var event *gomatrixserverlib.HeaderedEvent
|
||||
var event *types.HeaderedEvent
|
||||
if wantLatestState {
|
||||
// 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
|
||||
|
@ -294,7 +383,10 @@ func OnIncomingStateTypeRequest(
|
|||
}, &stateAfterRes)
|
||||
if err != nil {
|
||||
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 {
|
||||
event = stateAfterRes.StateEvents[0]
|
||||
|
@ -306,12 +398,14 @@ func OnIncomingStateTypeRequest(
|
|||
if event == nil {
|
||||
return util.JSONResponse{
|
||||
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{
|
||||
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{}
|
||||
|
|
253
clientapi/routing/state_test.go
Normal file
253
clientapi/routing/state_test.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
rsapi "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
var ()
|
||||
|
||||
type stateTestRoomserverAPI struct {
|
||||
rsapi.RoomserverInternalAPI
|
||||
t *testing.T
|
||||
roomState map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent
|
||||
roomIDStr string
|
||||
roomVersion gomatrixserverlib.RoomVersion
|
||||
userIDStr string
|
||||
// userID -> senderID
|
||||
senderMapping map[string]string
|
||||
}
|
||||
|
||||
func (s stateTestRoomserverAPI) QueryRoomVersionForRoom(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) {
|
||||
if roomID == s.roomIDStr {
|
||||
return s.roomVersion, nil
|
||||
} else {
|
||||
s.t.Logf("room version queried for %s", roomID)
|
||||
return "", fmt.Errorf("unknown room")
|
||||
}
|
||||
}
|
||||
|
||||
func (s stateTestRoomserverAPI) QueryLatestEventsAndState(
|
||||
ctx context.Context,
|
||||
req *rsapi.QueryLatestEventsAndStateRequest,
|
||||
res *rsapi.QueryLatestEventsAndStateResponse,
|
||||
) error {
|
||||
res.RoomExists = req.RoomID == s.roomIDStr
|
||||
if !res.RoomExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
res.StateEvents = []*types.HeaderedEvent{}
|
||||
for _, stateKeyTuple := range req.StateToFetch {
|
||||
val, ok := s.roomState[stateKeyTuple]
|
||||
if ok && val != nil {
|
||||
res.StateEvents = append(res.StateEvents, val)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s stateTestRoomserverAPI) QueryMembershipForUser(
|
||||
ctx context.Context,
|
||||
req *rsapi.QueryMembershipForUserRequest,
|
||||
res *rsapi.QueryMembershipForUserResponse,
|
||||
) error {
|
||||
if req.UserID.String() == s.userIDStr {
|
||||
res.HasBeenInRoom = true
|
||||
res.IsInRoom = true
|
||||
res.RoomExists = true
|
||||
res.Membership = spec.Join
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s stateTestRoomserverAPI) QuerySenderIDForUser(
|
||||
ctx context.Context,
|
||||
roomID spec.RoomID,
|
||||
userID spec.UserID,
|
||||
) (*spec.SenderID, error) {
|
||||
sID, ok := s.senderMapping[userID.String()]
|
||||
if ok {
|
||||
sender := spec.SenderID(sID)
|
||||
return &sender, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s stateTestRoomserverAPI) QueryUserIDForSender(
|
||||
ctx context.Context,
|
||||
roomID spec.RoomID,
|
||||
senderID spec.SenderID,
|
||||
) (*spec.UserID, error) {
|
||||
for uID, sID := range s.senderMapping {
|
||||
if sID == string(senderID) {
|
||||
parsedUserID, err := spec.NewUserID(uID, true)
|
||||
if err != nil {
|
||||
s.t.Fatalf("Mock QueryUserIDForSender failed: %s", err)
|
||||
}
|
||||
return parsedUserID, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s stateTestRoomserverAPI) QueryStateAfterEvents(
|
||||
ctx context.Context,
|
||||
req *rsapi.QueryStateAfterEventsRequest,
|
||||
res *rsapi.QueryStateAfterEventsResponse,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_OnIncomingStateTypeRequest(t *testing.T) {
|
||||
var tempRoomServerCfg config.RoomServer
|
||||
tempRoomServerCfg.Defaults(config.DefaultOpts{})
|
||||
defaultRoomVersion := tempRoomServerCfg.DefaultRoomVersion
|
||||
pseudoIDRoomVersion := gomatrixserverlib.RoomVersionPseudoIDs
|
||||
nonPseudoIDRoomVersion := gomatrixserverlib.RoomVersionV10
|
||||
|
||||
userIDStr := "@testuser:domain"
|
||||
eventType := "com.example.test"
|
||||
stateKey := "testStateKey"
|
||||
roomIDStr := "!id:domain"
|
||||
|
||||
device := &uapi.Device{
|
||||
UserID: userIDStr,
|
||||
}
|
||||
|
||||
t.Run("request simple state key", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
rsAPI := stateTestRoomserverAPI{
|
||||
roomVersion: defaultRoomVersion,
|
||||
roomIDStr: roomIDStr,
|
||||
roomState: map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{
|
||||
{
|
||||
EventType: eventType,
|
||||
StateKey: stateKey,
|
||||
}: mustCreateStatePDU(t, defaultRoomVersion, roomIDStr, eventType, stateKey, map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}),
|
||||
},
|
||||
userIDStr: userIDStr,
|
||||
}
|
||||
|
||||
jsonResp := OnIncomingStateTypeRequest(ctx, device, rsAPI, roomIDStr, eventType, stateKey, false)
|
||||
|
||||
assert.DeepEqual(t, jsonResp, util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: spec.RawJSON(`{"foo":"bar"}`),
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("user ID key translated to room key in pseudo ID rooms", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
stateSenderUserID := "@sender:domain"
|
||||
stateSenderRoomKey := "testsenderkey"
|
||||
|
||||
rsAPI := stateTestRoomserverAPI{
|
||||
roomVersion: pseudoIDRoomVersion,
|
||||
roomIDStr: roomIDStr,
|
||||
roomState: map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{
|
||||
{
|
||||
EventType: eventType,
|
||||
StateKey: stateSenderRoomKey,
|
||||
}: mustCreateStatePDU(t, pseudoIDRoomVersion, roomIDStr, eventType, stateSenderRoomKey, map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}),
|
||||
{
|
||||
EventType: eventType,
|
||||
StateKey: stateSenderUserID,
|
||||
}: mustCreateStatePDU(t, pseudoIDRoomVersion, roomIDStr, eventType, stateSenderUserID, map[string]interface{}{
|
||||
"not": "thisone",
|
||||
}),
|
||||
},
|
||||
userIDStr: userIDStr,
|
||||
senderMapping: map[string]string{
|
||||
stateSenderUserID: stateSenderRoomKey,
|
||||
},
|
||||
}
|
||||
|
||||
jsonResp := OnIncomingStateTypeRequest(ctx, device, rsAPI, roomIDStr, eventType, stateSenderUserID, false)
|
||||
|
||||
assert.DeepEqual(t, jsonResp, util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: spec.RawJSON(`{"foo":"bar"}`),
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("user ID key not translated to room key in non-pseudo ID rooms", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
stateSenderUserID := "@sender:domain"
|
||||
stateSenderRoomKey := "testsenderkey"
|
||||
|
||||
rsAPI := stateTestRoomserverAPI{
|
||||
roomVersion: nonPseudoIDRoomVersion,
|
||||
roomIDStr: roomIDStr,
|
||||
roomState: map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent{
|
||||
{
|
||||
EventType: eventType,
|
||||
StateKey: stateSenderRoomKey,
|
||||
}: mustCreateStatePDU(t, nonPseudoIDRoomVersion, roomIDStr, eventType, stateSenderRoomKey, map[string]interface{}{
|
||||
"not": "thisone",
|
||||
}),
|
||||
{
|
||||
EventType: eventType,
|
||||
StateKey: stateSenderUserID,
|
||||
}: mustCreateStatePDU(t, nonPseudoIDRoomVersion, roomIDStr, eventType, stateSenderUserID, map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}),
|
||||
},
|
||||
userIDStr: userIDStr,
|
||||
senderMapping: map[string]string{
|
||||
stateSenderUserID: stateSenderUserID,
|
||||
},
|
||||
}
|
||||
|
||||
jsonResp := OnIncomingStateTypeRequest(ctx, device, rsAPI, roomIDStr, eventType, stateSenderUserID, false)
|
||||
|
||||
assert.DeepEqual(t, jsonResp, util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: spec.RawJSON(`{"foo":"bar"}`),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mustCreateStatePDU(t *testing.T, roomVer gomatrixserverlib.RoomVersion, roomID string, stateType string, stateKey string, stateContent map[string]interface{}) *types.HeaderedEvent {
|
||||
t.Helper()
|
||||
roomVerImpl := gomatrixserverlib.MustGetRoomVersion(roomVer)
|
||||
|
||||
evBytes, err := json.Marshal(map[string]interface{}{
|
||||
"room_id": roomID,
|
||||
"type": stateType,
|
||||
"state_key": stateKey,
|
||||
"content": stateContent,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create event: %v", err)
|
||||
}
|
||||
|
||||
ev, err := roomVerImpl.NewEventFromTrustedJSON(evBytes, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create event: %v", err)
|
||||
}
|
||||
|
||||
return &types.HeaderedEvent{PDU: ev}
|
||||
}
|
|
@ -21,8 +21,8 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
|
||||
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/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
// Protocols implements
|
||||
|
@ -33,13 +33,16 @@ func Protocols(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, dev
|
|||
resp := &appserviceAPI.ProtocolResponse{}
|
||||
|
||||
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 protocol != "" {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("The protocol is unknown."),
|
||||
JSON: spec.NotFound("The protocol is unknown."),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
@ -71,12 +74,15 @@ func User(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, device *
|
|||
Protocol: protocol,
|
||||
Params: params.Encode(),
|
||||
}, resp); err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !resp.Exists {
|
||||
return util.JSONResponse{
|
||||
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{
|
||||
|
@ -97,12 +103,15 @@ func Location(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, devi
|
|||
Protocol: protocol,
|
||||
Params: params.Encode(),
|
||||
}, resp); err != nil {
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
if !resp.Exists {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("No portal rooms were found."),
|
||||
JSON: spec.NotFound("No portal rooms were found."),
|
||||
}
|
||||
}
|
||||
return util.JSONResponse{
|
||||
|
|
|
@ -19,12 +19,12 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"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/setup/config"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
userdb "github.com/matrix-org/dendrite/userapi/storage"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/util"
|
||||
|
@ -60,28 +60,37 @@ func RequestEmailToken(req *http.Request, threePIDAPI api.ClientUserAPI, cfg *co
|
|||
|
||||
if err != nil {
|
||||
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 {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MatrixError{
|
||||
ErrCode: "M_THREEPID_IN_USE",
|
||||
JSON: spec.MatrixError{
|
||||
ErrCode: spec.ErrorThreePIDInUse,
|
||||
Err: userdb.Err3PIDInUse.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
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")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
|
@ -102,21 +111,27 @@ func CheckAndSave3PIDAssociation(
|
|||
|
||||
// Check if the association has been validated
|
||||
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{
|
||||
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")
|
||||
return jsonerror.InternalServerError()
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
if !verified {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
JSON: jsonerror.MatrixError{
|
||||
ErrCode: "M_THREEPID_AUTH_FAILED",
|
||||
JSON: spec.MatrixError{
|
||||
ErrCode: spec.ErrorThreePIDAuthFailed,
|
||||
Err: "Failed to auth 3pid",
|
||||
},
|
||||
}
|
||||
|
@ -127,7 +142,10 @@ func CheckAndSave3PIDAssociation(
|
|||
err = threepid.PublishAssociation(req.Context(), body.Creds, device.UserID, cfg, client)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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{
|
||||
|
@ -145,7 +166,10 @@ func CheckAndSave3PIDAssociation(
|
|||
Medium: medium,
|
||||
}, &struct{}{}); err != nil {
|
||||
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{
|
||||
|
@ -161,7 +185,10 @@ func GetAssociated3PIDs(
|
|||
localpart, domain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||
if err != nil {
|
||||
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{}
|
||||
|
@ -171,7 +198,10 @@ func GetAssociated3PIDs(
|
|||
}, res)
|
||||
if err != nil {
|
||||
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{
|
||||
|
@ -192,7 +222,10 @@ func Forget3PID(req *http.Request, threepidAPI api.ClientUserAPI) util.JSONRespo
|
|||
Medium: body.Medium,
|
||||
}, &struct{}{}); err != nil {
|
||||
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{
|
||||
|
|
|
@ -15,16 +15,18 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/version"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
|
@ -53,42 +55,43 @@ func UpgradeRoom(
|
|||
if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil {
|
||||
return util.JSONResponse{
|
||||
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: device.UserID,
|
||||
RoomID: roomID,
|
||||
RoomVersion: gomatrixserverlib.RoomVersion(r.NewVersion),
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
util.GetLogger(req.Context()).WithError(err).Error("device UserID is invalid")
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
upgradeResp := roomserverAPI.PerformRoomUpgradeResponse{}
|
||||
|
||||
if err := rsAPI.PerformRoomUpgrade(req.Context(), &upgradeReq, &upgradeResp); err != nil {
|
||||
return jsonerror.InternalAPIError(req.Context(), err)
|
||||
}
|
||||
|
||||
if upgradeResp.Error != nil {
|
||||
if upgradeResp.Error.Code == roomserverAPI.PerformErrorNoRoom {
|
||||
newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion))
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
case roomserverAPI.ErrNotAllowed:
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: spec.Forbidden(e.Error()),
|
||||
}
|
||||
default:
|
||||
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusNotFound,
|
||||
JSON: jsonerror.NotFound("Room does not exist"),
|
||||
JSON: spec.NotFound("Room does not exist"),
|
||||
}
|
||||
} else if upgradeResp.Error.Code == roomserverAPI.PerformErrorNotAllowed {
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusForbidden,
|
||||
JSON: jsonerror.Forbidden(upgradeResp.Error.Msg),
|
||||
}
|
||||
} else {
|
||||
return jsonerror.InternalServerError()
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
JSON: spec.InternalServerError{},
|
||||
}
|
||||
}
|
||||
|
||||
return util.JSONResponse{
|
||||
Code: http.StatusOK,
|
||||
JSON: upgradeRoomResponse{
|
||||
ReplacementRoom: upgradeResp.NewRoomID,
|
||||
ReplacementRoom: newRoomID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func SearchUserDirectory(
|
|||
provider userapi.QuerySearchProfilesAPI,
|
||||
searchString string,
|
||||
limit int,
|
||||
federation *fclient.FederationClient,
|
||||
federation fclient.FederationClient,
|
||||
localServerName spec.ServerName,
|
||||
) util.JSONResponse {
|
||||
if limit < 10 {
|
||||
|
|
|
@ -25,9 +25,9 @@ import (
|
|||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/util"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
// RequestTurnServer implements:
|
||||
|
@ -60,7 +60,10 @@ func RequestTurnServer(req *http.Request, device *api.Device, cfg *config.Client
|
|||
|
||||
if err != nil {
|
||||
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))
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||
"github.com/matrix-org/dendrite/roomserver/api"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
|
@ -63,14 +64,34 @@ type idServerStoreInviteResponse struct {
|
|||
}
|
||||
|
||||
var (
|
||||
// ErrMissingParameter is the error raised if a request for 3PID invite has
|
||||
// an incomplete body
|
||||
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 = fmt.Errorf("'address', 'id_server' and 'medium' must all be supplied")
|
||||
errNotTrusted = fmt.Errorf("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.
|
||||
// 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
|
||||
|
@ -98,7 +119,7 @@ func CheckAndProcessInvite(
|
|||
} else if body.Address == "" || body.IDServer == "" || body.Medium == "" {
|
||||
// If at least one of the 3PID-specific fields is supplied but not all
|
||||
// of them, return an error
|
||||
err = ErrMissingParameter
|
||||
err = ErrMissingParameter{}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -334,8 +355,22 @@ func emit3PIDInviteEvent(
|
|||
rsAPI api.ClientRoomserverAPI,
|
||||
evTime time.Time,
|
||||
) error {
|
||||
builder := &gomatrixserverlib.EventBuilder{
|
||||
Sender: device.UserID,
|
||||
userID, err := spec.NewUserID(device.UserID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
validRoomID, err := spec.NewRoomID(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sender, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if sender == nil {
|
||||
return fmt.Errorf("sender ID not found for %s in %s", *userID, *validRoomID)
|
||||
}
|
||||
proto := &gomatrixserverlib.ProtoEvent{
|
||||
SenderID: string(*sender),
|
||||
RoomID: roomID,
|
||||
Type: "m.room.third_party_invite",
|
||||
StateKey: &res.Token,
|
||||
|
@ -349,7 +384,7 @@ func emit3PIDInviteEvent(
|
|||
PublicKeys: res.PublicKeys,
|
||||
}
|
||||
|
||||
if err := builder.SetContent(content); err != nil {
|
||||
if err = proto.SetContent(content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -359,7 +394,7 @@ func emit3PIDInviteEvent(
|
|||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -367,8 +402,8 @@ func emit3PIDInviteEvent(
|
|||
return api.SendEvents(
|
||||
ctx, rsAPI,
|
||||
api.KindNew,
|
||||
[]*gomatrixserverlib.HeaderedEvent{
|
||||
event.Headered(queryRes.RoomVersion),
|
||||
[]*types.HeaderedEvent{
|
||||
event,
|
||||
},
|
||||
device.UserDomain(),
|
||||
cfg.Matrix.ServerName,
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/setup/config"
|
||||
"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
|
||||
|
@ -133,7 +134,7 @@ func CheckAssociation(
|
|||
return false, "", "", err
|
||||
}
|
||||
|
||||
if respBody.ErrCode == "M_SESSION_NOT_VALIDATED" {
|
||||
if respBody.ErrCode == string(spec.ErrorSessionNotValidated) {
|
||||
return false, "", "", nil
|
||||
} else if len(respBody.ErrCode) > 0 {
|
||||
return false, "", "", errors.New(respBody.Error)
|
||||
|
@ -186,5 +187,5 @@ func isTrusted(idServer string, cfg *config.ClientAPI) error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNotTrusted
|
||||
return ErrNotTrusted{}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ func CreateClient(
|
|||
|
||||
func CreateFederationClient(
|
||||
cfg *config.Dendrite, s *pineconeSessions.Sessions,
|
||||
) *fclient.FederationClient {
|
||||
) fclient.FederationClient {
|
||||
return fclient.NewFederationClient(
|
||||
cfg.Global.SigningIdentities(),
|
||||
fclient.WithTransport(createTransport(s)),
|
||||
|
|
|
@ -98,7 +98,7 @@ func GenerateDefaultConfig(sk ed25519.PrivateKey, storageDir string, cacheDir st
|
|||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", filepath.Join(storageDir, dbPrefix)))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationsender.db", filepath.Join(storageDir, dbPrefix)))
|
||||
cfg.RelayAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-relayapi.db", filepath.Join(storageDir, dbPrefix)))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||
cfg.MSCs.MSCs = []string{"msc2836"}
|
||||
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", filepath.Join(storageDir, dbPrefix)))
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
|
@ -126,7 +126,7 @@ func (p *P2PMonolith) SetupPinecone(sk ed25519.PrivateKey) {
|
|||
}
|
||||
|
||||
func (p *P2PMonolith) SetupDendrite(
|
||||
processCtx *process.ProcessContext, cfg *config.Dendrite, cm sqlutil.Connections, routers httputil.Routers,
|
||||
processCtx *process.ProcessContext, cfg *config.Dendrite, cm *sqlutil.Connections, routers httputil.Routers,
|
||||
port int, enableRelaying bool, enableMetrics bool, enableWebsockets bool) {
|
||||
|
||||
p.port = port
|
||||
|
@ -143,13 +143,12 @@ func (p *P2PMonolith) SetupDendrite(
|
|||
fsAPI := federationapi.NewInternalAPI(
|
||||
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
|
||||
)
|
||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation)
|
||||
|
||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||
|
||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||
|
||||
userProvider := users.NewPineconeUserProvider(p.Router, p.Sessions, userAPI, federation)
|
||||
roomProvider := rooms.NewPineconeRoomProvider(p.Router, p.Sessions, fsAPI, federation)
|
||||
|
||||
|
@ -222,8 +221,8 @@ func (p *P2PMonolith) closeAllResources() {
|
|||
p.httpServerMu.Lock()
|
||||
if p.httpServer != nil {
|
||||
_ = p.httpServer.Shutdown(context.Background())
|
||||
p.httpServerMu.Unlock()
|
||||
}
|
||||
p.httpServerMu.Unlock()
|
||||
|
||||
select {
|
||||
case p.stopHandlingEvents <- true:
|
||||
|
|
|
@ -33,14 +33,14 @@ type PineconeRoomProvider struct {
|
|||
r *pineconeRouter.Router
|
||||
s *pineconeSessions.Sessions
|
||||
fedSender api.FederationInternalAPI
|
||||
fedClient *fclient.FederationClient
|
||||
fedClient fclient.FederationClient
|
||||
}
|
||||
|
||||
func NewPineconeRoomProvider(
|
||||
r *pineconeRouter.Router,
|
||||
s *pineconeSessions.Sessions,
|
||||
fedSender api.FederationInternalAPI,
|
||||
fedClient *fclient.FederationClient,
|
||||
fedClient fclient.FederationClient,
|
||||
) *PineconeRoomProvider {
|
||||
p := &PineconeRoomProvider{
|
||||
r: r,
|
||||
|
@ -68,7 +68,7 @@ func (p *PineconeRoomProvider) Rooms() []fclient.PublicRoom {
|
|||
// bulkFetchPublicRoomsFromServers fetches public rooms from the list of homeservers.
|
||||
// Returns a list of public rooms.
|
||||
func bulkFetchPublicRoomsFromServers(
|
||||
ctx context.Context, fedClient *fclient.FederationClient,
|
||||
ctx context.Context, fedClient fclient.FederationClient,
|
||||
origin spec.ServerName,
|
||||
homeservers map[spec.ServerName]struct{},
|
||||
) (publicRooms []fclient.PublicRoom) {
|
||||
|
|
|
@ -39,7 +39,7 @@ type PineconeUserProvider struct {
|
|||
r *pineconeRouter.Router
|
||||
s *pineconeSessions.Sessions
|
||||
userAPI userapi.QuerySearchProfilesAPI
|
||||
fedClient *fclient.FederationClient
|
||||
fedClient fclient.FederationClient
|
||||
}
|
||||
|
||||
const PublicURL = "/_matrix/p2p/profiles"
|
||||
|
@ -48,7 +48,7 @@ func NewPineconeUserProvider(
|
|||
r *pineconeRouter.Router,
|
||||
s *pineconeSessions.Sessions,
|
||||
userAPI userapi.QuerySearchProfilesAPI,
|
||||
fedClient *fclient.FederationClient,
|
||||
fedClient fclient.FederationClient,
|
||||
) *PineconeUserProvider {
|
||||
p := &PineconeUserProvider{
|
||||
r: r,
|
||||
|
@ -95,7 +95,7 @@ func (p *PineconeUserProvider) QuerySearchProfiles(ctx context.Context, req *use
|
|||
// Returns a list of user profiles.
|
||||
func bulkFetchUserDirectoriesFromServers(
|
||||
ctx context.Context, req *userapi.QuerySearchProfilesRequest,
|
||||
fedClient *fclient.FederationClient,
|
||||
fedClient fclient.FederationClient,
|
||||
homeservers map[spec.ServerName]struct{},
|
||||
) (profiles []authtypes.Profile) {
|
||||
jsonBody, err := json.Marshal(req)
|
||||
|
|
|
@ -134,7 +134,7 @@ func main() {
|
|||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", filepath.Join(*instanceDir, *instanceName)))
|
||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", filepath.Join(*instanceDir, *instanceName)))
|
||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationapi.db", filepath.Join(*instanceDir, *instanceName)))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||
cfg.MSCs.MSCs = []string{"msc2836"}
|
||||
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", filepath.Join(*instanceDir, *instanceName)))
|
||||
cfg.ClientAPI.RegistrationDisabled = false
|
||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||
|
|
|
@ -38,7 +38,7 @@ func (n *Node) CreateClient() *fclient.Client {
|
|||
|
||||
func (n *Node) CreateFederationClient(
|
||||
cfg *config.Dendrite,
|
||||
) *fclient.FederationClient {
|
||||
) fclient.FederationClient {
|
||||
tr := &http.Transport{}
|
||||
tr.RegisterProtocol(
|
||||
"matrix", &yggroundtripper{
|
||||
|
|
|
@ -29,11 +29,11 @@ import (
|
|||
type YggdrasilRoomProvider struct {
|
||||
node *yggconn.Node
|
||||
fedSender api.FederationInternalAPI
|
||||
fedClient *fclient.FederationClient
|
||||
fedClient fclient.FederationClient
|
||||
}
|
||||
|
||||
func NewYggdrasilRoomProvider(
|
||||
node *yggconn.Node, fedSender api.FederationInternalAPI, fedClient *fclient.FederationClient,
|
||||
node *yggconn.Node, fedSender api.FederationInternalAPI, fedClient fclient.FederationClient,
|
||||
) *YggdrasilRoomProvider {
|
||||
p := &YggdrasilRoomProvider{
|
||||
node: node,
|
||||
|
@ -54,7 +54,7 @@ func (p *YggdrasilRoomProvider) Rooms() []fclient.PublicRoom {
|
|||
// bulkFetchPublicRoomsFromServers fetches public rooms from the list of homeservers.
|
||||
// Returns a list of public rooms.
|
||||
func bulkFetchPublicRoomsFromServers(
|
||||
ctx context.Context, fedClient *fclient.FederationClient,
|
||||
ctx context.Context, fedClient fclient.FederationClient,
|
||||
origin spec.ServerName,
|
||||
homeservers []spec.ServerName,
|
||||
) (publicRooms []fclient.PublicRoom) {
|
||||
|
|
|
@ -55,7 +55,7 @@ var latest, _ = semver.NewVersion("v6.6.6") // Dummy version, used as "HEAD"
|
|||
// due to the error:
|
||||
// When using COPY with more than one source file, the destination must be a directory and end with a /
|
||||
// We need to run a postgres anyway, so use the dockerfile associated with Complement instead.
|
||||
const DockerfilePostgreSQL = `FROM golang:1.18-stretch as build
|
||||
const DockerfilePostgreSQL = `FROM golang:1.18-buster as build
|
||||
RUN apt-get update && apt-get install -y postgresql
|
||||
WORKDIR /build
|
||||
ARG BINARY
|
||||
|
@ -74,16 +74,16 @@ RUN ./generate-keys --private-key matrix_key.pem --tls-cert server.crt --tls-key
|
|||
# Replace the connection string with a single postgres DB, using user/db = 'postgres' and no password
|
||||
RUN sed -i "s%connection_string:.*$%connection_string: postgresql://postgres@localhost/postgres?sslmode=disable%g" dendrite.yaml
|
||||
# No password when connecting over localhost
|
||||
RUN sed -i "s%127.0.0.1/32 md5%127.0.0.1/32 trust%g" /etc/postgresql/9.6/main/pg_hba.conf
|
||||
RUN sed -i "s%127.0.0.1/32 md5%127.0.0.1/32 trust%g" /etc/postgresql/11/main/pg_hba.conf
|
||||
# Bump up max conns for moar concurrency
|
||||
RUN sed -i 's/max_connections = 100/max_connections = 2000/g' /etc/postgresql/9.6/main/postgresql.conf
|
||||
RUN sed -i 's/max_connections = 100/max_connections = 2000/g' /etc/postgresql/11/main/postgresql.conf
|
||||
RUN sed -i 's/max_open_conns:.*$/max_open_conns: 100/g' dendrite.yaml
|
||||
|
||||
# This entry script starts postgres, waits for it to be up then starts dendrite
|
||||
RUN echo '\
|
||||
#!/bin/bash -eu \n\
|
||||
pg_lsclusters \n\
|
||||
pg_ctlcluster 9.6 main start \n\
|
||||
pg_ctlcluster 11 main start \n\
|
||||
\n\
|
||||
until pg_isready \n\
|
||||
do \n\
|
||||
|
@ -101,7 +101,7 @@ ENV BINARY=dendrite
|
|||
EXPOSE 8008 8448
|
||||
CMD /build/run_dendrite.sh`
|
||||
|
||||
const DockerfileSQLite = `FROM golang:1.18-stretch as build
|
||||
const DockerfileSQLite = `FROM golang:1.18-buster as build
|
||||
RUN apt-get update && apt-get install -y postgresql
|
||||
WORKDIR /build
|
||||
ARG BINARY
|
||||
|
@ -119,7 +119,7 @@ RUN ./generate-keys --private-key matrix_key.pem --tls-cert server.crt --tls-key
|
|||
|
||||
# Make sure the SQLite databases are in a persistent location, we're already mapping
|
||||
# the postgresql folder so let's just use that for simplicity
|
||||
RUN sed -i "s%connection_string:.file:%connection_string: file:\/var\/lib\/postgresql\/9.6\/main\/%g" dendrite.yaml
|
||||
RUN sed -i "s%connection_string:.file:%connection_string: file:\/var\/lib\/postgresql\/11\/main\/%g" dendrite.yaml
|
||||
|
||||
# This entry script starts postgres, waits for it to be up then starts dendrite
|
||||
RUN echo '\
|
||||
|
@ -402,7 +402,7 @@ func runImage(dockerClient *client.Client, volumeName string, branchNameToImageI
|
|||
{
|
||||
Type: mount.TypeVolume,
|
||||
Source: volumeName,
|
||||
Target: "/var/lib/postgresql/9.6/main",
|
||||
Target: "/var/lib/postgresql/11/main",
|
||||
},
|
||||
},
|
||||
}, nil, nil, "dendrite_upgrade_test_"+branchName)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"github.com/matrix-org/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
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
|
||||
createRoomResp, err := users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
Preset: "trusted_private_chat",
|
||||
Preset: spec.PresetTrustedPrivateChat,
|
||||
Invite: []string{users[1].userID},
|
||||
IsDirect: true,
|
||||
})
|
||||
|
@ -98,7 +99,7 @@ func runTests(baseURL string, v *semver.Version) error {
|
|||
publicRoomID := ""
|
||||
createRoomResp, err = users[0].client.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
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
|
||||
// try to join it
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||
"github.com/matrix-org/dendrite/setup/process"
|
||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/matrix-org/dendrite/appservice"
|
||||
|
@ -156,13 +157,14 @@ func main() {
|
|||
|
||||
keyRing := fsAPI.KeyRing()
|
||||
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federationClient)
|
||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||
|
||||
// The underlying roomserver implementation needs to be able to call the fedsender.
|
||||
// This is different to rsAPI which can be the http client which doesn't need this
|
||||
// dependency. Other components also need updating after their dependencies are up.
|
||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||
|
||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federationClient)
|
||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||
|
||||
rsAPI.SetAppserviceAPI(asAPI)
|
||||
rsAPI.SetUserAPI(userAPI)
|
||||
|
||||
|
@ -187,6 +189,16 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
upCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "dendrite",
|
||||
Name: "up",
|
||||
ConstLabels: map[string]string{
|
||||
"version": internal.VersionString(),
|
||||
},
|
||||
})
|
||||
upCounter.Add(1)
|
||||
prometheus.MustRegister(upCounter)
|
||||
|
||||
// Expose the matrix APIs directly rather than putting them under a /api path.
|
||||
go func() {
|
||||
basepkg.SetupAndServeHTTP(processCtx, cfg, routers, httpAddr, nil, nil)
|
||||
|
|
|
@ -74,7 +74,7 @@ func main() {
|
|||
// don't hit matrix.org when running tests!!!
|
||||
cfg.FederationAPI.KeyPerspectives = config.KeyPerspectives{}
|
||||
cfg.MediaAPI.BasePath = config.Path(filepath.Join(*dirPath, "media"))
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946", "msc2444", "msc2753"}
|
||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2444", "msc2753"}
|
||||
cfg.Logging[0].Level = "trace"
|
||||
cfg.Logging[0].Type = "std"
|
||||
cfg.UserAPI.BCryptCost = bcrypt.MinCost
|
||||
|
|
|
@ -11,13 +11,16 @@ import (
|
|||
|
||||
"github.com/matrix-org/dendrite/internal/caching"
|
||||
"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/storage"
|
||||
"github.com/matrix-org/dendrite/roomserver/types"
|
||||
"github.com/matrix-org/dendrite/setup"
|
||||
"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/gomatrixserverlib"
|
||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||
)
|
||||
|
||||
// This is a utility for inspecting state snapshots and running state resolution
|
||||
|
@ -65,10 +68,14 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
natsInstance := &jetstream.NATSInstance{}
|
||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm,
|
||||
natsInstance, caching.NewRistrettoCache(128*1024*1024, time.Hour, true), false)
|
||||
|
||||
roomInfo := &types.RoomInfo{
|
||||
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
||||
}
|
||||
stateres := state.NewStateResolution(roomserverDB, roomInfo)
|
||||
stateres := state.NewStateResolution(roomserverDB, roomInfo, rsAPI)
|
||||
|
||||
if *difference {
|
||||
if len(snapshotNIDs) != 2 {
|
||||
|
@ -91,14 +98,14 @@ func main() {
|
|||
}
|
||||
|
||||
var eventEntries []types.Event
|
||||
eventEntries, err = roomserverDB.Events(ctx, roomInfo, eventNIDs)
|
||||
eventEntries, err = roomserverDB.Events(ctx, roomInfo.RoomVersion, eventNIDs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
events := make(map[types.EventNID]*gomatrixserverlib.Event, len(eventEntries))
|
||||
events := make(map[types.EventNID]gomatrixserverlib.PDU, len(eventEntries))
|
||||
for _, entry := range eventEntries {
|
||||
events[entry.EventNID] = entry.Event
|
||||
events[entry.EventNID] = entry.PDU
|
||||
}
|
||||
|
||||
if len(removed) > 0 {
|
||||
|
@ -149,15 +156,15 @@ func main() {
|
|||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
authEventIDMap := make(map[string]struct{})
|
||||
events := make([]*gomatrixserverlib.Event, len(eventEntries))
|
||||
events := make([]gomatrixserverlib.PDU, len(eventEntries))
|
||||
for i := range eventEntries {
|
||||
events[i] = eventEntries[i].Event
|
||||
events[i] = eventEntries[i].PDU
|
||||
for _, authEventID := range eventEntries[i].AuthEventIDs() {
|
||||
authEventIDMap[authEventID] = struct{}{}
|
||||
}
|
||||
|
@ -174,17 +181,17 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
authEvents := make([]*gomatrixserverlib.Event, len(authEventEntries))
|
||||
authEvents := make([]gomatrixserverlib.PDU, len(authEventEntries))
|
||||
for i := range authEventEntries {
|
||||
authEvents[i] = authEventEntries[i].Event
|
||||
authEvents[i] = authEventEntries[i].PDU
|
||||
}
|
||||
|
||||
fmt.Println("Resolving state")
|
||||
var resolved Events
|
||||
resolved, err = gomatrixserverlib.ResolveConflicts(
|
||||
gomatrixserverlib.RoomVersion(*roomVersion),
|
||||
events,
|
||||
authEvents,
|
||||
gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -208,7 +215,7 @@ func main() {
|
|||
fmt.Println("Returned", count, "state events after filtering")
|
||||
}
|
||||
|
||||
type Events []*gomatrixserverlib.Event
|
||||
type Events []gomatrixserverlib.PDU
|
||||
|
||||
func (e Events) Len() int {
|
||||
return len(e)
|
||||
|
|
|
@ -69,8 +69,7 @@ global:
|
|||
# e.g. localhost:443
|
||||
well_known_server_name: ""
|
||||
|
||||
# The server name to delegate client-server communications to, with optional port
|
||||
# e.g. localhost:443
|
||||
# The base URL to delegate client-server communications to e.g. https://localhost
|
||||
well_known_client_name: ""
|
||||
|
||||
# Lists of domains that the server will trust as identity servers to verify third
|
||||
|
@ -277,7 +276,6 @@ media_api:
|
|||
mscs:
|
||||
mscs:
|
||||
# - msc2836 # (Threading, see https://github.com/matrix-org/matrix-doc/pull/2836)
|
||||
# - msc2946 # (Spaces Summary, see https://github.com/matrix-org/matrix-doc/pull/2946)
|
||||
|
||||
# Configuration for the Sync API.
|
||||
sync_api:
|
||||
|
|
16
docs/FAQ.md
16
docs/FAQ.md
|
@ -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
|
||||
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?
|
||||
|
||||
|
@ -64,16 +64,18 @@ Use [dendrite.matrix.org](https://dendrite.matrix.org) which we officially suppo
|
|||
|
||||
## Does Dendrite support Space Summaries?
|
||||
|
||||
Yes, [Space Summaries](https://github.com/matrix-org/matrix-spec-proposals/pull/2946) were merged into the Matrix Spec as of 2022-01-17 however, they are still treated as an MSC (Matrix Specification Change) in Dendrite. In order to enable Space Summaries in Dendrite, you must add the MSC to the MSC configuration section in the configuration YAML. If the MSC is not enabled, a user will typically see a perpetual loading icon on the summary page. See below for a demonstration of how to add to the Dendrite configuration:
|
||||
Yes
|
||||
|
||||
## Does Dendrite support Threads?
|
||||
|
||||
Yes, to enable them [msc2836](https://github.com/matrix-org/matrix-spec-proposals/pull/2836) would need to be added to mscs configuration in order to support Threading. Other MSCs are not currently supported.
|
||||
|
||||
```
|
||||
mscs:
|
||||
mscs:
|
||||
- msc2946
|
||||
- msc2836
|
||||
```
|
||||
|
||||
Similarly, [msc2836](https://github.com/matrix-org/matrix-spec-proposals/pull/2836) would need to be added to mscs configuration in order to support Threading. Other MSCs are not currently supported.
|
||||
|
||||
Please note that MSCs should be considered experimental and can result in significant usability issues when enabled. If you'd like more details on how MSCs are ratified or the current status of MSCs, please see the [Matrix specification documentation](https://spec.matrix.org/proposals/) on the subject.
|
||||
|
||||
## Does Dendrite support push notifications?
|
||||
|
@ -103,7 +105,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?
|
||||
|
||||
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?
|
||||
|
||||
|
@ -157,7 +159,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
|
||||
|
||||
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?
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ or alternatively, in the [installation](installation/) folder:
|
|||
|
||||
1. [Planning your deployment](installation/1_planning.md)
|
||||
2. [Setting up the domain](installation/2_domainname.md)
|
||||
3. [Preparing database storage](installation/3_database.md)
|
||||
4. [Generating signing keys](installation/4_signingkey.md)
|
||||
5. [Installing as a monolith](installation/5_install_monolith.md)
|
||||
6. [Populate the configuration](installation/7_configuration.md)
|
||||
7. [Starting the monolith](installation/8_starting_monolith.md)
|
||||
3. [Installing Dendrite](installation/manual/1_build.md)
|
||||
4. [Preparing database storage](installation/manual/2_database.md)
|
||||
5. [Populate the configuration](installation/manual/3_configuration.md)
|
||||
6. [Generating signing keys](installation/manual/4_signingkey.md)
|
||||
7. [Starting Dendrite](installation/manual/5_starting_dendrite.md)
|
||||
|
|
|
@ -11,10 +11,9 @@ User accounts can be created on a Dendrite instance in a number of ways.
|
|||
|
||||
## From the command line
|
||||
|
||||
The `create-account` tool is built in the `bin` folder when building Dendrite with
|
||||
the `build.sh` script.
|
||||
The `create-account` tool is built in the `bin` folder when [building](../installation/build) Dendrite.
|
||||
|
||||
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.
|
||||
|
||||
An example of using `create-account` to create a **normal account**:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
title: Supported admin APIs
|
||||
parent: Administration
|
||||
nav_order: 4
|
||||
permalink: /administration/adminapi
|
||||
---
|
||||
|
||||
|
@ -51,11 +52,15 @@ the room IDs of all affected rooms.
|
|||
|
||||
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:
|
||||
|
||||
```
|
||||
```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.
|
||||
|
||||
## POST `/_dendrite/admin/purgeRoom/{roomID}`
|
||||
|
||||
This endpoint instructs Dendrite to remove the given room from its database. 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`
|
||||
|
||||
Request body format:
|
||||
```
|
||||
```json
|
||||
{
|
||||
"user_id": "@target_user:server_name",
|
||||
"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.
|
||||
If successfully sent, the API will return the following response:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"event_id": "<event_id>"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
title: Optimise your installation
|
||||
parent: Installation
|
||||
parent: Administration
|
||||
has_toc: true
|
||||
nav_order: 11
|
||||
permalink: /installation/start/optimisation
|
||||
nav_order: 5
|
||||
permalink: /administration/optimisation
|
||||
---
|
||||
|
||||
# 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
|
||||
`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
|
||||
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
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue