Compare commits
24 commits
main
...
s7evink/me
Author | SHA1 | Date | |
---|---|---|---|
cae2b61c88 | |||
0aa1b6c218 | |||
16075ce657 | |||
5a7d36d1b3 | |||
92a633d0e4 | |||
6942c198b9 | |||
4e27ff28b8 | |||
f825ce2935 | |||
2bcb89ad4c | |||
63f239f336 | |||
b93a9e4615 | |||
9569498761 | |||
9ddd62925c | |||
3085928906 | |||
fa4f7021a1 | |||
da51f32e03 | |||
4bf57a2519 | |||
dcd28e3614 | |||
641bac0ce5 | |||
552eaf2940 | |||
8b5afcf680 | |||
302d8d7089 | |||
2b3b355ebd | |||
bc8e83fd28 |
|
@ -1,59 +0,0 @@
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
GHCR_NAMESPACE: sigb.us
|
|
||||||
PLATFORMS: linux/amd64
|
|
||||||
FORGEJO_USER: signaryk
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
monolith:
|
|
||||||
name: Monolith image
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: ghcr.io/catthehacker/ubuntu:act-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Get release tag & build flags
|
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
|
||||||
run: |
|
|
||||||
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login to sigb.us container registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: git.sigb.us
|
|
||||||
username: ${{ env.FORGEJO_USER }}
|
|
||||||
password: ${{ secrets.FORGEJO_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build main monolith image
|
|
||||||
id: docker_build_monolith
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: ${{ env.PLATFORMS }}
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
git.sigb.us/${{ env.GHCR_NAMESPACE }}/dendrite:${{ github.ref_name }}
|
|
||||||
git.sigb.us/${{ env.GHCR_NAMESPACE }}/dendrite:latest
|
|
||||||
git.sigb.us/${{ env.GHCR_NAMESPACE }}/dendrite:devel
|
|
||||||
|
|
||||||
- name: Build release monolith image
|
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
|
||||||
id: docker_build_monolith_release
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: ${{ env.PLATFORMS }}
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
git.sigb.us/${{ env.GHCR_NAMESPACE }}/dendrite:latest
|
|
||||||
git.sigb.us/${{ env.GHCR_NAMESPACE }}/dendrite:stable
|
|
||||||
git.sigb.us/${{ env.GHCR_NAMESPACE }}/dendrite:${{ env.RELEASE_VERSION }}
|
|
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
|
@ -62,6 +62,6 @@ If you can identify any relevant log snippets from server logs, please include
|
||||||
those (please be careful to remove any personal or private data). Please surround them with
|
those (please be careful to remove any personal or private data). Please surround them with
|
||||||
``` (three backticks, on a line on their own), so that they are formatted legibly.
|
``` (three backticks, on a line on their own), so that they are formatted legibly.
|
||||||
|
|
||||||
Alternatively, please send logs to @kegan:matrix.org, @s7evink:matrix.org or @devonh:one.ems.host
|
Alternatively, please send logs to @kegan:matrix.org or @neilalexander:matrix.org
|
||||||
with a link to the respective Github issue, thanks!
|
with a link to the respective Github issue, thanks!
|
||||||
-->
|
-->
|
||||||
|
|
2
.github/codecov.yaml
vendored
2
.github/codecov.yaml
vendored
|
@ -7,7 +7,7 @@ coverage:
|
||||||
project:
|
project:
|
||||||
default:
|
default:
|
||||||
target: auto
|
target: auto
|
||||||
threshold: 0.1%
|
threshold: 0%
|
||||||
base: auto
|
base: auto
|
||||||
flags:
|
flags:
|
||||||
- unittests
|
- unittests
|
||||||
|
|
81
.github/workflows/dendrite.yml
vendored
81
.github/workflows/dendrite.yml
vendored
|
@ -28,10 +28,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ false }} # disable for now
|
if: ${{ false }} # disable for now
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
cache: true
|
cache: true
|
||||||
|
@ -41,7 +41,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
@ -66,11 +66,11 @@ jobs:
|
||||||
name: Linting
|
name: Linting
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Install libolm
|
- name: Install libolm
|
||||||
run: sudo apt-get install libolm-dev libolm3
|
run: sudo apt-get install libolm-dev libolm3
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
|
@ -102,14 +102,14 @@ jobs:
|
||||||
--health-timeout 5s
|
--health-timeout 5s
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Install libolm
|
- name: Install libolm
|
||||||
run: sudo apt-get install libolm-dev libolm3
|
run: sudo apt-get install libolm-dev libolm3
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
# manually set up caches, as they otherwise clash with different steps using setup-go with cache=true
|
# manually set up caches, as they otherwise clash with different steps using setup-go with cache=true
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
@ -123,7 +123,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
# Optional: pass GITHUB_TOKEN to avoid rate limiting.
|
# Optional: pass GITHUB_TOKEN to avoid rate limiting.
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- run: go test -json -v ./... 2>&1 | gotestfmt -hide all
|
- run: go test -json -v ./... 2>&1 | gotestfmt
|
||||||
env:
|
env:
|
||||||
POSTGRES_HOST: localhost
|
POSTGRES_HOST: localhost
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
@ -141,12 +141,12 @@ jobs:
|
||||||
goos: ["linux"]
|
goos: ["linux"]
|
||||||
goarch: ["amd64", "386"]
|
goarch: ["amd64", "386"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -174,12 +174,12 @@ jobs:
|
||||||
goos: ["windows"]
|
goos: ["windows"]
|
||||||
goarch: ["amd64"]
|
goarch: ["amd64"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -235,11 +235,11 @@ jobs:
|
||||||
--health-timeout 5s
|
--health-timeout 5s
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Install libolm
|
- name: Install libolm
|
||||||
run: sudo apt-get install libolm-dev libolm3
|
run: sudo apt-get install libolm-dev libolm3
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- name: Set up gotestfmt
|
- name: Set up gotestfmt
|
||||||
|
@ -247,7 +247,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
# Optional: pass GITHUB_TOKEN to avoid rate limiting.
|
# Optional: pass GITHUB_TOKEN to avoid rate limiting.
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -255,18 +255,17 @@ jobs:
|
||||||
key: ${{ runner.os }}-go-stable-test-race-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-stable-test-race-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-stable-test-race-
|
${{ 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 -hide all
|
- run: go test -race -json -v -coverpkg=./... -coverprofile=cover.out $(go list ./... | grep -v /cmd/dendrite*) 2>&1 | gotestfmt
|
||||||
env:
|
env:
|
||||||
POSTGRES_HOST: localhost
|
POSTGRES_HOST: localhost
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: dendrite
|
POSTGRES_DB: dendrite
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
flags: unittests
|
flags: unittests
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
|
|
||||||
# run database upgrade tests
|
# run database upgrade tests
|
||||||
upgrade_test:
|
upgrade_test:
|
||||||
|
@ -275,22 +274,12 @@ jobs:
|
||||||
needs: initial-tests-done
|
needs: initial-tests-done
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
cache: true
|
cache: true
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-upgrade-test-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-upgrade-test-
|
|
||||||
- name: Docker version
|
|
||||||
run: docker version
|
|
||||||
- name: Build upgrade-tests
|
- name: Build upgrade-tests
|
||||||
run: go build ./cmd/dendrite-upgrade-tests
|
run: go build ./cmd/dendrite-upgrade-tests
|
||||||
- name: Test upgrade (PostgreSQL)
|
- name: Test upgrade (PostgreSQL)
|
||||||
|
@ -305,22 +294,12 @@ jobs:
|
||||||
needs: initial-tests-done
|
needs: initial-tests-done
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
cache: true
|
cache: true
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-upgrade-direct-test-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-upgrade-direct-test-
|
|
||||||
- name: Docker version
|
|
||||||
run: docker version
|
|
||||||
- name: Build upgrade-tests
|
- name: Build upgrade-tests
|
||||||
run: go build ./cmd/dendrite-upgrade-tests
|
run: go build ./cmd/dendrite-upgrade-tests
|
||||||
- name: Test upgrade (PostgreSQL)
|
- name: Test upgrade (PostgreSQL)
|
||||||
|
@ -357,8 +336,8 @@ jobs:
|
||||||
SYTEST_BRANCH: ${{ github.head_ref }}
|
SYTEST_BRANCH: ${{ github.head_ref }}
|
||||||
CGO_ENABLED: ${{ matrix.cgo && 1 }}
|
CGO_ENABLED: ${{ matrix.cgo && 1 }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -381,7 +360,7 @@ jobs:
|
||||||
run: /src/are-we-synapse-yet.py /logs/results.tap -v
|
run: /src/are-we-synapse-yet.py /logs/results.tap -v
|
||||||
continue-on-error: true # not fatal
|
continue-on-error: true # not fatal
|
||||||
- name: Upload Sytest logs
|
- name: Upload Sytest logs
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: Sytest Logs - ${{ job.status }} - (Dendrite, ${{ join(matrix.*, ', ') }})
|
name: Sytest Logs - ${{ job.status }} - (Dendrite, ${{ join(matrix.*, ', ') }})
|
||||||
|
@ -421,8 +400,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
|
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
|
||||||
go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
|
go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
|
||||||
- name: Run actions/checkout@v4 for dendrite
|
- name: Run actions/checkout@v3 for dendrite
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: dendrite
|
path: dendrite
|
||||||
|
|
||||||
|
@ -457,7 +436,7 @@ jobs:
|
||||||
# Run Complement
|
# Run Complement
|
||||||
- run: |
|
- run: |
|
||||||
set -o pipefail &&
|
set -o pipefail &&
|
||||||
go test -v -json -tags dendrite_blacklist ./tests ./tests/csapi 2>&1 | gotestfmt -hide all
|
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt
|
||||||
shell: bash
|
shell: bash
|
||||||
name: Run Complement Tests
|
name: Run Complement Tests
|
||||||
env:
|
env:
|
||||||
|
|
48
.github/workflows/docker.yml
vendored
48
.github/workflows/docker.yml
vendored
|
@ -27,22 +27,26 @@ jobs:
|
||||||
security-events: write # To upload Trivy sarif files
|
security-events: write # To upload Trivy sarif files
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
- name: Get release tag & build flags
|
- name: Get release tag & build flags
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
run: |
|
run: |
|
||||||
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
echo "BUILD=$(git rev-parse --short HEAD || \"\")" >> $GITHUB_ENV
|
||||||
|
BRANCH=$(git symbolic-ref --short HEAD | tr -d \/)
|
||||||
|
[ ${BRANCH} == "main" ] && BRANCH=""
|
||||||
|
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ env.DOCKER_HUB_USER }}
|
username: ${{ env.DOCKER_HUB_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
- name: Login to GitHub Containers
|
- name: Login to GitHub Containers
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
@ -56,6 +60,7 @@ jobs:
|
||||||
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache
|
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
|
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache,mode=max
|
||||||
context: .
|
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 }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
@ -70,6 +75,7 @@ jobs:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
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 }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
@ -98,22 +104,26 @@ jobs:
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
- name: Get release tag & build flags
|
- name: Get release tag & build flags
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
run: |
|
run: |
|
||||||
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
echo "BUILD=$(git rev-parse --short HEAD || \"\")" >> $GITHUB_ENV
|
||||||
|
BRANCH=$(git symbolic-ref --short HEAD | tr -d \/)
|
||||||
|
[ ${BRANCH} == "main" ] && BRANCH=""
|
||||||
|
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ env.DOCKER_HUB_USER }}
|
username: ${{ env.DOCKER_HUB_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
- name: Login to GitHub Containers
|
- name: Login to GitHub Containers
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
@ -127,6 +137,7 @@ jobs:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
file: ./build/docker/Dockerfile.demo-pinecone
|
file: ./build/docker/Dockerfile.demo-pinecone
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
|
@ -142,6 +153,7 @@ jobs:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
file: ./build/docker/Dockerfile.demo-pinecone
|
file: ./build/docker/Dockerfile.demo-pinecone
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
|
@ -159,22 +171,26 @@ jobs:
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
- name: Get release tag & build flags
|
- name: Get release tag & build flags
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
run: |
|
run: |
|
||||||
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
echo "BUILD=$(git rev-parse --short HEAD || \"\")" >> $GITHUB_ENV
|
||||||
|
BRANCH=$(git symbolic-ref --short HEAD | tr -d \/)
|
||||||
|
[ ${BRANCH} == "main" ] && BRANCH=""
|
||||||
|
echo "BRANCH=${BRANCH}" >> $GITHUB_ENV
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ env.DOCKER_HUB_USER }}
|
username: ${{ env.DOCKER_HUB_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
- name: Login to GitHub Containers
|
- name: Login to GitHub Containers
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
@ -188,6 +204,7 @@ jobs:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
file: ./build/docker/Dockerfile.demo-yggdrasil
|
file: ./build/docker/Dockerfile.demo-yggdrasil
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
|
@ -203,6 +220,7 @@ jobs:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
file: ./build/docker/Dockerfile.demo-yggdrasil
|
file: ./build/docker/Dockerfile.demo-yggdrasil
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
|
|
2
.github/workflows/gh-pages.yml
vendored
2
.github/workflows/gh-pages.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
uses: actions/configure-pages@v2
|
uses: actions/configure-pages@v2
|
||||||
- name: Build with Jekyll
|
- name: Build with Jekyll
|
||||||
|
|
4
.github/workflows/helm.yml
vendored
4
.github/workflows/helm.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ jobs:
|
||||||
version: v3.10.0
|
version: v3.10.0
|
||||||
|
|
||||||
- name: Run chart-releaser
|
- name: Run chart-releaser
|
||||||
uses: helm/chart-releaser-action@ed43eb303604cbc0eeec8390544f7748dc6c790d # specific commit, since `mark_as_latest` is not yet in a release
|
uses: helm/chart-releaser-action@v1.4.1
|
||||||
env:
|
env:
|
||||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
with:
|
with:
|
||||||
|
|
6
.github/workflows/k8s.yml
vendored
6
.github/workflows/k8s.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
outputs:
|
outputs:
|
||||||
changed: ${{ steps.list-changed.outputs.changed }}
|
changed: ${{ steps.list-changed.outputs.changed }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: azure/setup-helm@v3
|
- uses: azure/setup-helm@v3
|
||||||
|
@ -48,7 +48,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ inputs.checkoutCommit }}
|
ref: ${{ inputs.checkoutCommit }}
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
- name: Create k3d cluster
|
- name: Create k3d cluster
|
||||||
uses: nolar/setup-k3d-k3s@v1
|
uses: nolar/setup-k3d-k3s@v1
|
||||||
with:
|
with:
|
||||||
version: v1.28
|
version: v1.21
|
||||||
- name: Remove node taints
|
- name: Remove node taints
|
||||||
run: |
|
run: |
|
||||||
kubectl taint --all=true nodes node.cloudprovider.kubernetes.io/uninitialized- || true
|
kubectl taint --all=true nodes node.cloudprovider.kubernetes.io/uninitialized- || true
|
||||||
|
|
68
.github/workflows/schedules.yaml
vendored
68
.github/workflows/schedules.yaml
vendored
|
@ -10,26 +10,8 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_date: # https://stackoverflow.com/questions/63014786/how-to-schedule-a-github-actions-nightly-build-but-run-it-only-when-there-where
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Check latest commit
|
|
||||||
outputs:
|
|
||||||
should_run: ${{ steps.should_run.outputs.should_run }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: print latest_commit
|
|
||||||
run: echo ${{ github.sha }}
|
|
||||||
|
|
||||||
- id: should_run
|
|
||||||
continue-on-error: true
|
|
||||||
name: check latest commit is less than a day
|
|
||||||
if: ${{ github.event_name == 'schedule' }}
|
|
||||||
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
|
|
||||||
|
|
||||||
# run Sytest in different variations
|
# run Sytest in different variations
|
||||||
sytest:
|
sytest:
|
||||||
needs: check_date
|
|
||||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: "Sytest (${{ matrix.label }})"
|
name: "Sytest (${{ matrix.label }})"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -56,8 +38,8 @@ jobs:
|
||||||
RACE_DETECTION: 1
|
RACE_DETECTION: 1
|
||||||
COVER: 1
|
COVER: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/go-build
|
~/.cache/go-build
|
||||||
|
@ -80,7 +62,7 @@ jobs:
|
||||||
run: /src/are-we-synapse-yet.py /logs/results.tap -v
|
run: /src/are-we-synapse-yet.py /logs/results.tap -v
|
||||||
continue-on-error: true # not fatal
|
continue-on-error: true # not fatal
|
||||||
- name: Upload Sytest logs
|
- name: Upload Sytest logs
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: Sytest Logs - ${{ job.status }} - (Dendrite ${{ join(matrix.*, ' ') }})
|
name: Sytest Logs - ${{ job.status }} - (Dendrite ${{ join(matrix.*, ' ') }})
|
||||||
|
@ -93,34 +75,31 @@ jobs:
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
name: "Sytest Coverage"
|
name: "Sytest Coverage"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ sytest, check_date ] # only run once Sytest is done and there was a commit
|
needs: sytest # only run once Sytest is done
|
||||||
if: ${{ always() && needs.check_date.outputs.should_run != 'false' }}
|
if: ${{ always() }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v3
|
||||||
- name: Collect coverage
|
- name: Collect coverage
|
||||||
run: |
|
run: |
|
||||||
go tool covdata textfmt -i="$(find Sytest* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o sytest.cov
|
go tool covdata textfmt -i="$(find Sytest* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o sytest.cov
|
||||||
grep -Ev 'relayapi|setup/mscs|api_trace' sytest.cov > final.cov
|
grep -Ev 'relayapi|setup/mscs|api_trace' sytest.cov > final.cov
|
||||||
go tool covdata func -i="$(find Sytest* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
go tool covdata func -i="$(find Sytest* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
files: ./final.cov
|
files: ./final.cov
|
||||||
flags: sytest
|
flags: sytest
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
|
|
||||||
# run Complement
|
# run Complement
|
||||||
complement:
|
complement:
|
||||||
needs: check_date
|
|
||||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
|
||||||
name: "Complement (${{ matrix.label }})"
|
name: "Complement (${{ matrix.label }})"
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -149,9 +128,9 @@ jobs:
|
||||||
# See https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md specifically GOROOT_1_17_X64
|
# See https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md specifically GOROOT_1_17_X64
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
|
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
|
||||||
go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
|
go get -v github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
|
||||||
- name: Run actions/checkout@v4 for dendrite
|
- name: Run actions/checkout@v3 for dendrite
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: dendrite
|
path: dendrite
|
||||||
|
|
||||||
|
@ -195,7 +174,7 @@ jobs:
|
||||||
# Run Complement
|
# Run Complement
|
||||||
- run: |
|
- run: |
|
||||||
set -o pipefail &&
|
set -o pipefail &&
|
||||||
go test -v -json -tags dendrite_blacklist ./tests ./tests/csapi 2>&1 | gotestfmt -hide all
|
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt
|
||||||
shell: bash
|
shell: bash
|
||||||
name: Run Complement Tests
|
name: Run Complement Tests
|
||||||
env:
|
env:
|
||||||
|
@ -206,7 +185,7 @@ jobs:
|
||||||
working-directory: complement
|
working-directory: complement
|
||||||
|
|
||||||
- name: Upload Complement logs
|
- name: Upload Complement logs
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: Complement Logs - (Dendrite ${{ join(matrix.*, ' ') }})
|
name: Complement Logs - (Dendrite ${{ join(matrix.*, ' ') }})
|
||||||
|
@ -217,32 +196,30 @@ jobs:
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
name: "Complement Coverage"
|
name: "Complement Coverage"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ complement, check_date ] # only run once Complements is done and there was a commit
|
needs: complement # only run once Complement is done
|
||||||
if: ${{ always() && needs.check_date.outputs.should_run != 'false' }}
|
if: ${{ always() }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
cache: true
|
cache: true
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v3
|
||||||
- name: Collect coverage
|
- name: Collect coverage
|
||||||
run: |
|
run: |
|
||||||
go tool covdata textfmt -i="$(find Complement* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o complement.cov
|
go tool covdata textfmt -i="$(find Complement* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" -o complement.cov
|
||||||
grep -Ev 'relayapi|setup/mscs|api_trace' complement.cov > final.cov
|
grep -Ev 'relayapi|setup/mscs|api_trace' complement.cov > final.cov
|
||||||
go tool covdata func -i="$(find Complement* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
go tool covdata func -i="$(find Complement* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)"
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
files: ./final.cov
|
files: ./final.cov
|
||||||
flags: complement
|
flags: complement
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
token: ${{ secrets.CODECOV_TOKEN }} # required
|
|
||||||
|
|
||||||
element-web:
|
element-web:
|
||||||
if: ${{ false }} # disable for now, as Cypress has been replaced by Playwright
|
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -251,7 +228,7 @@ jobs:
|
||||||
# Our test suite includes some screenshot tests with unusual diacritics, which are
|
# Our test suite includes some screenshot tests with unusual diacritics, which are
|
||||||
# supposed to be covered by STIXGeneral.
|
# supposed to be covered by STIXGeneral.
|
||||||
tools: fonts-stix
|
tools: fonts-stix
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: matrix-org/matrix-react-sdk
|
repository: matrix-org/matrix-react-sdk
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
@ -282,7 +259,6 @@ jobs:
|
||||||
TMPDIR: ${{ runner.temp }}
|
TMPDIR: ${{ runner.temp }}
|
||||||
|
|
||||||
element-web-pinecone:
|
element-web-pinecone:
|
||||||
if: ${{ false }} # disable for now, as Cypress has been replaced by Playwright
|
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -291,7 +267,7 @@ jobs:
|
||||||
# Our test suite includes some screenshot tests with unusual diacritics, which are
|
# Our test suite includes some screenshot tests with unusual diacritics, which are
|
||||||
# supposed to be covered by STIXGeneral.
|
# supposed to be covered by STIXGeneral.
|
||||||
tools: fonts-stix
|
tools: fonts-stix
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: matrix-org/matrix-react-sdk
|
repository: matrix-org/matrix-react-sdk
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
# Allow GitHub config
|
# Allow GitHub config
|
||||||
!.github
|
!.github
|
||||||
!.forgejo
|
|
||||||
|
|
||||||
# Downloads
|
# Downloads
|
||||||
/.downloads
|
/.downloads
|
||||||
|
@ -78,7 +77,4 @@ media_store/
|
||||||
build
|
build
|
||||||
|
|
||||||
# golang workspaces
|
# golang workspaces
|
||||||
go.work*
|
go.work*
|
||||||
|
|
||||||
# helm chart
|
|
||||||
helm/dendrite/charts/
|
|
|
@ -6,7 +6,7 @@ run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
|
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
timeout: 5m
|
deadline: 30m
|
||||||
|
|
||||||
# exit code when at least one issue was found, default is 1
|
# exit code when at least one issue was found, default is 1
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
|
@ -18,6 +18,24 @@ run:
|
||||||
#build-tags:
|
#build-tags:
|
||||||
# - mytag
|
# - mytag
|
||||||
|
|
||||||
|
# which dirs to skip: they won't be analyzed;
|
||||||
|
# can use regexp here: generated.*, regexp is applied on full path;
|
||||||
|
# default value is empty list, but next dirs are always skipped independently
|
||||||
|
# from this option's value:
|
||||||
|
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||||
|
skip-dirs:
|
||||||
|
- bin
|
||||||
|
- docs
|
||||||
|
|
||||||
|
# which files to skip: they will be analyzed, but issues from them
|
||||||
|
# won't be reported. Default value is empty list, but there is
|
||||||
|
# no need to include all autogenerated files, we confidently recognize
|
||||||
|
# autogenerated files. If it's not please let us know.
|
||||||
|
skip-files:
|
||||||
|
- ".*\\.md$"
|
||||||
|
- ".*\\.sh$"
|
||||||
|
- "^cmd/syncserver-integration-tests/testdata.go$"
|
||||||
|
|
||||||
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
|
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
|
||||||
# If invoked with -mod=readonly, the go command is disallowed from the implicit
|
# If invoked with -mod=readonly, the go command is disallowed from the implicit
|
||||||
# automatic updating of go.mod described above. Instead, it fails when any changes
|
# automatic updating of go.mod described above. Instead, it fails when any changes
|
||||||
|
@ -32,8 +50,7 @@ run:
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||||
formats:
|
format: colored-line-number
|
||||||
- format: colored-line-number
|
|
||||||
|
|
||||||
# print lines of code with issue, default is true
|
# print lines of code with issue, default is true
|
||||||
print-issued-lines: true
|
print-issued-lines: true
|
||||||
|
@ -62,8 +79,9 @@ linters-settings:
|
||||||
# see https://github.com/kisielk/errcheck#excluding-functions for details
|
# see https://github.com/kisielk/errcheck#excluding-functions for details
|
||||||
#exclude: /path/to/file.txt
|
#exclude: /path/to/file.txt
|
||||||
govet:
|
govet:
|
||||||
enable:
|
# report about shadowed variables
|
||||||
- shadow
|
check-shadowing: true
|
||||||
|
|
||||||
# settings per analyzer
|
# settings per analyzer
|
||||||
settings:
|
settings:
|
||||||
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
||||||
|
@ -162,6 +180,7 @@ linters-settings:
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- errcheck
|
- errcheck
|
||||||
|
- goconst
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- goimports # Does everything gofmt does
|
- goimports # Does everything gofmt does
|
||||||
- gosimple
|
- gosimple
|
||||||
|
@ -192,31 +211,12 @@ linters:
|
||||||
- stylecheck
|
- stylecheck
|
||||||
- typecheck # Should turn back on soon
|
- typecheck # Should turn back on soon
|
||||||
- unconvert # Should turn back on soon
|
- unconvert # Should turn back on soon
|
||||||
- goconst # Slightly annoying, as it reports "issues" in SQL statements
|
|
||||||
disable-all: false
|
disable-all: false
|
||||||
presets:
|
presets:
|
||||||
fast: false
|
fast: false
|
||||||
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
# which files to skip: they will be analyzed, but issues from them
|
|
||||||
# won't be reported. Default value is empty list, but there is
|
|
||||||
# no need to include all autogenerated files, we confidently recognize
|
|
||||||
# autogenerated files. If it's not please let us know.
|
|
||||||
exclude-files:
|
|
||||||
- ".*\\.md$"
|
|
||||||
- ".*\\.sh$"
|
|
||||||
- "^cmd/syncserver-integration-tests/testdata.go$"
|
|
||||||
|
|
||||||
# which dirs to skip: they won't be analyzed;
|
|
||||||
# can use regexp here: generated.*, regexp is applied on full path;
|
|
||||||
# default value is empty list, but next dirs are always skipped independently
|
|
||||||
# from this option's value:
|
|
||||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
|
||||||
exclude-dirs:
|
|
||||||
- bin
|
|
||||||
- docs
|
|
||||||
|
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
# List of regexps of issue texts to exclude, empty list by default.
|
||||||
# But independently from this option we use default exclude patterns,
|
# But independently from this option we use default exclude patterns,
|
||||||
# it can be disabled by `exclude-use-default: false`. To list all
|
# it can be disabled by `exclude-use-default: false`. To list all
|
||||||
|
|
126
CHANGES.md
126
CHANGES.md
|
@ -1,131 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Dendrite 0.13.7 (2024-04-09)
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- Fixed an issue where the displayname/avatar of an invited user was replaced with the inviter's details
|
|
||||||
- Improved server startup performance by avoiding unnecessary room ACL queries
|
|
||||||
- This change reduces memory footprint as it caches ACL regex patterns once instead of for each room
|
|
||||||
- Unnecessary Relay related queries have been removed. **Note**: To use relays, you now need to explicitly enable them using the `federation_api.enable_relays` config
|
|
||||||
- Fixed space summaries over federation
|
|
||||||
- Improved usage of external NATS JetStream by reusing existing connections instead of opening new ones unnecessarily
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Modernized Appservices (contributed by [tulir](https://github.com/tulir))
|
|
||||||
- Added event reporting with Synapse Admin endpoints for querying them
|
|
||||||
- Updated dependencies
|
|
||||||
|
|
||||||
## Dendrite 0.13.6 (2024-01-26)
|
|
||||||
|
|
||||||
Upgrading to this version is **highly** recommended, as it contains several QoL improvements.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- Use `AckExplicitPolicy` for JetStream consumers, so messages don't pile up in NATS
|
|
||||||
- A rare panic when assigning a state key NID has been fixed
|
|
||||||
- A rare panic when checking powerlevels has been fixed
|
|
||||||
- Notary keys requests for all keys now work correctly
|
|
||||||
- Spec compliance:
|
|
||||||
- Return `M_INVALID_PARAM` when querying room aliases
|
|
||||||
- Handle empty `from` parameter when requesting `/messages`
|
|
||||||
- Add CORP headers on media endpoints
|
|
||||||
- Remove `aliases` from `/publicRooms` responses
|
|
||||||
- Allow `+` in MXIDs (Contributed by [RosstheRoss](https://github.com/RosstheRoss))
|
|
||||||
- Fixes membership transitions from `knock` to `join` in `knock_restricted` rooms
|
|
||||||
- Incremental syncs now batch querying events (Contributed by [recht](https://github.com/recht))
|
|
||||||
- Move `/joined_members` back to the clientAPI/roomserver, which should make bridges happier again
|
|
||||||
- Backfilling from other servers now only uses at max 100 events instead of potentially thousands
|
|
||||||
|
|
||||||
## Dendrite 0.13.5 (2023-12-12)
|
|
||||||
|
|
||||||
Upgrading to this version is **highly** recommended, as it fixes several long-standing bugs in
|
|
||||||
our CanonicalJSON implementation.
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
- Convert unicode escapes to lowercase (gomatrixserverlib)
|
|
||||||
- Fix canonical json utf-16 surrogate pair detection logic (gomatrixserverlib)
|
|
||||||
- Handle negative zero and exponential numbers in Canonical JSON verification (gomatrixserverlib)
|
|
||||||
- Avoid logging unnecessary messages when unable to fetch server keys if multiple fetchers are used (gomatrixserverlib)
|
|
||||||
- Issues around the device list updater have been fixed, which should ensure that there are always
|
|
||||||
workers available to process incoming device list updates.
|
|
||||||
- A panic in the `/hierarchy` endpoints used for spaces has been fixed (client-server and server-server API)
|
|
||||||
- Fixes around the way we handle database transactions (including a potential connection leak)
|
|
||||||
- ACLs are now updated when received as outliers
|
|
||||||
- A race condition, which could lead to bridges instantly leaving a room after joining it, between the SyncAPI and
|
|
||||||
Appservices has been fixed
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **Appservice login is now supported!**
|
|
||||||
- Users can now kick themselves (used by some bridges)
|
|
||||||
|
|
||||||
## Dendrite 0.13.4 (2023-10-25)
|
|
||||||
|
|
||||||
Upgrading to this version is **highly** recommended, as it fixes a long-standing bug in the state resolution
|
|
||||||
algorithm.
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- The "device list updater" now de-duplicates the servers to fetch devices from on startup. (This also
|
|
||||||
avoids spamming the logs when shutting down.)
|
|
||||||
- A bug in the state resolution algorithm has been fixed. This bug could result in users "being reset"
|
|
||||||
out of rooms and other missing state events due to calculating the wrong state.
|
|
||||||
- A bug when setting notifications from Element Android has been fixed by implementing MSC3987
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Updated dependencies
|
|
||||||
- Internal NATS Server has been updated from v2.9.19 to v2.9.23
|
|
||||||
|
|
||||||
## Dendrite 0.13.3 (2023-09-28)
|
|
||||||
|
|
||||||
### Fixes:
|
|
||||||
|
|
||||||
- The `user_id` query parameter when authenticating is now used correctly (contributed by [tulir](https://github.com/tulir))
|
|
||||||
- Invitations are now correctly pushed to devices
|
|
||||||
- A bug which could result in the corruption of `m.direct` account data has been fixed
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- [Sliding Sync proxy](https://github.com/matrix-org/sliding-sync) can be configured in the `/.well-known/matrix/client` response
|
|
||||||
- Room version 11 is now supported
|
|
||||||
- Clients can request the `federation` `event_format` when creating filters
|
|
||||||
- Many under the hood improvements for [MSC4014: Pseudonymous Identities](https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/pseudo-ids/proposals/4014-pseudonymous-identities.md)
|
|
||||||
|
|
||||||
### Other
|
|
||||||
|
|
||||||
- Dendrite now requires Go 1.20 if building from source
|
|
||||||
|
|
||||||
## 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)
|
## 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.
|
This releases fixes a long-standing "off-by-one" error which could result in state resets. Upgrading to this version is **highly** recommended.
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
#
|
#
|
||||||
# base installs required dependencies and runs go mod download to cache dependencies
|
# base installs required dependencies and runs go mod download to cache dependencies
|
||||||
#
|
#
|
||||||
# Pinned to alpine3.18 until https://github.com/mattn/go-sqlite3/issues/1164 is solved
|
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.20-alpine AS base
|
||||||
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21-alpine3.18 AS base
|
|
||||||
RUN apk --update --no-cache add bash build-base curl git
|
RUN apk --update --no-cache add bash build-base curl git
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -14,6 +13,7 @@ FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG FLAGS
|
||||||
RUN --mount=target=. \
|
RUN --mount=target=. \
|
||||||
--mount=type=cache,target=/root/.cache/go-build \
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
--mount=type=cache,target=/go/pkg/mod \
|
--mount=type=cache,target=/go/pkg/mod \
|
||||||
|
@ -21,7 +21,7 @@ RUN --mount=target=. \
|
||||||
GOARCH="$TARGETARCH" \
|
GOARCH="$TARGETARCH" \
|
||||||
GOOS="linux" \
|
GOOS="linux" \
|
||||||
CGO_ENABLED=$([ "$TARGETARCH" = "$USERARCH" ] && echo "1" || echo "0") \
|
CGO_ENABLED=$([ "$TARGETARCH" = "$USERARCH" ] && echo "1" || echo "0") \
|
||||||
go build -v -trimpath -o /out/ ./cmd/...
|
go build -v -ldflags="${FLAGS}" -trimpath -o /out/ ./cmd/...
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -36,7 +36,7 @@ If you have further questions, please take a look at [our FAQ](docs/FAQ.md) or j
|
||||||
See the [Planning your Installation](https://matrix-org.github.io/dendrite/installation/planning) page for
|
See the [Planning your Installation](https://matrix-org.github.io/dendrite/installation/planning) page for
|
||||||
more information on requirements.
|
more information on requirements.
|
||||||
|
|
||||||
To build Dendrite, you will need Go 1.20 or later.
|
To build Dendrite, you will need Go 1.18 or later.
|
||||||
|
|
||||||
For a usable federating Dendrite deployment, you will also need:
|
For a usable federating Dendrite deployment, you will also need:
|
||||||
|
|
||||||
|
|
|
@ -82,17 +82,9 @@ type UserIDExistsResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ASProtocolLegacyPath = "/_matrix/app/unstable/thirdparty/protocol/"
|
ASProtocolPath = "/_matrix/app/unstable/thirdparty/protocol/"
|
||||||
ASUserLegacyPath = "/_matrix/app/unstable/thirdparty/user"
|
ASUserPath = "/_matrix/app/unstable/thirdparty/user"
|
||||||
ASLocationLegacyPath = "/_matrix/app/unstable/thirdparty/location"
|
ASLocationPath = "/_matrix/app/unstable/thirdparty/location"
|
||||||
ASRoomAliasExistsLegacyPath = "/rooms/"
|
|
||||||
ASUserExistsLegacyPath = "/users/"
|
|
||||||
|
|
||||||
ASProtocolPath = "/_matrix/app/v1/thirdparty/protocol/"
|
|
||||||
ASUserPath = "/_matrix/app/v1/thirdparty/user"
|
|
||||||
ASLocationPath = "/_matrix/app/v1/thirdparty/location"
|
|
||||||
ASRoomAliasExistsPath = "/_matrix/app/v1/rooms/"
|
|
||||||
ASUserExistsPath = "/_matrix/app/v1/users/"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProtocolRequest struct {
|
type ProtocolRequest struct {
|
||||||
|
|
|
@ -14,18 +14,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
|
||||||
"github.com/matrix-org/dendrite/syncapi"
|
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/nats-io/nats.go"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"github.com/matrix-org/dendrite/appservice"
|
||||||
"github.com/matrix-org/dendrite/appservice/api"
|
"github.com/matrix-org/dendrite/appservice/api"
|
||||||
|
@ -43,10 +32,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/test/testrig"
|
"github.com/matrix-org/dendrite/test/testrig"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
|
|
||||||
return &statistics.ServerStatistics{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppserviceInternalAPI(t *testing.T) {
|
func TestAppserviceInternalAPI(t *testing.T) {
|
||||||
|
|
||||||
// Set expected results
|
// Set expected results
|
||||||
|
@ -159,7 +144,7 @@ func TestAppserviceInternalAPI(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
|
||||||
|
|
||||||
runCases(t, asAPI)
|
runCases(t, asAPI)
|
||||||
|
@ -254,7 +239,7 @@ func TestAppserviceInternalAPI_UnixSocket_Simple(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(ctx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(ctx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
usrAPI := userapi.NewInternalAPI(ctx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(ctx, cfg, &natsInstance, usrAPI, rsAPI)
|
||||||
|
|
||||||
t.Run("UserIDExists", func(t *testing.T) {
|
t.Run("UserIDExists", func(t *testing.T) {
|
||||||
|
@ -393,7 +378,7 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
|
||||||
// Create required internal APIs
|
// Create required internal APIs
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil)
|
||||||
// start the consumer
|
// start the consumer
|
||||||
appservice.NewInternalAPI(processCtx, cfg, natsInstance, usrAPI, rsAPI)
|
appservice.NewInternalAPI(processCtx, cfg, natsInstance, usrAPI, rsAPI)
|
||||||
|
|
||||||
|
@ -417,190 +402,3 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
|
||||||
close(evChan)
|
close(evChan)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: If this test panics, it is because we timed out waiting for the
|
|
||||||
// join event to come through to the appservice and we close the DB/shutdown Dendrite. This makes the
|
|
||||||
// syncAPI unhappy, as it is unable to write to the database.
|
|
||||||
func TestOutputAppserviceEvent(t *testing.T) {
|
|
||||||
alice := test.NewUser(t)
|
|
||||||
bob := test.NewUser(t)
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
|
|
||||||
defer closeDB()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
natsInstance := &jetstream.NATSInstance{}
|
|
||||||
|
|
||||||
evChan := make(chan struct{})
|
|
||||||
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
// Create required internal APIs
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
|
||||||
|
|
||||||
// Create the router, so we can hit `/joined_members`
|
|
||||||
routers := httputil.NewRouters()
|
|
||||||
|
|
||||||
accessTokens := map[*test.User]userDevice{
|
|
||||||
bob: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
usrAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
|
||||||
clientapi.AddPublicRoutes(processCtx, routers, cfg, natsInstance, nil, rsAPI, nil, nil, nil, usrAPI, nil, nil, caching.DisableMetrics)
|
|
||||||
createAccessTokens(t, accessTokens, usrAPI, processCtx.Context(), routers)
|
|
||||||
|
|
||||||
room := test.NewRoom(t, alice)
|
|
||||||
|
|
||||||
// Invite Bob
|
|
||||||
room.CreateAndInsert(t, alice, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "invite",
|
|
||||||
}, test.WithStateKey(bob.ID))
|
|
||||||
|
|
||||||
// create a dummy AS url, handling the events
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var txn consumers.ApplicationServiceTransaction
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&txn)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, ev := range txn.Events {
|
|
||||||
if ev.Type != spec.MRoomMember {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ev.StateKey != nil && *ev.StateKey == bob.ID {
|
|
||||||
membership := gjson.GetBytes(ev.Content, "membership").Str
|
|
||||||
t.Logf("Processing membership: %s", membership)
|
|
||||||
switch membership {
|
|
||||||
case spec.Invite:
|
|
||||||
// Accept the invite
|
|
||||||
joinEv := room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "join",
|
|
||||||
}, test.WithStateKey(bob.ID))
|
|
||||||
|
|
||||||
if err := rsapi.SendEvents(context.Background(), rsAPI, rsapi.KindNew, []*types.HeaderedEvent{joinEv}, "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
case spec.Join: // the AS has received the join event, now hit `/joined_members` to validate that
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/_matrix/client/v3/rooms/"+room.ID+"/joined_members", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[bob].accessToken)
|
|
||||||
routers.Client.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected HTTP 200, got %d: %s", rec.Code, rec.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both Alice and Bob should be joined. If not, we have a race condition
|
|
||||||
if !gjson.GetBytes(rec.Body.Bytes(), "joined."+alice.ID).Exists() {
|
|
||||||
t.Errorf("Alice is not joined to the room") // in theory should not happen
|
|
||||||
}
|
|
||||||
if !gjson.GetBytes(rec.Body.Bytes(), "joined."+bob.ID).Exists() {
|
|
||||||
t.Errorf("Bob is not joined to the room")
|
|
||||||
}
|
|
||||||
evChan <- struct{}{}
|
|
||||||
default:
|
|
||||||
t.Fatalf("Unexpected membership: %s", membership)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
as := &config.ApplicationService{
|
|
||||||
ID: "someID",
|
|
||||||
URL: srv.URL,
|
|
||||||
ASToken: "",
|
|
||||||
HSToken: "",
|
|
||||||
SenderLocalpart: "senderLocalPart",
|
|
||||||
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
|
|
||||||
"users": {{RegexpObject: regexp.MustCompile(bob.ID)}},
|
|
||||||
"aliases": {{RegexpObject: regexp.MustCompile(room.ID)}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
as.CreateHTTPClient(cfg.AppServiceAPI.DisableTLSValidation)
|
|
||||||
|
|
||||||
// Create a dummy application service
|
|
||||||
cfg.AppServiceAPI.Derived.ApplicationServices = []config.ApplicationService{*as}
|
|
||||||
|
|
||||||
// Prepare AS Streams on the old topic to validate that they get deleted
|
|
||||||
jsCtx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
|
||||||
|
|
||||||
token := jetstream.Tokenise(as.ID)
|
|
||||||
if err := jetstream.JetStreamConsumer(
|
|
||||||
processCtx.Context(), jsCtx, cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent),
|
|
||||||
cfg.Global.JetStream.Durable("Appservice_"+token),
|
|
||||||
50, // maximum number of events to send in a single transaction
|
|
||||||
func(ctx context.Context, msgs []*nats.Msg) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the syncAPI to have `/joined_members` available
|
|
||||||
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, natsInstance, usrAPI, rsAPI, caches, caching.DisableMetrics)
|
|
||||||
|
|
||||||
// start the consumer
|
|
||||||
appservice.NewInternalAPI(processCtx, cfg, natsInstance, usrAPI, rsAPI)
|
|
||||||
|
|
||||||
// At this point, the old JetStream consumers should be deleted
|
|
||||||
for consumer := range jsCtx.Consumers(cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent)) {
|
|
||||||
if consumer.Name == cfg.Global.JetStream.Durable("Appservice_"+token)+"Pull" {
|
|
||||||
t.Fatalf("Consumer still exists")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the room, this triggers the AS to receive an invite for Bob.
|
|
||||||
if err := rsapi.SendEvents(context.Background(), rsAPI, rsapi.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
// Pretty generous timeout duration...
|
|
||||||
case <-time.After(time.Millisecond * 1000): // wait for the AS to process the events
|
|
||||||
t.Errorf("Timed out waiting for join event")
|
|
||||||
case <-evChan:
|
|
||||||
}
|
|
||||||
close(evChan)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type userDevice struct {
|
|
||||||
accessToken string
|
|
||||||
deviceID string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAccessTokens(t *testing.T, accessTokens map[*test.User]userDevice, userAPI uapi.UserInternalAPI, ctx context.Context, routers httputil.Routers) {
|
|
||||||
t.Helper()
|
|
||||||
for u := range accessTokens {
|
|
||||||
localpart, serverName, _ := gomatrixserverlib.SplitID('@', u.ID)
|
|
||||||
userRes := &uapi.PerformAccountCreationResponse{}
|
|
||||||
password := util.RandomString(8)
|
|
||||||
if err := userAPI.PerformAccountCreation(ctx, &uapi.PerformAccountCreationRequest{
|
|
||||||
AccountType: u.AccountType,
|
|
||||||
Localpart: localpart,
|
|
||||||
ServerName: serverName,
|
|
||||||
Password: password,
|
|
||||||
}, userRes); err != nil {
|
|
||||||
t.Errorf("failed to create account: %s", err)
|
|
||||||
}
|
|
||||||
req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
|
|
||||||
"type": authtypes.LoginTypePassword,
|
|
||||||
"identifier": map[string]interface{}{
|
|
||||||
"type": "m.id.user",
|
|
||||||
"user": u.ID,
|
|
||||||
},
|
|
||||||
"password": password,
|
|
||||||
}))
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
routers.Client.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("failed to login: %s", rec.Body.String())
|
|
||||||
}
|
|
||||||
accessTokens[u] = userDevice{
|
|
||||||
accessToken: gjson.GetBytes(rec.Body.Bytes(), "access_token").String(),
|
|
||||||
deviceID: gjson.GetBytes(rec.Body.Bytes(), "device_id").String(),
|
|
||||||
password: password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -71,14 +71,13 @@ func NewOutputRoomEventConsumer(
|
||||||
ctx: process.Context(),
|
ctx: process.Context(),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
jetstream: js,
|
jetstream: js,
|
||||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputAppserviceEvent),
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
|
||||||
rsAPI: rsAPI,
|
rsAPI: rsAPI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start consuming from room servers
|
// Start consuming from room servers
|
||||||
func (s *OutputRoomEventConsumer) Start() error {
|
func (s *OutputRoomEventConsumer) Start() error {
|
||||||
durableNames := make([]string, 0, len(s.cfg.Derived.ApplicationServices))
|
|
||||||
for _, as := range s.cfg.Derived.ApplicationServices {
|
for _, as := range s.cfg.Derived.ApplicationServices {
|
||||||
appsvc := as
|
appsvc := as
|
||||||
state := &appserviceState{
|
state := &appserviceState{
|
||||||
|
@ -96,15 +95,6 @@ func (s *OutputRoomEventConsumer) Start() error {
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("failed to create %q consumer: %w", token, err)
|
return fmt.Errorf("failed to create %q consumer: %w", token, err)
|
||||||
}
|
}
|
||||||
durableNames = append(durableNames, s.cfg.Matrix.JetStream.Durable("Appservice_"+token))
|
|
||||||
}
|
|
||||||
// Cleanup any consumers still existing on the OutputRoomEvent stream
|
|
||||||
// to avoid messages not being deleted
|
|
||||||
for _, consumerName := range durableNames {
|
|
||||||
err := s.jetstream.DeleteConsumer(s.cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent), consumerName+"Pull")
|
|
||||||
if err != nil && err != nats.ErrConsumerNotFound {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -138,7 +128,7 @@ func (s *OutputRoomEventConsumer) onMessage(
|
||||||
if len(output.NewRoomEvent.AddsStateEventIDs) > 0 {
|
if len(output.NewRoomEvent.AddsStateEventIDs) > 0 {
|
||||||
newEventID := output.NewRoomEvent.Event.EventID()
|
newEventID := output.NewRoomEvent.Event.EventID()
|
||||||
eventsReq := &api.QueryEventsByIDRequest{
|
eventsReq := &api.QueryEventsByIDRequest{
|
||||||
RoomID: output.NewRoomEvent.Event.RoomID().String(),
|
RoomID: output.NewRoomEvent.Event.RoomID(),
|
||||||
EventIDs: make([]string, 0, len(output.NewRoomEvent.AddsStateEventIDs)),
|
EventIDs: make([]string, 0, len(output.NewRoomEvent.AddsStateEventIDs)),
|
||||||
}
|
}
|
||||||
eventsRes := &api.QueryEventsByIDResponse{}
|
eventsRes := &api.QueryEventsByIDResponse{}
|
||||||
|
@ -206,21 +196,13 @@ func (s *OutputRoomEventConsumer) sendEvents(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the transaction to the appservice.
|
// Send the transaction to the appservice.
|
||||||
// https://spec.matrix.org/v1.9/application-service-api/#pushing-events
|
// https://matrix.org/docs/spec/application_service/r0.1.2#put-matrix-app-v1-transactions-txnid
|
||||||
path := "_matrix/app/v1/transactions"
|
address := fmt.Sprintf("%s/transactions/%s?access_token=%s", state.RequestUrl(), txnID, url.QueryEscape(state.HSToken))
|
||||||
if s.cfg.LegacyPaths {
|
|
||||||
path = "transactions"
|
|
||||||
}
|
|
||||||
address := fmt.Sprintf("%s/%s/%s", state.RequestUrl(), path, txnID)
|
|
||||||
if s.cfg.LegacyAuth {
|
|
||||||
address += "?access_token=" + url.QueryEscape(state.HSToken)
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "PUT", address, bytes.NewBuffer(transaction))
|
req, err := http.NewRequestWithContext(ctx, "PUT", address, bytes.NewBuffer(transaction))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", state.HSToken))
|
|
||||||
resp, err := state.HTTPClient.Do(req)
|
resp, err := state.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return state.backoffAndPause(err)
|
return state.backoffAndPause(err)
|
||||||
|
@ -254,7 +236,11 @@ func (s *appserviceState) backoffAndPause(err error) error {
|
||||||
// TODO: This should be cached, see https://github.com/matrix-org/dendrite/issues/1682
|
// TODO: This should be cached, see https://github.com/matrix-org/dendrite/issues/1682
|
||||||
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event *types.HeaderedEvent, appservice *config.ApplicationService) bool {
|
func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Context, event *types.HeaderedEvent, appservice *config.ApplicationService) bool {
|
||||||
user := ""
|
user := ""
|
||||||
userID, err := s.rsAPI.QueryUserIDForSender(ctx, event.RoomID(), event.SenderID())
|
validRoomID, err := spec.NewRoomID(event.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
userID, err := s.rsAPI.QueryUserIDForSender(ctx, *validRoomID, event.SenderID())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
user = userID.String()
|
user = userID.String()
|
||||||
}
|
}
|
||||||
|
@ -264,7 +250,7 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
|
||||||
return false
|
return false
|
||||||
case appservice.IsInterestedInUserID(user):
|
case appservice.IsInterestedInUserID(user):
|
||||||
return true
|
return true
|
||||||
case appservice.IsInterestedInRoomID(event.RoomID().String()):
|
case appservice.IsInterestedInRoomID(event.RoomID()):
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +261,7 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all known room aliases of the room the event came from
|
// Check all known room aliases of the room the event came from
|
||||||
queryReq := api.GetAliasesForRoomIDRequest{RoomID: event.RoomID().String()}
|
queryReq := api.GetAliasesForRoomIDRequest{RoomID: event.RoomID()}
|
||||||
var queryRes api.GetAliasesForRoomIDResponse
|
var queryRes api.GetAliasesForRoomIDResponse
|
||||||
if err := s.rsAPI.GetAliasesForRoomID(ctx, &queryReq, &queryRes); err == nil {
|
if err := s.rsAPI.GetAliasesForRoomID(ctx, &queryReq, &queryRes); err == nil {
|
||||||
for _, alias := range queryRes.Aliases {
|
for _, alias := range queryRes.Aliases {
|
||||||
|
@ -286,7 +272,7 @@ func (s *OutputRoomEventConsumer) appserviceIsInterestedInEvent(ctx context.Cont
|
||||||
} else {
|
} else {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"appservice": appservice.ID,
|
"appservice": appservice.ID,
|
||||||
"room_id": event.RoomID().String(),
|
"room_id": event.RoomID(),
|
||||||
}).WithError(err).Errorf("Unable to get aliases for room")
|
}).WithError(err).Errorf("Unable to get aliases for room")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +288,7 @@ func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, e
|
||||||
// until we have a lighter way of checking the state before the event that
|
// until we have a lighter way of checking the state before the event that
|
||||||
// doesn't involve state res, then this is probably OK.
|
// doesn't involve state res, then this is probably OK.
|
||||||
membershipReq := &api.QueryMembershipsForRoomRequest{
|
membershipReq := &api.QueryMembershipsForRoomRequest{
|
||||||
RoomID: event.RoomID().String(),
|
RoomID: event.RoomID(),
|
||||||
JoinedOnly: true,
|
JoinedOnly: true,
|
||||||
}
|
}
|
||||||
membershipRes := &api.QueryMembershipsForRoomResponse{}
|
membershipRes := &api.QueryMembershipsForRoomResponse{}
|
||||||
|
@ -331,7 +317,7 @@ func (s *OutputRoomEventConsumer) appserviceJoinedAtEvent(ctx context.Context, e
|
||||||
} else {
|
} else {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"appservice": appservice.ID,
|
"appservice": appservice.ID,
|
||||||
"room_id": event.RoomID().String(),
|
"room_id": event.RoomID(),
|
||||||
}).WithError(err).Errorf("Unable to get membership for room")
|
}).WithError(err).Errorf("Unable to get membership for room")
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -19,10 +19,10 @@ package query
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -32,6 +32,9 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const roomAliasExistsPath = "/rooms/"
|
||||||
|
const userIDExistsPath = "/users/"
|
||||||
|
|
||||||
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
||||||
type AppServiceQueryAPI struct {
|
type AppServiceQueryAPI struct {
|
||||||
Cfg *config.AppServiceAPI
|
Cfg *config.AppServiceAPI
|
||||||
|
@ -52,23 +55,14 @@ func (a *AppServiceQueryAPI) RoomAliasExists(
|
||||||
// Determine which application service should handle this request
|
// Determine which application service should handle this request
|
||||||
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
||||||
if appservice.URL != "" && appservice.IsInterestedInRoomAlias(request.Alias) {
|
if appservice.URL != "" && appservice.IsInterestedInRoomAlias(request.Alias) {
|
||||||
path := api.ASRoomAliasExistsPath
|
|
||||||
if a.Cfg.LegacyPaths {
|
|
||||||
path = api.ASRoomAliasExistsLegacyPath
|
|
||||||
}
|
|
||||||
// The full path to the rooms API, includes hs token
|
// The full path to the rooms API, includes hs token
|
||||||
URL, err := url.Parse(appservice.RequestUrl() + path)
|
URL, err := url.Parse(appservice.RequestUrl() + roomAliasExistsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
URL.Path += request.Alias
|
URL.Path += request.Alias
|
||||||
if a.Cfg.LegacyAuth {
|
apiURL := URL.String() + "?access_token=" + appservice.HSToken
|
||||||
q := URL.Query()
|
|
||||||
q.Set("access_token", appservice.HSToken)
|
|
||||||
URL.RawQuery = q.Encode()
|
|
||||||
}
|
|
||||||
apiURL := URL.String()
|
|
||||||
|
|
||||||
// Send a request to each application service. If one responds that it has
|
// Send a request to each application service. If one responds that it has
|
||||||
// created the room, immediately return.
|
// created the room, immediately return.
|
||||||
|
@ -76,7 +70,6 @@ func (a *AppServiceQueryAPI) RoomAliasExists(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", appservice.HSToken))
|
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
resp, err := appservice.HTTPClient.Do(req)
|
resp, err := appservice.HTTPClient.Do(req)
|
||||||
|
@ -130,21 +123,12 @@ func (a *AppServiceQueryAPI) UserIDExists(
|
||||||
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
for _, appservice := range a.Cfg.Derived.ApplicationServices {
|
||||||
if appservice.URL != "" && appservice.IsInterestedInUserID(request.UserID) {
|
if appservice.URL != "" && appservice.IsInterestedInUserID(request.UserID) {
|
||||||
// The full path to the rooms API, includes hs token
|
// The full path to the rooms API, includes hs token
|
||||||
path := api.ASUserExistsPath
|
URL, err := url.Parse(appservice.RequestUrl() + userIDExistsPath)
|
||||||
if a.Cfg.LegacyPaths {
|
|
||||||
path = api.ASUserExistsLegacyPath
|
|
||||||
}
|
|
||||||
URL, err := url.Parse(appservice.RequestUrl() + path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
URL.Path += request.UserID
|
URL.Path += request.UserID
|
||||||
if a.Cfg.LegacyAuth {
|
apiURL := URL.String() + "?access_token=" + appservice.HSToken
|
||||||
q := URL.Query()
|
|
||||||
q.Set("access_token", appservice.HSToken)
|
|
||||||
URL.RawQuery = q.Encode()
|
|
||||||
}
|
|
||||||
apiURL := URL.String()
|
|
||||||
|
|
||||||
// Send a request to each application service. If one responds that it has
|
// Send a request to each application service. If one responds that it has
|
||||||
// created the user, immediately return.
|
// created the user, immediately return.
|
||||||
|
@ -152,7 +136,6 @@ func (a *AppServiceQueryAPI) UserIDExists(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", appservice.HSToken))
|
|
||||||
resp, err := appservice.HTTPClient.Do(req.WithContext(ctx))
|
resp, err := appservice.HTTPClient.Do(req.WithContext(ctx))
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -193,22 +176,25 @@ type thirdpartyResponses interface {
|
||||||
api.ASProtocolResponse | []api.ASUserResponse | []api.ASLocationResponse
|
api.ASProtocolResponse | []api.ASUserResponse | []api.ASLocationResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestDo[T thirdpartyResponses](as *config.ApplicationService, url string, response *T) error {
|
func requestDo[T thirdpartyResponses](client *http.Client, url string, response *T) (err error) {
|
||||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
origURL := url
|
||||||
if err != nil {
|
// try v1 and unstable appservice endpoints
|
||||||
return err
|
for _, version := range []string{"v1", "unstable"} {
|
||||||
|
var resp *http.Response
|
||||||
|
var body []byte
|
||||||
|
asURL := strings.Replace(origURL, "unstable", version, 1)
|
||||||
|
resp, err = client.Get(asURL)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close() // nolint: errcheck
|
||||||
|
body, err = io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return json.Unmarshal(body, &response)
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", as.HSToken))
|
return err
|
||||||
resp, err := as.HTTPClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close() // nolint: errcheck
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return json.Unmarshal(body, &response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppServiceQueryAPI) Locations(
|
func (a *AppServiceQueryAPI) Locations(
|
||||||
|
@ -221,23 +207,17 @@ func (a *AppServiceQueryAPI) Locations(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := api.ASLocationPath
|
|
||||||
if a.Cfg.LegacyPaths {
|
|
||||||
path = api.ASLocationLegacyPath
|
|
||||||
}
|
|
||||||
for _, as := range a.Cfg.Derived.ApplicationServices {
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
var asLocations []api.ASLocationResponse
|
var asLocations []api.ASLocationResponse
|
||||||
if a.Cfg.LegacyAuth {
|
params.Set("access_token", as.HSToken)
|
||||||
params.Set("access_token", as.HSToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := as.RequestUrl() + path
|
url := as.RequestUrl() + api.ASLocationPath
|
||||||
if req.Protocol != "" {
|
if req.Protocol != "" {
|
||||||
url += "/" + req.Protocol
|
url += "/" + req.Protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := requestDo[[]api.ASLocationResponse](&as, url+"?"+params.Encode(), &asLocations); err != nil {
|
if err := requestDo[[]api.ASLocationResponse](as.HTTPClient, url+"?"+params.Encode(), &asLocations); err != nil {
|
||||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'locations' from application service")
|
log.WithError(err).Error("unable to get 'locations' from application service")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,23 +242,17 @@ func (a *AppServiceQueryAPI) User(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := api.ASUserPath
|
|
||||||
if a.Cfg.LegacyPaths {
|
|
||||||
path = api.ASUserLegacyPath
|
|
||||||
}
|
|
||||||
for _, as := range a.Cfg.Derived.ApplicationServices {
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
var asUsers []api.ASUserResponse
|
var asUsers []api.ASUserResponse
|
||||||
if a.Cfg.LegacyAuth {
|
params.Set("access_token", as.HSToken)
|
||||||
params.Set("access_token", as.HSToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := as.RequestUrl() + path
|
url := as.RequestUrl() + api.ASUserPath
|
||||||
if req.Protocol != "" {
|
if req.Protocol != "" {
|
||||||
url += "/" + req.Protocol
|
url += "/" + req.Protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := requestDo[[]api.ASUserResponse](&as, url+"?"+params.Encode(), &asUsers); err != nil {
|
if err := requestDo[[]api.ASUserResponse](as.HTTPClient, url+"?"+params.Encode(), &asUsers); err != nil {
|
||||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'user' from application service")
|
log.WithError(err).Error("unable to get 'user' from application service")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,10 +272,6 @@ func (a *AppServiceQueryAPI) Protocols(
|
||||||
req *api.ProtocolRequest,
|
req *api.ProtocolRequest,
|
||||||
resp *api.ProtocolResponse,
|
resp *api.ProtocolResponse,
|
||||||
) error {
|
) error {
|
||||||
protocolPath := api.ASProtocolPath
|
|
||||||
if a.Cfg.LegacyPaths {
|
|
||||||
protocolPath = api.ASProtocolLegacyPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// get a single protocol response
|
// get a single protocol response
|
||||||
if req.Protocol != "" {
|
if req.Protocol != "" {
|
||||||
|
@ -319,8 +289,8 @@ func (a *AppServiceQueryAPI) Protocols(
|
||||||
response := api.ASProtocolResponse{}
|
response := api.ASProtocolResponse{}
|
||||||
for _, as := range a.Cfg.Derived.ApplicationServices {
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
var proto api.ASProtocolResponse
|
var proto api.ASProtocolResponse
|
||||||
if err := requestDo[api.ASProtocolResponse](&as, as.RequestUrl()+protocolPath+req.Protocol, &proto); err != nil {
|
if err := requestDo[api.ASProtocolResponse](as.HTTPClient, as.RequestUrl()+api.ASProtocolPath+req.Protocol, &proto); err != nil {
|
||||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'protocol' from application service")
|
log.WithError(err).Error("unable to get 'protocol' from application service")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,8 +319,8 @@ func (a *AppServiceQueryAPI) Protocols(
|
||||||
for _, as := range a.Cfg.Derived.ApplicationServices {
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
for _, p := range as.Protocols {
|
for _, p := range as.Protocols {
|
||||||
var proto api.ASProtocolResponse
|
var proto api.ASProtocolResponse
|
||||||
if err := requestDo[api.ASProtocolResponse](&as, as.RequestUrl()+protocolPath+p, &proto); err != nil {
|
if err := requestDo[api.ASProtocolResponse](as.HTTPClient, as.RequestUrl()+api.ASProtocolPath+p, &proto); err != nil {
|
||||||
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'protocol' from application service")
|
log.WithError(err).Error("unable to get 'protocol' from application service")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
existing, ok := response[p]
|
existing, ok := response[p]
|
||||||
|
|
|
@ -944,12 +944,4 @@ rmv remote user can join room with version 10
|
||||||
rmv User can invite remote user to room with version 10
|
rmv User can invite remote user to room with version 10
|
||||||
rmv Remote user can backfill in a room with version 10
|
rmv Remote user can backfill in a room with version 10
|
||||||
rmv Can reject invites over federation for rooms with version 10
|
rmv Can reject invites over federation for rooms with version 10
|
||||||
rmv Can receive redactions from regular users over federation in room version 10
|
rmv Can receive redactions from regular users over federation in room version 10
|
||||||
rmv User can create and send/receive messages in a room with version 11
|
|
||||||
rmv local user can join room with version 11
|
|
||||||
rmv User can invite local user to room with version 11
|
|
||||||
rmv remote user can join room with version 11
|
|
||||||
rmv User can invite remote user to room with version 11
|
|
||||||
rmv Remote user can backfill in a room with version 11
|
|
||||||
rmv Can reject invites over federation for rooms with version 11
|
|
||||||
rmv Can receive redactions from regular users over federation in room version 11
|
|
|
@ -38,7 +38,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
@ -191,13 +190,13 @@ func startup() {
|
||||||
serverKeyAPI := &signing.YggdrasilKeys{}
|
serverKeyAPI := &signing.YggdrasilKeys{}
|
||||||
keyRing := serverKeyAPI.KeyRing()
|
keyRing := serverKeyAPI.KeyRing()
|
||||||
|
|
||||||
fedSenderAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, caching.EnableMetrics, fedSenderAPI.IsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
asQuery := appservice.NewInternalAPI(
|
asQuery := appservice.NewInternalAPI(
|
||||||
processCtx, cfg, &natsInstance, userAPI, rsAPI,
|
processCtx, cfg, &natsInstance, userAPI, rsAPI,
|
||||||
)
|
)
|
||||||
rsAPI.SetAppserviceAPI(asQuery)
|
rsAPI.SetAppserviceAPI(asQuery)
|
||||||
|
fedSenderAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true)
|
||||||
rsAPI.SetFederationAPI(fedSenderAPI, keyRing)
|
rsAPI.SetFederationAPI(fedSenderAPI, keyRing)
|
||||||
|
|
||||||
monolith := setup.Monolith{
|
monolith := setup.Monolith{
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# Pinned to alpine3.18 until https://github.com/mattn/go-sqlite3/issues/1164 is solved
|
FROM docker.io/golang:1.19-alpine AS base
|
||||||
FROM docker.io/golang:1.21-alpine3.18 AS base
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Needs to be separate from the main Dockerfile for OpenShift,
|
# Needs to be separate from the main Dockerfile for OpenShift,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# Pinned to alpine3.18 until https://github.com/mattn/go-sqlite3/issues/1164 is solved
|
FROM docker.io/golang:1.19-alpine AS base
|
||||||
FROM docker.io/golang:1.21-alpine3.18 AS base
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Needs to be separate from the main Dockerfile for OpenShift,
|
# Needs to be separate from the main Dockerfile for OpenShift,
|
||||||
|
|
|
@ -216,7 +216,7 @@ func (m *DendriteMonolith) Start() {
|
||||||
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
|
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
|
||||||
)
|
)
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, caching.EnableMetrics, fsAPI.IsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation)
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||||
rsAPI.SetAppserviceAPI(asAPI)
|
rsAPI.SetAppserviceAPI(asAPI)
|
||||||
|
|
|
@ -15,5 +15,5 @@ tar -xzf master.tar.gz
|
||||||
|
|
||||||
# Run the tests!
|
# Run the tests!
|
||||||
cd complement-master
|
cd complement-master
|
||||||
COMPLEMENT_BASE_IMAGE=complement-dendrite:latest go test -v -count=1 ./tests ./tests/csapi
|
COMPLEMENT_BASE_IMAGE=complement-dendrite:latest go test -v -count=1 ./tests
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,10 @@ package clientapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ func TestAdminCreateToken(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
accessTokens := map[*test.User]userDevice{
|
accessTokens := map[*test.User]userDevice{
|
||||||
aliceAdmin: {},
|
aliceAdmin: {},
|
||||||
|
@ -198,7 +196,7 @@ func TestAdminListRegistrationTokens(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
accessTokens := map[*test.User]userDevice{
|
accessTokens := map[*test.User]userDevice{
|
||||||
aliceAdmin: {},
|
aliceAdmin: {},
|
||||||
|
@ -316,7 +314,7 @@ func TestAdminGetRegistrationToken(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
accessTokens := map[*test.User]userDevice{
|
accessTokens := map[*test.User]userDevice{
|
||||||
aliceAdmin: {},
|
aliceAdmin: {},
|
||||||
|
@ -417,7 +415,7 @@ func TestAdminDeleteRegistrationToken(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
accessTokens := map[*test.User]userDevice{
|
accessTokens := map[*test.User]userDevice{
|
||||||
aliceAdmin: {},
|
aliceAdmin: {},
|
||||||
|
@ -511,7 +509,7 @@ func TestAdminUpdateRegistrationToken(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
accessTokens := map[*test.User]userDevice{
|
accessTokens := map[*test.User]userDevice{
|
||||||
aliceAdmin: {},
|
aliceAdmin: {},
|
||||||
|
@ -695,7 +693,7 @@ func TestAdminResetPassword(t *testing.T) {
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
// Needed for changing the password/login
|
// Needed for changing the password/login
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
// We mostly need the userAPI for this test, so nil for other APIs/caches etc.
|
// We mostly need the userAPI 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
|
@ -793,7 +791,7 @@ func TestPurgeRoom(t *testing.T) {
|
||||||
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
||||||
rsAPI.SetFederationAPI(fsAPI, nil)
|
rsAPI.SetFederationAPI(fsAPI, nil)
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics)
|
syncapi.AddPublicRoutes(processCtx, routers, cfg, cm, &natsInstance, userAPI, rsAPI, caches, caching.DisableMetrics)
|
||||||
|
|
||||||
// Create the room
|
// Create the room
|
||||||
|
@ -865,7 +863,7 @@ func TestAdminEvacuateRoom(t *testing.T) {
|
||||||
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, rsAPI, caches, nil, true)
|
||||||
rsAPI.SetFederationAPI(fsAPI, nil)
|
rsAPI.SetFederationAPI(fsAPI, nil)
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// Create the room
|
// Create the room
|
||||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
|
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
|
||||||
|
@ -966,7 +964,7 @@ func TestAdminEvacuateUser(t *testing.T) {
|
||||||
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, basepkg.CreateFederationClient(cfg, nil), rsAPI, caches, nil, true)
|
fsAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, basepkg.CreateFederationClient(cfg, nil), rsAPI, caches, nil, true)
|
||||||
rsAPI.SetFederationAPI(fsAPI, nil)
|
rsAPI.SetFederationAPI(fsAPI, nil)
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// Create the room
|
// Create the room
|
||||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
|
if err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", api.DoNotSendToOtherServers, nil, false); err != nil {
|
||||||
|
@ -1057,7 +1055,7 @@ func TestAdminMarkAsStale(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -1094,382 +1092,3 @@ func TestAdminMarkAsStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdminQueryEventReports(t *testing.T) {
|
|
||||||
alice := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
|
||||||
bob := test.NewUser(t)
|
|
||||||
room := test.NewRoom(t, alice)
|
|
||||||
room2 := test.NewRoom(t, alice)
|
|
||||||
|
|
||||||
// room2 has a name and canonical alias
|
|
||||||
room2.CreateAndInsert(t, alice, spec.MRoomName, map[string]string{"name": "Testing"}, test.WithStateKey(""))
|
|
||||||
room2.CreateAndInsert(t, alice, spec.MRoomCanonicalAlias, map[string]string{"alias": "#testing"}, test.WithStateKey(""))
|
|
||||||
|
|
||||||
// Join the rooms with Bob
|
|
||||||
room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "join",
|
|
||||||
}, test.WithStateKey(bob.ID))
|
|
||||||
room2.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "join",
|
|
||||||
}, test.WithStateKey(bob.ID))
|
|
||||||
|
|
||||||
// Create a few events to report
|
|
||||||
eventsToReportPerRoom := make(map[string][]string)
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
ev1 := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "hello world"})
|
|
||||||
ev2 := room2.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "hello world"})
|
|
||||||
eventsToReportPerRoom[room.ID] = append(eventsToReportPerRoom[room.ID], ev1.EventID())
|
|
||||||
eventsToReportPerRoom[room2.ID] = append(eventsToReportPerRoom[room2.ID], ev2.EventID())
|
|
||||||
}
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
/*if dbType == test.DBTypeSQLite {
|
|
||||||
t.Skip()
|
|
||||||
}*/
|
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
|
||||||
routers := httputil.NewRouters()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
defer close()
|
|
||||||
natsInstance := jetstream.NATSInstance{}
|
|
||||||
jsctx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
|
||||||
defer jetstream.DeleteAllStreams(jsctx, &cfg.Global.JetStream)
|
|
||||||
|
|
||||||
// Use an actual roomserver for this
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
if err := api.SendEvents(context.Background(), rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
if err := api.SendEvents(context.Background(), rsAPI, api.KindNew, room2.Events(), "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
|
||||||
|
|
||||||
accessTokens := map[*test.User]userDevice{
|
|
||||||
alice: {},
|
|
||||||
bob: {},
|
|
||||||
}
|
|
||||||
createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
|
|
||||||
|
|
||||||
reqBody := map[string]any{
|
|
||||||
"reason": "baaad",
|
|
||||||
"score": -100,
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
// Report all events
|
|
||||||
for roomID, eventIDs := range eventsToReportPerRoom {
|
|
||||||
for _, eventID := range eventIDs {
|
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/report/%s", roomID, eventID), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[bob].accessToken)
|
|
||||||
|
|
||||||
routers.Client.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected report to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type response struct {
|
|
||||||
EventReports []api.QueryAdminEventReportsResponse `json:"event_reports"`
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
NextToken *int64 `json:"next_token,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Can query all reports", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, "/_synapse/admin/v1/event_reports", strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting reports to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
var resp response
|
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantCount := 20
|
|
||||||
// Only validating the count
|
|
||||||
if len(resp.EventReports) != wantCount {
|
|
||||||
t.Fatalf("expected %d events, got %d", wantCount, len(resp.EventReports))
|
|
||||||
}
|
|
||||||
if resp.Total != int64(wantCount) {
|
|
||||||
t.Fatalf("expected total to be %d, got %d", wantCount, resp.Total)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can filter on room", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/_synapse/admin/v1/event_reports?room_id=%s", room.ID), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting reports to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
var resp response
|
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantCount := 10
|
|
||||||
// Only validating the count
|
|
||||||
if len(resp.EventReports) != wantCount {
|
|
||||||
t.Fatalf("expected %d events, got %d", wantCount, len(resp.EventReports))
|
|
||||||
}
|
|
||||||
if resp.Total != int64(wantCount) {
|
|
||||||
t.Fatalf("expected total to be %d, got %d", wantCount, resp.Total)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can filter on user_id", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/_synapse/admin/v1/event_reports?user_id=%s", "@doesnotexist:test"), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting reports to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
var resp response
|
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user does not exist, so we expect no results
|
|
||||||
wantCount := 0
|
|
||||||
// Only validating the count
|
|
||||||
if len(resp.EventReports) != wantCount {
|
|
||||||
t.Fatalf("expected %d events, got %d", wantCount, len(resp.EventReports))
|
|
||||||
}
|
|
||||||
if resp.Total != int64(wantCount) {
|
|
||||||
t.Fatalf("expected total to be %d, got %d", wantCount, resp.Total)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can set direction=f", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/_synapse/admin/v1/event_reports?room_id=%s&dir=f", room.ID), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting reports to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
var resp response
|
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
wantCount := 10
|
|
||||||
// Only validating the count
|
|
||||||
if len(resp.EventReports) != wantCount {
|
|
||||||
t.Fatalf("expected %d events, got %d", wantCount, len(resp.EventReports))
|
|
||||||
}
|
|
||||||
if resp.Total != int64(wantCount) {
|
|
||||||
t.Fatalf("expected total to be %d, got %d", wantCount, resp.Total)
|
|
||||||
}
|
|
||||||
// we now should have the first reported event
|
|
||||||
wantEventID := eventsToReportPerRoom[room.ID][0]
|
|
||||||
gotEventID := resp.EventReports[0].EventID
|
|
||||||
if gotEventID != wantEventID {
|
|
||||||
t.Fatalf("expected eventID to be %v, got %v", wantEventID, gotEventID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can limit and paginate", func(t *testing.T) {
|
|
||||||
var from int64 = 0
|
|
||||||
var limit int64 = 5
|
|
||||||
var wantTotal int64 = 10 // We expect there to be 10 events in total
|
|
||||||
var resp response
|
|
||||||
for from+limit <= wantTotal {
|
|
||||||
resp = response{}
|
|
||||||
t.Logf("Getting reports starting from %d", from)
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/_synapse/admin/v1/event_reports?room_id=%s&limit=%d&from=%d", room2.ID, limit, from), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting reports to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantCount := 5 // we are limited to 5
|
|
||||||
if len(resp.EventReports) != wantCount {
|
|
||||||
t.Fatalf("expected %d events, got %d", wantCount, len(resp.EventReports))
|
|
||||||
}
|
|
||||||
if resp.Total != int64(wantTotal) {
|
|
||||||
t.Fatalf("expected total to be %d, got %d", wantCount, resp.Total)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've reached the end
|
|
||||||
if (from + int64(len(resp.EventReports))) == wantTotal {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The next_token should be set
|
|
||||||
if resp.NextToken == nil {
|
|
||||||
t.Fatal("expected nextToken to be set")
|
|
||||||
}
|
|
||||||
from = *resp.NextToken
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEventReportsGetDelete(t *testing.T) {
|
|
||||||
alice := test.NewUser(t, test.WithAccountType(uapi.AccountTypeAdmin))
|
|
||||||
bob := test.NewUser(t)
|
|
||||||
room := test.NewRoom(t, alice)
|
|
||||||
|
|
||||||
// Add a name and alias
|
|
||||||
roomName := "Testing"
|
|
||||||
alias := "#testing"
|
|
||||||
room.CreateAndInsert(t, alice, spec.MRoomName, map[string]string{"name": roomName}, test.WithStateKey(""))
|
|
||||||
room.CreateAndInsert(t, alice, spec.MRoomCanonicalAlias, map[string]string{"alias": alias}, test.WithStateKey(""))
|
|
||||||
|
|
||||||
// Join the rooms with Bob
|
|
||||||
room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "join",
|
|
||||||
}, test.WithStateKey(bob.ID))
|
|
||||||
|
|
||||||
// Create a few events to report
|
|
||||||
|
|
||||||
eventIDToReport := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "hello world"})
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
|
||||||
routers := httputil.NewRouters()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
defer close()
|
|
||||||
natsInstance := jetstream.NATSInstance{}
|
|
||||||
jsctx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
|
||||||
defer jetstream.DeleteAllStreams(jsctx, &cfg.Global.JetStream)
|
|
||||||
|
|
||||||
// Use an actual roomserver for this
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
if err := api.SendEvents(context.Background(), rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
|
||||||
|
|
||||||
accessTokens := map[*test.User]userDevice{
|
|
||||||
alice: {},
|
|
||||||
bob: {},
|
|
||||||
}
|
|
||||||
createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
|
|
||||||
|
|
||||||
reqBody := map[string]any{
|
|
||||||
"reason": "baaad",
|
|
||||||
"score": -100,
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
// Report the event
|
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/report/%s", room.ID, eventIDToReport.EventID()), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[bob].accessToken)
|
|
||||||
|
|
||||||
routers.Client.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected report to succeed, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Can not query with invalid ID", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, "/_synapse/admin/v1/event_reports/abc", strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusBadRequest {
|
|
||||||
t.Fatalf("expected getting report to fail, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can query with valid ID", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, "/_synapse/admin/v1/event_reports/1", strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting report to fail, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
resp := api.QueryAdminEventReportResponse{}
|
|
||||||
if err = json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// test a few things
|
|
||||||
if resp.EventID != eventIDToReport.EventID() {
|
|
||||||
t.Fatalf("expected eventID to be %s, got %s instead", eventIDToReport.EventID(), resp.EventID)
|
|
||||||
}
|
|
||||||
if resp.RoomName != roomName {
|
|
||||||
t.Fatalf("expected roomName to be %s, got %s instead", roomName, resp.RoomName)
|
|
||||||
}
|
|
||||||
if resp.CanonicalAlias != alias {
|
|
||||||
t.Fatalf("expected alias to be %s, got %s instead", alias, resp.CanonicalAlias)
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(resp.EventJSON, eventIDToReport.JSON()) {
|
|
||||||
t.Fatal("mismatching eventJSON")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can delete with a valid ID", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodDelete, "/_synapse/admin/v1/event_reports/1", strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected getting report to fail, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Can not query deleted report", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodGet, "/_synapse/admin/v1/event_reports/1", strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.SynapseAdmin.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code == http.StatusOK {
|
|
||||||
t.Fatalf("expected getting report to fail, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -31,13 +32,8 @@ import (
|
||||||
// called after authorization has completed, with the result of the authorization.
|
// called after authorization has completed, with the result of the authorization.
|
||||||
// If the final return value is non-nil, an error occurred and the cleanup function
|
// If the final return value is non-nil, an error occurred and the cleanup function
|
||||||
// is nil.
|
// is nil.
|
||||||
func LoginFromJSONReader(
|
func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.UserLoginAPI, userAPI UserInternalAPIForLogin, cfg *config.ClientAPI) (*Login, LoginCleanupFunc, *util.JSONResponse) {
|
||||||
req *http.Request,
|
reqBytes, err := io.ReadAll(r)
|
||||||
useraccountAPI uapi.UserLoginAPI,
|
|
||||||
userAPI UserInternalAPIForLogin,
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
) (*Login, LoginCleanupFunc, *util.JSONResponse) {
|
|
||||||
reqBytes, err := io.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := &util.JSONResponse{
|
err := &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
|
@ -61,28 +57,14 @@ func LoginFromJSONReader(
|
||||||
switch header.Type {
|
switch header.Type {
|
||||||
case authtypes.LoginTypePassword:
|
case authtypes.LoginTypePassword:
|
||||||
typ = &LoginTypePassword{
|
typ = &LoginTypePassword{
|
||||||
UserAPI: useraccountAPI,
|
GetAccountByPassword: useraccountAPI.QueryAccountByPassword,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
case authtypes.LoginTypeToken:
|
case authtypes.LoginTypeToken:
|
||||||
typ = &LoginTypeToken{
|
typ = &LoginTypeToken{
|
||||||
UserAPI: userAPI,
|
UserAPI: userAPI,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
case authtypes.LoginTypeApplicationService:
|
|
||||||
token, err := ExtractAccessToken(req)
|
|
||||||
if err != nil {
|
|
||||||
err := &util.JSONResponse{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
JSON: spec.MissingToken(err.Error()),
|
|
||||||
}
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
typ = &LoginTypeApplicationService{
|
|
||||||
Config: cfg,
|
|
||||||
Token: token,
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
err := util.JSONResponse{
|
err := util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
|
@ -91,7 +73,7 @@ func LoginFromJSONReader(
|
||||||
return nil, nil, &err
|
return nil, nil, &err
|
||||||
}
|
}
|
||||||
|
|
||||||
return typ.LoginFromJSON(req.Context(), reqBytes)
|
return typ.LoginFromJSON(ctx, reqBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserInternalAPIForLogin contains the aspects of UserAPI required for logging in.
|
// UserInternalAPIForLogin contains the aspects of UserAPI required for logging in.
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
// 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 auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
|
||||||
"github.com/matrix-org/dendrite/internal"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoginTypeApplicationService describes how to authenticate as an
|
|
||||||
// application service
|
|
||||||
type LoginTypeApplicationService struct {
|
|
||||||
Config *config.ClientAPI
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements Type
|
|
||||||
func (t *LoginTypeApplicationService) Name() string {
|
|
||||||
return authtypes.LoginTypeApplicationService
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginFromJSON implements Type
|
|
||||||
func (t *LoginTypeApplicationService) LoginFromJSON(
|
|
||||||
ctx context.Context, reqBytes []byte,
|
|
||||||
) (*Login, LoginCleanupFunc, *util.JSONResponse) {
|
|
||||||
var r Login
|
|
||||||
if err := httputil.UnmarshalJSON(reqBytes, &r); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := internal.ValidateApplicationServiceRequest(t.Config, r.Identifier.User, t.Token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup := func(ctx context.Context, j *util.JSONResponse) {}
|
|
||||||
return &r, cleanup, nil
|
|
||||||
}
|
|
|
@ -17,9 +17,7 @@ package auth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -35,9 +33,8 @@ func TestLoginFromJSONReader(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
tsts := []struct {
|
tsts := []struct {
|
||||||
Name string
|
Name string
|
||||||
Body string
|
Body string
|
||||||
Token string
|
|
||||||
|
|
||||||
WantUsername string
|
WantUsername string
|
||||||
WantDeviceID string
|
WantDeviceID string
|
||||||
|
@ -65,30 +62,6 @@ func TestLoginFromJSONReader(t *testing.T) {
|
||||||
WantDeviceID: "adevice",
|
WantDeviceID: "adevice",
|
||||||
WantDeletedTokens: []string{"atoken"},
|
WantDeletedTokens: []string{"atoken"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "appServiceWorksUserID",
|
|
||||||
Body: `{
|
|
||||||
"type": "m.login.application_service",
|
|
||||||
"identifier": { "type": "m.id.user", "user": "@alice:example.com" },
|
|
||||||
"device_id": "adevice"
|
|
||||||
}`,
|
|
||||||
Token: "astoken",
|
|
||||||
|
|
||||||
WantUsername: "@alice:example.com",
|
|
||||||
WantDeviceID: "adevice",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "appServiceWorksLocalpart",
|
|
||||||
Body: `{
|
|
||||||
"type": "m.login.application_service",
|
|
||||||
"identifier": { "type": "m.id.user", "user": "alice" },
|
|
||||||
"device_id": "adevice"
|
|
||||||
}`,
|
|
||||||
Token: "astoken",
|
|
||||||
|
|
||||||
WantUsername: "alice",
|
|
||||||
WantDeviceID: "adevice",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tst := range tsts {
|
for _, tst := range tsts {
|
||||||
t.Run(tst.Name, func(t *testing.T) {
|
t.Run(tst.Name, func(t *testing.T) {
|
||||||
|
@ -99,35 +72,11 @@ func TestLoginFromJSONReader(t *testing.T) {
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Derived: &config.Derived{
|
|
||||||
ApplicationServices: []config.ApplicationService{
|
|
||||||
{
|
|
||||||
ID: "anapplicationservice",
|
|
||||||
ASToken: "astoken",
|
|
||||||
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
|
|
||||||
"users": {
|
|
||||||
{
|
|
||||||
Exclusive: true,
|
|
||||||
Regex: "@alice:example.com",
|
|
||||||
RegexpObject: regexp.MustCompile("@alice:example.com"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
login, cleanup, err := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &userAPI, &userAPI, cfg)
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tst.Body))
|
if err != nil {
|
||||||
if tst.Token != "" {
|
t.Fatalf("LoginFromJSONReader failed: %+v", err)
|
||||||
req.Header.Add("Authorization", "Bearer "+tst.Token)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
login, cleanup, jsonErr := LoginFromJSONReader(req, &userAPI, &userAPI, cfg)
|
|
||||||
if jsonErr != nil {
|
|
||||||
t.Fatalf("LoginFromJSONReader failed: %+v", jsonErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup(ctx, &util.JSONResponse{Code: http.StatusOK})
|
cleanup(ctx, &util.JSONResponse{Code: http.StatusOK})
|
||||||
|
|
||||||
if login.Username() != tst.WantUsername {
|
if login.Username() != tst.WantUsername {
|
||||||
|
@ -155,9 +104,8 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
tsts := []struct {
|
tsts := []struct {
|
||||||
Name string
|
Name string
|
||||||
Body string
|
Body string
|
||||||
Token string
|
|
||||||
|
|
||||||
WantErrCode spec.MatrixErrorCode
|
WantErrCode spec.MatrixErrorCode
|
||||||
}{
|
}{
|
||||||
|
@ -194,45 +142,6 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
WantErrCode: spec.ErrorInvalidParam,
|
WantErrCode: spec.ErrorInvalidParam,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "noASToken",
|
|
||||||
Body: `{
|
|
||||||
"type": "m.login.application_service",
|
|
||||||
"identifier": { "type": "m.id.user", "user": "@alice:example.com" },
|
|
||||||
"device_id": "adevice"
|
|
||||||
}`,
|
|
||||||
WantErrCode: "M_MISSING_TOKEN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "badASToken",
|
|
||||||
Token: "badastoken",
|
|
||||||
Body: `{
|
|
||||||
"type": "m.login.application_service",
|
|
||||||
"identifier": { "type": "m.id.user", "user": "@alice:example.com" },
|
|
||||||
"device_id": "adevice"
|
|
||||||
}`,
|
|
||||||
WantErrCode: "M_UNKNOWN_TOKEN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "badASNamespace",
|
|
||||||
Token: "astoken",
|
|
||||||
Body: `{
|
|
||||||
"type": "m.login.application_service",
|
|
||||||
"identifier": { "type": "m.id.user", "user": "@bob:example.com" },
|
|
||||||
"device_id": "adevice"
|
|
||||||
}`,
|
|
||||||
WantErrCode: "M_EXCLUSIVE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "badASUserID",
|
|
||||||
Token: "astoken",
|
|
||||||
Body: `{
|
|
||||||
"type": "m.login.application_service",
|
|
||||||
"identifier": { "type": "m.id.user", "user": "@alice:wrong.example.com" },
|
|
||||||
"device_id": "adevice"
|
|
||||||
}`,
|
|
||||||
WantErrCode: "M_INVALID_USERNAME",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tst := range tsts {
|
for _, tst := range tsts {
|
||||||
t.Run(tst.Name, func(t *testing.T) {
|
t.Run(tst.Name, func(t *testing.T) {
|
||||||
|
@ -243,30 +152,8 @@ func TestBadLoginFromJSONReader(t *testing.T) {
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Derived: &config.Derived{
|
|
||||||
ApplicationServices: []config.ApplicationService{
|
|
||||||
{
|
|
||||||
ID: "anapplicationservice",
|
|
||||||
ASToken: "astoken",
|
|
||||||
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
|
|
||||||
"users": {
|
|
||||||
{
|
|
||||||
Exclusive: true,
|
|
||||||
Regex: "@alice:example.com",
|
|
||||||
RegexpObject: regexp.MustCompile("@alice:example.com"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tst.Body))
|
_, cleanup, errRes := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &userAPI, &userAPI, cfg)
|
||||||
if tst.Token != "" {
|
|
||||||
req.Header.Add("Authorization", "Bearer "+tst.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, cleanup, errRes := LoginFromJSONReader(req, &userAPI, &userAPI, cfg)
|
|
||||||
if errRes == nil {
|
if errRes == nil {
|
||||||
cleanup(ctx, nil)
|
cleanup(ctx, nil)
|
||||||
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
|
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
|
||||||
|
@ -292,14 +179,6 @@ func (ua *fakeUserInternalAPI) QueryAccountByPassword(ctx context.Context, req *
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ua *fakeUserInternalAPI) QueryAccountByLocalpart(ctx context.Context, req *uapi.QueryAccountByLocalpartRequest, res *uapi.QueryAccountByLocalpartResponse) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ua *fakeUserInternalAPI) PerformAccountCreation(ctx context.Context, req *uapi.PerformAccountCreationRequest, res *uapi.PerformAccountCreationResponse) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ua *fakeUserInternalAPI) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error {
|
func (ua *fakeUserInternalAPI) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error {
|
||||||
ua.DeletedTokens = append(ua.DeletedTokens, req.Token)
|
ua.DeletedTokens = append(ua.DeletedTokens, req.Token)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -16,9 +16,6 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -31,6 +28,8 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type GetAccountByPassword func(ctx context.Context, req *api.QueryAccountByPasswordRequest, res *api.QueryAccountByPasswordResponse) error
|
||||||
|
|
||||||
type PasswordRequest struct {
|
type PasswordRequest struct {
|
||||||
Login
|
Login
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
@ -38,8 +37,8 @@ type PasswordRequest struct {
|
||||||
|
|
||||||
// LoginTypePassword implements https://matrix.org/docs/spec/client_server/r0.6.1#password-based
|
// LoginTypePassword implements https://matrix.org/docs/spec/client_server/r0.6.1#password-based
|
||||||
type LoginTypePassword struct {
|
type LoginTypePassword struct {
|
||||||
Config *config.ClientAPI
|
GetAccountByPassword GetAccountByPassword
|
||||||
UserAPI api.UserLoginAPI
|
Config *config.ClientAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *LoginTypePassword) Name() string {
|
func (t *LoginTypePassword) Name() string {
|
||||||
|
@ -60,21 +59,22 @@ func (t *LoginTypePassword) LoginFromJSON(ctx context.Context, reqBytes []byte)
|
||||||
return login, func(context.Context, *util.JSONResponse) {}, nil
|
return login, func(context.Context, *util.JSONResponse) {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *LoginTypePassword) Login(ctx context.Context, request *PasswordRequest) (*Login, *util.JSONResponse) {
|
func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) {
|
||||||
fullUsername := request.Username()
|
r := req.(*PasswordRequest)
|
||||||
if fullUsername == "" {
|
username := r.Username()
|
||||||
|
if username == "" {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: spec.BadJSON("A username must be supplied."),
|
JSON: spec.BadJSON("A username must be supplied."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(request.Password) == 0 {
|
if len(r.Password) == 0 {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: spec.BadJSON("A password must be supplied."),
|
JSON: spec.BadJSON("A password must be supplied."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
username, domain, err := userutil.ParseUsernameParam(fullUsername, t.Config.Matrix)
|
localpart, domain, err := userutil.ParseUsernameParam(username, t.Config.Matrix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
|
@ -87,38 +87,12 @@ func (t *LoginTypePassword) Login(ctx context.Context, request *PasswordRequest)
|
||||||
JSON: spec.InvalidUsername("The server name is not known."),
|
JSON: spec.InvalidUsername("The server name is not known."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Squash username to all lowercase letters
|
||||||
var account *api.Account
|
|
||||||
if t.Config.Ldap.Enabled {
|
|
||||||
isAdmin, err := t.authenticateLdap(username, request.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
acc, err := t.getOrCreateAccount(ctx, username, domain, isAdmin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
account = acc
|
|
||||||
} else {
|
|
||||||
acc, err := t.authenticateDb(ctx, username, domain, request.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
account = acc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the user, so login.Username() can do the right thing
|
|
||||||
request.Identifier.User = account.UserID
|
|
||||||
request.User = account.UserID
|
|
||||||
return &request.Login, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *LoginTypePassword) authenticateDb(ctx context.Context, username string, domain spec.ServerName, password string) (*api.Account, *util.JSONResponse) {
|
|
||||||
res := &api.QueryAccountByPasswordResponse{}
|
res := &api.QueryAccountByPasswordResponse{}
|
||||||
err := t.UserAPI.QueryAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
|
err = t.GetAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
|
||||||
Localpart: strings.ToLower(username),
|
Localpart: strings.ToLower(localpart),
|
||||||
ServerName: domain,
|
ServerName: domain,
|
||||||
PlaintextPassword: password,
|
PlaintextPassword: r.Password,
|
||||||
}, res)
|
}, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
|
@ -127,11 +101,13 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, username string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we couldn't find the user by the lower cased localpart, try the provided
|
||||||
|
// localpart as is.
|
||||||
if !res.Exists {
|
if !res.Exists {
|
||||||
err = t.UserAPI.QueryAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
|
err = t.GetAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
|
||||||
Localpart: username,
|
Localpart: localpart,
|
||||||
ServerName: domain,
|
ServerName: domain,
|
||||||
PlaintextPassword: password,
|
PlaintextPassword: r.Password,
|
||||||
}, res)
|
}, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
|
@ -139,6 +115,8 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, username string,
|
||||||
JSON: spec.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
|
||||||
|
// but that would leak the existence of the user.
|
||||||
if !res.Exists {
|
if !res.Exists {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
|
@ -146,141 +124,8 @@ func (t *LoginTypePassword) authenticateDb(ctx context.Context, username string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.Account, nil
|
// Set the user, so login.Username() can do the right thing
|
||||||
}
|
r.Identifier.User = res.Account.UserID
|
||||||
func (t *LoginTypePassword) authenticateLdap(username, password string) (bool, *util.JSONResponse) {
|
r.User = res.Account.UserID
|
||||||
var conn *ldap.Conn
|
return &r.Login, nil
|
||||||
conn, err := ldap.DialURL(t.Config.Ldap.Uri)
|
|
||||||
if err != nil {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.Unknown("unable to connect to ldap: " + err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
if t.Config.Ldap.AdminBindEnabled {
|
|
||||||
err = conn.Bind(t.Config.Ldap.AdminBindDn, t.Config.Ldap.AdminBindPassword)
|
|
||||||
if err != nil {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.Unknown("unable to bind to ldap: " + err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filter := strings.ReplaceAll(t.Config.Ldap.SearchFilter, "{username}", username)
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
|
||||||
t.Config.Ldap.BaseDn, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
|
||||||
0, 0, false, filter, []string{t.Config.Ldap.SearchAttribute}, nil,
|
|
||||||
)
|
|
||||||
result, err := conn.Search(searchRequest)
|
|
||||||
if err != nil {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.Unknown("unable to bind to search ldap: " + err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(result.Entries) > 1 {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.BadJSON("'user' must be duplicated."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(result.Entries) < 1 {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.BadJSON("'user' not found."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userDN := result.Entries[0].DN
|
|
||||||
err = conn.Bind(userDN, password)
|
|
||||||
if err != nil {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bindDn := strings.ReplaceAll(t.Config.Ldap.UserBindDn, "{username}", username)
|
|
||||||
err = conn.Bind(bindDn, password)
|
|
||||||
if err != nil {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin, err := t.isLdapAdmin(conn, username)
|
|
||||||
if err != nil {
|
|
||||||
return false, &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isAdmin, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *LoginTypePassword) isLdapAdmin(conn *ldap.Conn, username string) (bool, error) {
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
|
||||||
t.Config.Ldap.AdminGroupDn,
|
|
||||||
ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
|
|
||||||
strings.ReplaceAll(t.Config.Ldap.AdminGroupFilter, "{username}", username),
|
|
||||||
[]string{t.Config.Ldap.AdminGroupAttribute},
|
|
||||||
nil)
|
|
||||||
|
|
||||||
sr, err := conn.Search(searchRequest)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sr.Entries) < 1 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *LoginTypePassword) getOrCreateAccount(ctx context.Context, username string, domain spec.ServerName, admin bool) (*api.Account, *util.JSONResponse) {
|
|
||||||
var existing api.QueryAccountByLocalpartResponse
|
|
||||||
err := t.UserAPI.QueryAccountByLocalpart(ctx, &api.QueryAccountByLocalpartRequest{
|
|
||||||
Localpart: username,
|
|
||||||
ServerName: domain,
|
|
||||||
}, &existing)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
return existing.Account, nil
|
|
||||||
}
|
|
||||||
if err != sql.ErrNoRows {
|
|
||||||
return nil, &util.JSONResponse{
|
|
||||||
Code: http.StatusUnauthorized,
|
|
||||||
JSON: spec.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accountType := api.AccountTypeUser
|
|
||||||
if admin {
|
|
||||||
accountType = api.AccountTypeAdmin
|
|
||||||
}
|
|
||||||
var created api.PerformAccountCreationResponse
|
|
||||||
err = t.UserAPI.PerformAccountCreation(ctx, &api.PerformAccountCreationRequest{
|
|
||||||
AppServiceID: "ldap",
|
|
||||||
Localpart: username,
|
|
||||||
Password: uuid.New().String(),
|
|
||||||
AccountType: accountType,
|
|
||||||
OnConflict: api.ConflictAbort,
|
|
||||||
}, &created)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(*api.ErrorConflict); ok {
|
|
||||||
return nil, &util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.UserInUse("Desired user ID is already taken."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, &util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.Unknown("failed to create account: " + err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return created.Account, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ type LoginCleanupFunc func(context.Context, *util.JSONResponse)
|
||||||
// https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types
|
// https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types
|
||||||
type LoginIdentifier struct {
|
type LoginIdentifier struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
// when type = m.id.user or m.id.application_service
|
// when type = m.id.user
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
// when type = m.id.thirdparty
|
// when type = m.id.thirdparty
|
||||||
Medium string `json:"medium"`
|
Medium string `json:"medium"`
|
||||||
|
@ -113,8 +113,8 @@ type UserInteractive struct {
|
||||||
|
|
||||||
func NewUserInteractive(userAccountAPI api.UserLoginAPI, cfg *config.ClientAPI) *UserInteractive {
|
func NewUserInteractive(userAccountAPI api.UserLoginAPI, cfg *config.ClientAPI) *UserInteractive {
|
||||||
typePassword := &LoginTypePassword{
|
typePassword := &LoginTypePassword{
|
||||||
UserAPI: userAccountAPI,
|
GetAccountByPassword: userAccountAPI.QueryAccountByPassword,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
return &UserInteractive{
|
return &UserInteractive{
|
||||||
Flows: []userInteractiveFlow{
|
Flows: []userInteractiveFlow{
|
||||||
|
|
|
@ -45,14 +45,6 @@ func (d *fakeAccountDatabase) QueryAccountByPassword(ctx context.Context, req *a
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *fakeAccountDatabase) QueryAccountByLocalpart(ctx context.Context, req *api.QueryAccountByLocalpartRequest, res *api.QueryAccountByLocalpartResponse) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *fakeAccountDatabase) PerformAccountCreation(ctx context.Context, req *api.PerformAccountCreationRequest, res *api.PerformAccountCreationResponse) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setup() *UserInteractive {
|
func setup() *UserInteractive {
|
||||||
cfg := &config.ClientAPI{
|
cfg := &config.ClientAPI{
|
||||||
Matrix: &config.Global{
|
Matrix: &config.Global{
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/routing"
|
"github.com/matrix-org/dendrite/clientapi/routing"
|
||||||
"github.com/matrix-org/dendrite/clientapi/threepid"
|
"github.com/matrix-org/dendrite/clientapi/threepid"
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/internal/pushrules"
|
"github.com/matrix-org/dendrite/internal/pushrules"
|
||||||
|
@ -50,10 +49,6 @@ type userDevice struct {
|
||||||
password string
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
|
|
||||||
return &statistics.ServerStatistics{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPutDevices(t *testing.T) {
|
func TestGetPutDevices(t *testing.T) {
|
||||||
alice := test.NewUser(t)
|
alice := test.NewUser(t)
|
||||||
bob := test.NewUser(t)
|
bob := test.NewUser(t)
|
||||||
|
@ -126,7 +121,7 @@ func TestGetPutDevices(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -175,7 +170,7 @@ func TestDeleteDevice(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -280,7 +275,7 @@ func TestDeleteDevices(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI/ for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -447,7 +442,7 @@ func TestSetDisplayname(t *testing.T) {
|
||||||
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil)
|
||||||
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
|
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
|
||||||
|
|
||||||
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -559,7 +554,7 @@ func TestSetAvatarURL(t *testing.T) {
|
||||||
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, natsInstance, rsAPI, nil)
|
||||||
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
|
asPI := appservice.NewInternalAPI(processCtx, cfg, natsInstance, userAPI, rsAPI)
|
||||||
|
|
||||||
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
AddPublicRoutes(processCtx, routers, cfg, natsInstance, base.CreateFederationClient(cfg, nil), rsAPI, asPI, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -637,7 +632,7 @@ func TestTyping(t *testing.T) {
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
// Needed to create accounts
|
// Needed to create accounts
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
|
@ -721,7 +716,7 @@ func TestMembership(t *testing.T) {
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
// Needed to create accounts
|
// Needed to create accounts
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
rsAPI.SetUserAPI(userAPI)
|
rsAPI.SetUserAPI(userAPI)
|
||||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -928,17 +923,13 @@ func TestCapabilities(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tempRoomServerCfg config.RoomServer
|
|
||||||
tempRoomServerCfg.Defaults(config.DefaultOpts{})
|
|
||||||
defaultRoomVersion := tempRoomServerCfg.DefaultRoomVersion
|
|
||||||
|
|
||||||
expectedMap := map[string]interface{}{
|
expectedMap := map[string]interface{}{
|
||||||
"capabilities": map[string]interface{}{
|
"capabilities": map[string]interface{}{
|
||||||
"m.change_password": map[string]bool{
|
"m.change_password": map[string]bool{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
"m.room_versions": map[string]interface{}{
|
"m.room_versions": map[string]interface{}{
|
||||||
"default": defaultRoomVersion,
|
"default": version.DefaultRoomVersion(),
|
||||||
"available": versionsMap,
|
"available": versionsMap,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -958,10 +949,9 @@ func TestCapabilities(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
|
|
||||||
// Needed to create accounts
|
// Needed to create accounts
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
|
@ -1006,10 +996,9 @@ func TestTurnserver(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
|
|
||||||
// Needed to create accounts
|
// Needed to create accounts
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
//rsAPI.SetUserAPI(userAPI)
|
//rsAPI.SetUserAPI(userAPI)
|
||||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -1105,10 +1094,9 @@ func Test3PID(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
|
|
||||||
// Needed to create accounts
|
// Needed to create accounts
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
// We mostly need the rsAPI/userAPI for this test, so nil for other APIs etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
|
@ -1284,7 +1272,7 @@ func TestPushRules(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -1426,7 +1414,7 @@ func TestPushRules(t *testing.T) {
|
||||||
validateFunc: func(t *testing.T, respBody *bytes.Buffer) {
|
validateFunc: func(t *testing.T, respBody *bytes.Buffer) {
|
||||||
actions := gjson.GetBytes(respBody.Bytes(), "actions").Array()
|
actions := gjson.GetBytes(respBody.Bytes(), "actions").Array()
|
||||||
// only a basic check
|
// only a basic check
|
||||||
assert.Equal(t, 0, len(actions))
|
assert.Equal(t, 1, len(actions))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1671,7 +1659,7 @@ func TestKeys(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -2133,7 +2121,7 @@ func TestKeyBackup(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
// 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)
|
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -2154,284 +2142,3 @@ func TestKeyBackup(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMembership(t *testing.T) {
|
|
||||||
alice := test.NewUser(t)
|
|
||||||
bob := test.NewUser(t)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
roomID string
|
|
||||||
user *test.User
|
|
||||||
additionalEvents func(t *testing.T, room *test.Room)
|
|
||||||
request func(t *testing.T, room *test.Room, accessToken string) *http.Request
|
|
||||||
wantOK bool
|
|
||||||
wantMemberCount int
|
|
||||||
}{
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "/joined_members - Bob never joined",
|
|
||||||
user: bob,
|
|
||||||
request: func(t *testing.T, room *test.Room, accessToken string) *http.Request {
|
|
||||||
return test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/joined_members", room.ID), test.WithQueryParams(map[string]string{
|
|
||||||
"access_token": accessToken,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
wantOK: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "/joined_members - Alice joined",
|
|
||||||
user: alice,
|
|
||||||
request: func(t *testing.T, room *test.Room, accessToken string) *http.Request {
|
|
||||||
return test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/joined_members", room.ID), test.WithQueryParams(map[string]string{
|
|
||||||
"access_token": accessToken,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
wantOK: true,
|
|
||||||
wantMemberCount: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "/joined_members - Alice leaves, shouldn't be able to see members ",
|
|
||||||
user: alice,
|
|
||||||
request: func(t *testing.T, room *test.Room, accessToken string) *http.Request {
|
|
||||||
return test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/joined_members", room.ID), test.WithQueryParams(map[string]string{
|
|
||||||
"access_token": accessToken,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
additionalEvents: func(t *testing.T, room *test.Room) {
|
|
||||||
room.CreateAndInsert(t, alice, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "leave",
|
|
||||||
}, test.WithStateKey(alice.ID))
|
|
||||||
},
|
|
||||||
wantOK: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "/joined_members - Bob joins, Alice sees two members",
|
|
||||||
user: alice,
|
|
||||||
request: func(t *testing.T, room *test.Room, accessToken string) *http.Request {
|
|
||||||
return test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/joined_members", room.ID), test.WithQueryParams(map[string]string{
|
|
||||||
"access_token": accessToken,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
additionalEvents: func(t *testing.T, room *test.Room) {
|
|
||||||
room.CreateAndInsert(t, bob, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "join",
|
|
||||||
}, test.WithStateKey(bob.ID))
|
|
||||||
},
|
|
||||||
wantOK: true,
|
|
||||||
wantMemberCount: 2,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
|
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
|
||||||
routers := httputil.NewRouters()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
defer close()
|
|
||||||
natsInstance := jetstream.NATSInstance{}
|
|
||||||
jsctx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
|
||||||
defer jetstream.DeleteAllStreams(jsctx, &cfg.Global.JetStream)
|
|
||||||
|
|
||||||
// Use an actual roomserver for this
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
|
||||||
|
|
||||||
accessTokens := map[*test.User]userDevice{
|
|
||||||
alice: {},
|
|
||||||
bob: {},
|
|
||||||
}
|
|
||||||
createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
room := test.NewRoom(t, alice)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
t.Logf("running cleanup for %s", tc.name)
|
|
||||||
})
|
|
||||||
// inject additional events
|
|
||||||
if tc.additionalEvents != nil {
|
|
||||||
tc.additionalEvents(t, room)
|
|
||||||
}
|
|
||||||
if err := api.SendEvents(context.Background(), rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
routers.Client.ServeHTTP(w, tc.request(t, room, accessTokens[tc.user].accessToken))
|
|
||||||
if w.Code != 200 && tc.wantOK {
|
|
||||||
t.Logf("%s", w.Body.String())
|
|
||||||
t.Fatalf("got HTTP %d want %d", w.Code, 200)
|
|
||||||
}
|
|
||||||
t.Logf("[%s] Resp: %s", tc.name, w.Body.String())
|
|
||||||
|
|
||||||
// check we got the expected events
|
|
||||||
if tc.wantOK {
|
|
||||||
memberCount := len(gjson.GetBytes(w.Body.Bytes(), "joined").Map())
|
|
||||||
if memberCount != tc.wantMemberCount {
|
|
||||||
t.Fatalf("expected %d members, got %d", tc.wantMemberCount, memberCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateRoomInvite(t *testing.T) {
|
|
||||||
alice := test.NewUser(t)
|
|
||||||
bob := test.NewUser(t)
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
|
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
|
||||||
routers := httputil.NewRouters()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
defer close()
|
|
||||||
natsInstance := jetstream.NATSInstance{}
|
|
||||||
jsctx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
|
||||||
defer jetstream.DeleteAllStreams(jsctx, &cfg.Global.JetStream)
|
|
||||||
|
|
||||||
// Use an actual roomserver for this
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
reqBody := map[string]any{
|
|
||||||
"invite": []string{bob.ID},
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/_matrix/client/v3/createRoom", strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[alice].accessToken)
|
|
||||||
|
|
||||||
routers.Client.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected room creation to be successful, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
roomID := gjson.GetBytes(w.Body.Bytes(), "room_id").Str
|
|
||||||
validRoomID, _ := spec.NewRoomID(roomID)
|
|
||||||
// Now ask the roomserver about the membership event of Bob
|
|
||||||
ev, err := rsAPI.CurrentStateEvent(context.Background(), *validRoomID, spec.MRoomMember, bob.ID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ev == nil {
|
|
||||||
t.Fatal("Membership event for Bob does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that there is NO displayname in content
|
|
||||||
if gjson.GetBytes(ev.Content(), "displayname").Exists() {
|
|
||||||
t.Fatal("Found displayname in invite")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReportEvent(t *testing.T) {
|
|
||||||
alice := test.NewUser(t)
|
|
||||||
bob := test.NewUser(t)
|
|
||||||
charlie := test.NewUser(t)
|
|
||||||
room := test.NewRoom(t, alice)
|
|
||||||
|
|
||||||
room.CreateAndInsert(t, charlie, spec.MRoomMember, map[string]interface{}{
|
|
||||||
"membership": "join",
|
|
||||||
}, test.WithStateKey(charlie.ID))
|
|
||||||
eventToReport := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "hello world"})
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
|
||||||
routers := httputil.NewRouters()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
defer close()
|
|
||||||
natsInstance := jetstream.NATSInstance{}
|
|
||||||
jsctx, _ := natsInstance.Prepare(processCtx, &cfg.Global.JetStream)
|
|
||||||
defer jetstream.DeleteAllStreams(jsctx, &cfg.Global.JetStream)
|
|
||||||
|
|
||||||
// Use an actual roomserver for this
|
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
if err := api.SendEvents(context.Background(), rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false); err != nil {
|
|
||||||
t.Fatalf("failed to send events: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
|
|
||||||
AddPublicRoutes(processCtx, routers, cfg, &natsInstance, nil, rsAPI, nil, nil, nil, userAPI, nil, nil, caching.DisableMetrics)
|
|
||||||
|
|
||||||
accessTokens := map[*test.User]userDevice{
|
|
||||||
alice: {},
|
|
||||||
bob: {},
|
|
||||||
charlie: {},
|
|
||||||
}
|
|
||||||
createAccessTokens(t, accessTokens, userAPI, processCtx.Context(), routers)
|
|
||||||
|
|
||||||
reqBody := map[string]any{
|
|
||||||
"reason": "baaad",
|
|
||||||
"score": -100,
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
t.Run("Bob is not joined and should not be able to report the event", func(t *testing.T) {
|
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/report/%s", room.ID, eventToReport.EventID()), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[bob].accessToken)
|
|
||||||
|
|
||||||
routers.Client.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusNotFound {
|
|
||||||
t.Fatalf("expected report to fail, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Charlie is joined but the event does not exist", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/report/$doesNotExist", room.ID), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[charlie].accessToken)
|
|
||||||
|
|
||||||
routers.Client.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusNotFound {
|
|
||||||
t.Fatalf("expected report to fail, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Charlie is joined and allowed to report the event", func(t *testing.T) {
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/_matrix/client/v3/rooms/%s/report/%s", room.ID, eventToReport.EventID()), strings.NewReader(string(body)))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+accessTokens[charlie].accessToken)
|
|
||||||
|
|
||||||
routers.Client.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Fatalf("expected report to be successful, got HTTP %d instead: %s", w.Code, w.Body.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -495,93 +495,3 @@ func AdminDownloadState(req *http.Request, device *api.Device, rsAPI roomserverA
|
||||||
JSON: struct{}{},
|
JSON: struct{}{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEventReports returns reported events for a given user/room.
|
|
||||||
func GetEventReports(
|
|
||||||
req *http.Request,
|
|
||||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
|
||||||
from, limit uint64,
|
|
||||||
backwards bool,
|
|
||||||
userID, roomID string,
|
|
||||||
) util.JSONResponse {
|
|
||||||
|
|
||||||
eventReports, count, err := rsAPI.QueryAdminEventReports(req.Context(), from, limit, backwards, userID, roomID)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Error("failed to query event reports")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := map[string]any{
|
|
||||||
"event_reports": eventReports,
|
|
||||||
"total": count,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a next_token if there are still reports
|
|
||||||
if int64(from+limit) < count {
|
|
||||||
resp["next_token"] = int(from) + len(eventReports)
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: resp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEventReport(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, reportID string) util.JSONResponse {
|
|
||||||
parsedReportID, err := strconv.ParseUint(reportID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
// Given this is an admin endpoint, let them know what didn't work.
|
|
||||||
JSON: spec.InvalidParam(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
report, err := rsAPI.QueryAdminEventReport(req.Context(), parsedReportID)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.Unknown(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: report,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteEventReport(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, reportID string) util.JSONResponse {
|
|
||||||
parsedReportID, err := strconv.ParseUint(reportID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
// Given this is an admin endpoint, let them know what didn't work.
|
|
||||||
JSON: spec.InvalidParam(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rsAPI.PerformAdminDeleteEventReport(req.Context(), parsedReportID)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.Unknown(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: struct{}{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUint64OrDefault(input string, defaultValue uint64) uint64 {
|
|
||||||
v, err := strconv.ParseUint(input, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/version"
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
@ -25,7 +24,7 @@ import (
|
||||||
|
|
||||||
// GetCapabilities returns information about the server's supported feature set
|
// GetCapabilities returns information about the server's supported feature set
|
||||||
// and other relevant capabilities to an authenticated user.
|
// and other relevant capabilities to an authenticated user.
|
||||||
func GetCapabilities(rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
func GetCapabilities() util.JSONResponse {
|
||||||
versionsMap := map[gomatrixserverlib.RoomVersion]string{}
|
versionsMap := map[gomatrixserverlib.RoomVersion]string{}
|
||||||
for v, desc := range version.SupportedRoomVersions() {
|
for v, desc := range version.SupportedRoomVersions() {
|
||||||
if desc.Stable() {
|
if desc.Stable() {
|
||||||
|
@ -41,7 +40,7 @@ func GetCapabilities(rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
"m.room_versions": map[string]interface{}{
|
"m.room_versions": map[string]interface{}{
|
||||||
"default": rsAPI.DefaultRoomVersion(),
|
"default": version.DefaultRoomVersion(),
|
||||||
"available": versionsMap,
|
"available": versionsMap,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -171,7 +171,7 @@ func createRoom(
|
||||||
|
|
||||||
// Clobber keys: creator, room_version
|
// Clobber keys: creator, room_version
|
||||||
|
|
||||||
roomVersion := rsAPI.DefaultRoomVersion()
|
roomVersion := roomserverVersion.DefaultRoomVersion()
|
||||||
if createRequest.RoomVersion != "" {
|
if createRequest.RoomVersion != "" {
|
||||||
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
||||||
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
||||||
|
|
|
@ -55,7 +55,7 @@ func DirectoryRoom(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.InvalidParam("Room alias must be in the form '#localpart:domain'"),
|
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ func SetLocalAlias(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.InvalidParam("Room alias must be in the form '#localpart:domain'"),
|
JSON: spec.BadJSON("Room alias must be in the form '#localpart:domain'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,39 +181,13 @@ func SetLocalAlias(
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
roomID, err := spec.NewRoomID(r.RoomID)
|
queryReq := roomserverAPI.SetRoomAliasRequest{
|
||||||
if err != nil {
|
UserID: device.UserID,
|
||||||
return util.JSONResponse{
|
RoomID: r.RoomID,
|
||||||
Code: http.StatusBadRequest,
|
Alias: alias,
|
||||||
JSON: spec.InvalidParam("invalid room ID"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
var queryRes roomserverAPI.SetRoomAliasResponse
|
||||||
userID, err := spec.NewUserID(device.UserID, true)
|
if err := rsAPI.SetRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
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")
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.SetRoomAlias failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
|
@ -221,7 +195,7 @@ func SetLocalAlias(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliasAlreadyExists {
|
if queryRes.AliasExists {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusConflict,
|
Code: http.StatusConflict,
|
||||||
JSON: spec.Unknown("The alias " + alias + " already exists."),
|
JSON: spec.Unknown("The alias " + alias + " already exists."),
|
||||||
|
@ -266,31 +240,6 @@ func RemoveLocalAlias(
|
||||||
JSON: spec.NotFound("The alias does not exist."),
|
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: spec.Forbidden("You do not have permission to remove this alias."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceSenderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *userID)
|
deviceSenderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -298,31 +247,28 @@ func RemoveLocalAlias(
|
||||||
JSON: spec.NotFound("The alias does not exist."),
|
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)
|
queryReq := roomserverAPI.RemoveRoomAliasRequest{
|
||||||
if err != nil {
|
Alias: alias,
|
||||||
|
SenderID: deviceSenderID,
|
||||||
|
}
|
||||||
|
var queryRes roomserverAPI.RemoveRoomAliasResponse
|
||||||
|
if err := rsAPI.RemoveRoomAlias(req.Context(), &queryReq, &queryRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
util.GetLogger(req.Context()).WithError(err).Error("aliasAPI.RemoveRoomAlias failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: spec.Unknown("internal server error"),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !aliasFound {
|
if !queryRes.Found {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: spec.NotFound("The alias does not exist."),
|
JSON: spec.NotFound("The alias does not exist."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !aliasRemoved {
|
if !queryRes.Removed {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Forbidden("You do not have permission to remove this alias."),
|
JSON: spec.Forbidden("You do not have permission to remove this alias."),
|
||||||
|
@ -391,7 +337,7 @@ func SetVisibility(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
if err != nil || senderID == nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.Unknown("failed to find senderID for this user"),
|
JSON: spec.Unknown("failed to find senderID for this user"),
|
||||||
|
@ -422,7 +368,7 @@ func SetVisibility(
|
||||||
|
|
||||||
// NOTSPEC: Check if the user's power is greater than power required to change m.room.canonical_alias event
|
// NOTSPEC: Check if the user's power is greater than power required to change m.room.canonical_alias event
|
||||||
power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU)
|
power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU)
|
||||||
if power.UserLevel(*senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) {
|
if power.UserLevel(senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Forbidden("userID doesn't have power level to change visibility"),
|
JSON: spec.Forbidden("userID doesn't have power level to change visibility"),
|
||||||
|
|
|
@ -33,36 +33,23 @@ func GetJoinedRooms(
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
rsAPI api.ClientRoomserverAPI,
|
rsAPI api.ClientRoomserverAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
var res api.QueryRoomsForUserResponse
|
||||||
if err != nil {
|
err := rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("Invalid device user ID")
|
UserID: device.UserID,
|
||||||
return util.JSONResponse{
|
WantMembership: "join",
|
||||||
Code: http.StatusInternalServerError,
|
}, &res)
|
||||||
JSON: spec.Unknown("internal server error"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rooms, err := rsAPI.QueryRoomsForUser(req.Context(), *deviceUserID, "join")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: spec.Unknown("internal server error"),
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if res.RoomIDs == nil {
|
||||||
var roomIDStrs []string
|
res.RoomIDs = []string{}
|
||||||
if rooms == nil {
|
|
||||||
roomIDStrs = []string{}
|
|
||||||
} else {
|
|
||||||
roomIDStrs = make([]string, len(rooms))
|
|
||||||
for i, roomID := range rooms {
|
|
||||||
roomIDStrs[i] = roomID.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: getJoinedRoomsResponse{roomIDStrs},
|
JSON: getJoinedRoomsResponse{res.RoomIDs},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
@ -22,10 +21,6 @@ import (
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testIsBlacklistedOrBackingOff = func(s spec.ServerName) (*statistics.ServerStatistics, error) {
|
|
||||||
return &statistics.ServerStatistics{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJoinRoomByIDOrAlias(t *testing.T) {
|
func TestJoinRoomByIDOrAlias(t *testing.T) {
|
||||||
alice := test.NewUser(t)
|
alice := test.NewUser(t)
|
||||||
bob := test.NewUser(t)
|
bob := test.NewUser(t)
|
||||||
|
@ -41,7 +36,7 @@ func TestJoinRoomByIDOrAlias(t *testing.T) {
|
||||||
natsInstance := jetstream.NATSInstance{}
|
natsInstance := jetstream.NATSInstance{}
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil) // creates the rs.Inputer etc
|
rsAPI.SetFederationAPI(nil, nil) // creates the rs.Inputer etc
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||||
|
|
||||||
// Create the users in the userapi
|
// Create the users in the userapi
|
||||||
|
|
|
@ -32,7 +32,7 @@ type crossSigningRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func UploadCrossSigningDeviceKeys(
|
func UploadCrossSigningDeviceKeys(
|
||||||
req *http.Request,
|
req *http.Request, userInteractiveAuth *auth.UserInteractive,
|
||||||
keyserverAPI api.ClientKeyAPI, device *api.Device,
|
keyserverAPI api.ClientKeyAPI, device *api.Device,
|
||||||
accountAPI api.ClientUserAPI, cfg *config.ClientAPI,
|
accountAPI api.ClientUserAPI, cfg *config.ClientAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
@ -62,8 +62,8 @@ func UploadCrossSigningDeviceKeys(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
typePassword := auth.LoginTypePassword{
|
typePassword := auth.LoginTypePassword{
|
||||||
UserAPI: accountAPI,
|
GetAccountByPassword: accountAPI.QueryAccountByPassword,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
|
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
|
||||||
return *authErr
|
return *authErr
|
||||||
|
|
|
@ -93,6 +93,7 @@ func UploadKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *api.Device)
|
||||||
|
|
||||||
type queryKeysRequest struct {
|
type queryKeysRequest struct {
|
||||||
Timeout int `json:"timeout"`
|
Timeout int `json:"timeout"`
|
||||||
|
Token string `json:"token"`
|
||||||
DeviceKeys map[string][]string `json:"device_keys"`
|
DeviceKeys map[string][]string `json:"device_keys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +119,7 @@ func QueryKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *api.Device) u
|
||||||
UserID: device.UserID,
|
UserID: device.UserID,
|
||||||
UserToDevices: r.DeviceKeys,
|
UserToDevices: r.DeviceKeys,
|
||||||
Timeout: r.GetTimeout(),
|
Timeout: r.GetTimeout(),
|
||||||
|
// TODO: Token?
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
@ -41,25 +40,28 @@ type flow struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func passwordLogin() flows {
|
||||||
|
f := flows{}
|
||||||
|
s := flow{
|
||||||
|
Type: "m.login.password",
|
||||||
|
}
|
||||||
|
f.Flows = append(f.Flows, s)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
// Login implements GET and POST /login
|
// Login implements GET and POST /login
|
||||||
func Login(
|
func Login(
|
||||||
req *http.Request, userAPI userapi.ClientUserAPI,
|
req *http.Request, userAPI userapi.ClientUserAPI,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if req.Method == http.MethodGet {
|
if req.Method == http.MethodGet {
|
||||||
loginFlows := []flow{{Type: authtypes.LoginTypePassword}}
|
// TODO: support other forms of login other than password, depending on config options
|
||||||
if len(cfg.Derived.ApplicationServices) > 0 {
|
|
||||||
loginFlows = append(loginFlows, flow{Type: authtypes.LoginTypeApplicationService})
|
|
||||||
}
|
|
||||||
// TODO: support other forms of login, depending on config options
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: flows{
|
JSON: passwordLogin(),
|
||||||
Flows: loginFlows,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
} else if req.Method == http.MethodPost {
|
} else if req.Method == http.MethodPost {
|
||||||
login, cleanup, authErr := auth.LoginFromJSONReader(req, userAPI, userAPI, cfg)
|
login, cleanup, authErr := auth.LoginFromJSONReader(req.Context(), req.Body, userAPI, userAPI, cfg)
|
||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
return *authErr
|
return *authErr
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func TestLogin(t *testing.T) {
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
// Needed for /login
|
// Needed for /login
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
// We mostly need the userAPI for this test, so nil for other APIs/caches etc.
|
// We mostly need the userAPI for this test, so nil for other APIs/caches etc.
|
||||||
Setup(routers, cfg, nil, nil, userAPI, nil, nil, nil, nil, nil, nil, nil, caching.DisableMetrics)
|
Setup(routers, cfg, nil, nil, userAPI, nil, nil, nil, nil, nil, nil, nil, caching.DisableMetrics)
|
||||||
|
@ -114,44 +114,6 @@ func TestLogin(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Inject a dummy application service, so we have a "m.login.application_service"
|
|
||||||
// in the login flows
|
|
||||||
as := &config.ApplicationService{}
|
|
||||||
cfg.AppServiceAPI.Derived.ApplicationServices = []config.ApplicationService{*as}
|
|
||||||
|
|
||||||
t.Run("Supported log-in flows are returned", func(t *testing.T) {
|
|
||||||
req := test.NewRequest(t, http.MethodGet, "/_matrix/client/v3/login")
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
routers.Client.ServeHTTP(rec, req)
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Fatalf("failed to get log-in flows: %s", rec.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("response: %s", rec.Body.String())
|
|
||||||
resp := flows{}
|
|
||||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
appServiceFound := false
|
|
||||||
passwordFound := false
|
|
||||||
for _, flow := range resp.Flows {
|
|
||||||
if flow.Type == "m.login.password" {
|
|
||||||
passwordFound = true
|
|
||||||
} else if flow.Type == "m.login.application_service" {
|
|
||||||
appServiceFound = true
|
|
||||||
} else {
|
|
||||||
t.Fatalf("got unknown login flow: %s", flow.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !appServiceFound {
|
|
||||||
t.Fatal("m.login.application_service missing from login flows")
|
|
||||||
}
|
|
||||||
if !passwordFound {
|
|
||||||
t.Fatal("m.login.password missing from login flows")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
|
req := test.NewRequest(t, http.MethodPost, "/_matrix/client/v3/login", test.WithJSONBody(t, map[string]interface{}{
|
||||||
|
|
|
@ -71,7 +71,7 @@ func SendBan(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
if err != nil || senderID == nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Forbidden("You don't have permission to ban this user, unknown senderID"),
|
JSON: spec.Forbidden("You don't have permission to ban this user, unknown senderID"),
|
||||||
|
@ -87,7 +87,7 @@ func SendBan(
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
allowedToBan := pl.UserLevel(*senderID) >= pl.Ban
|
allowedToBan := pl.UserLevel(senderID) >= pl.Ban
|
||||||
if !allowedToBan {
|
if !allowedToBan {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
|
@ -169,7 +169,7 @@ func SendKick(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
||||||
if err != nil || senderID == nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Forbidden("You don't have permission to kick this user, unknown senderID"),
|
JSON: spec.Forbidden("You don't have permission to kick this user, unknown senderID"),
|
||||||
|
@ -181,19 +181,11 @@ func SendKick(
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyUserID, err := spec.NewUserID(body.UserID, true)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.BadJSON("body userID is invalid"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pl, errRes := getPowerlevels(req, rsAPI, roomID)
|
pl, errRes := getPowerlevels(req, rsAPI, roomID)
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
allowedToKick := pl.UserLevel(*senderID) >= pl.Kick || bodyUserID.String() == deviceUserID.String()
|
allowedToKick := pl.UserLevel(senderID) >= pl.Kick
|
||||||
if !allowedToKick {
|
if !allowedToKick {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
|
@ -201,6 +193,13 @@ func SendKick(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bodyUserID, err := spec.NewUserID(body.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("body userID is invalid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
var queryRes roomserverAPI.QueryMembershipForUserResponse
|
||||||
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
err = rsAPI.QueryMembershipForUser(req.Context(), &roomserverAPI.QueryMembershipForUserRequest{
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
|
@ -324,18 +323,19 @@ func SendInvite(
|
||||||
}
|
}
|
||||||
|
|
||||||
// We already received the return value, so no need to check for an error here.
|
// We already received the return value, so no need to check for an error here.
|
||||||
response, _ := sendInvite(req.Context(), device, roomID, body.UserID, body.Reason, cfg, rsAPI, evTime)
|
response, _ := sendInvite(req.Context(), profileAPI, device, roomID, body.UserID, body.Reason, cfg, rsAPI, asAPI, evTime)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendInvite sends an invitation to a user. Returns a JSONResponse and an error
|
// sendInvite sends an invitation to a user. Returns a JSONResponse and an error
|
||||||
func sendInvite(
|
func sendInvite(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
profileAPI userapi.ClientUserAPI,
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
roomID, userID, reason string,
|
roomID, userID, reason string,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
rsAPI roomserverAPI.ClientRoomserverAPI,
|
rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
evTime time.Time,
|
asAPI appserviceAPI.AppServiceInternalAPI, evTime time.Time,
|
||||||
) (util.JSONResponse, error) {
|
) (util.JSONResponse, error) {
|
||||||
validRoomID, err := spec.NewRoomID(roomID)
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -358,7 +358,13 @@ func sendInvite(
|
||||||
JSON: spec.InvalidParam("UserID is invalid"),
|
JSON: spec.InvalidParam("UserID is invalid"),
|
||||||
}, err
|
}, 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())
|
identity, err := cfg.Matrix.SigningIdentityFor(device.UserDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -368,14 +374,16 @@ func sendInvite(
|
||||||
}
|
}
|
||||||
err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
err = rsAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
||||||
InviteInput: roomserverAPI.InviteInput{
|
InviteInput: roomserverAPI.InviteInput{
|
||||||
RoomID: *validRoomID,
|
RoomID: *validRoomID,
|
||||||
Inviter: *inviter,
|
Inviter: *inviter,
|
||||||
Invitee: *invitee,
|
Invitee: *invitee,
|
||||||
Reason: reason,
|
DisplayName: profile.DisplayName,
|
||||||
IsDirect: false,
|
AvatarURL: profile.AvatarURL,
|
||||||
KeyID: identity.KeyID,
|
Reason: reason,
|
||||||
PrivateKey: identity.PrivateKey,
|
IsDirect: false,
|
||||||
EventTime: evTime,
|
KeyID: identity.KeyID,
|
||||||
|
PrivateKey: identity.PrivateKey,
|
||||||
|
EventTime: evTime,
|
||||||
},
|
},
|
||||||
InviteRoomState: nil, // ask the roomserver to draw up invite room state for us
|
InviteRoomState: nil, // ask the roomserver to draw up invite room state for us
|
||||||
SendAsServer: string(device.UserDomain()),
|
SendAsServer: string(device.UserDomain()),
|
||||||
|
@ -468,8 +476,6 @@ func buildMembershipEvent(
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if senderID == nil {
|
|
||||||
return nil, fmt.Errorf("no sender ID for %s in %s", *userID, *validRoomID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
targetID, err := spec.NewUserID(targetUserID, true)
|
targetID, err := spec.NewUserID(targetUserID, true)
|
||||||
|
@ -479,8 +485,6 @@ func buildMembershipEvent(
|
||||||
targetSenderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *targetID)
|
targetSenderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *targetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *userID)
|
||||||
|
@ -488,8 +492,8 @@ func buildMembershipEvent(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildMembershipEventDirect(ctx, *targetSenderID, reason, profile.DisplayName, profile.AvatarURL,
|
return buildMembershipEventDirect(ctx, targetSenderID, reason, profile.DisplayName, profile.AvatarURL,
|
||||||
*senderID, device.UserDomain(), membership, roomID, isDirect, identity.KeyID, identity.PrivateKey, evTime, rsAPI)
|
senderID, device.UserDomain(), membership, roomID, isDirect, identity.KeyID, identity.PrivateKey, evTime, rsAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadProfile lookups the profile of a given user from the database and returns
|
// loadProfile lookups the profile of a given user from the database and returns
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
// Copyright 2024 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 (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-joined-members
|
|
||||||
type getJoinedMembersResponse struct {
|
|
||||||
Joined map[string]joinedMember `json:"joined"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type joinedMember struct {
|
|
||||||
DisplayName string `json:"display_name"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// The database stores 'displayname' without an underscore.
|
|
||||||
// Deserialize into this and then change to the actual API response
|
|
||||||
type databaseJoinedMember struct {
|
|
||||||
DisplayName string `json:"displayname"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetJoinedMembers implements
|
|
||||||
//
|
|
||||||
// GET /rooms/{roomId}/joined_members
|
|
||||||
func GetJoinedMembers(
|
|
||||||
req *http.Request, device *userapi.Device, roomID string,
|
|
||||||
rsAPI api.ClientRoomserverAPI,
|
|
||||||
) util.JSONResponse {
|
|
||||||
// Validate the userID
|
|
||||||
userID, err := spec.NewUserID(device.UserID, true)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.InvalidParam("Device UserID is invalid"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the roomID
|
|
||||||
validRoomID, err := spec.NewRoomID(roomID)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: spec.InvalidParam("RoomID is invalid"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current memberships for the requesting user to determine
|
|
||||||
// if they are allowed to query this endpoint.
|
|
||||||
queryReq := api.QueryMembershipForUserRequest{
|
|
||||||
RoomID: validRoomID.String(),
|
|
||||||
UserID: *userID,
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryRes api.QueryMembershipForUserResponse
|
|
||||||
if queryErr := rsAPI.QueryMembershipForUser(req.Context(), &queryReq, &queryRes); queryErr != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(queryErr).Error("rsAPI.QueryMembershipsForRoom failed")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !queryRes.HasBeenInRoom {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
JSON: spec.Forbidden("You aren't a member of the room and weren't previously a member of the room."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !queryRes.IsInRoom {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
JSON: spec.Forbidden("You aren't a member of the room and weren't previously a member of the room."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current membership events
|
|
||||||
var membershipsForRoomResp api.QueryMembershipsForRoomResponse
|
|
||||||
if err = rsAPI.QueryMembershipsForRoom(req.Context(), &api.QueryMembershipsForRoomRequest{
|
|
||||||
JoinedOnly: true,
|
|
||||||
RoomID: validRoomID.String(),
|
|
||||||
}, &membershipsForRoomResp); err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.QueryEventsByID failed")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var res getJoinedMembersResponse
|
|
||||||
res.Joined = make(map[string]joinedMember)
|
|
||||||
for _, ev := range membershipsForRoomResp.JoinEvents {
|
|
||||||
var content databaseJoinedMember
|
|
||||||
if err := json.Unmarshal(ev.Content, &content); err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("failed to unmarshal event content")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, err := rsAPI.QueryUserIDForSender(req.Context(), *validRoomID, spec.SenderID(ev.Sender))
|
|
||||||
if err != nil || userID == nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("rsAPI.QueryUserIDForSender failed")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Joined[userID.String()] = joinedMember(content)
|
|
||||||
}
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: res,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -73,8 +73,8 @@ func Password(
|
||||||
|
|
||||||
// Check if the existing password is correct.
|
// Check if the existing password is correct.
|
||||||
typePassword := auth.LoginTypePassword{
|
typePassword := auth.LoginTypePassword{
|
||||||
UserAPI: userAPI,
|
GetAccountByPassword: userAPI.QueryAccountByPassword,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
if _, authErr := typePassword.Login(req.Context(), &r.Auth.PasswordRequest); authErr != nil {
|
if _, authErr := typePassword.Login(req.Context(), &r.Auth.PasswordRequest); authErr != nil {
|
||||||
return *authErr
|
return *authErr
|
||||||
|
|
|
@ -16,7 +16,6 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -251,15 +250,11 @@ func updateProfile(
|
||||||
profile *authtypes.Profile,
|
profile *authtypes.Profile,
|
||||||
userID string, evTime time.Time,
|
userID string, evTime time.Time,
|
||||||
) (util.JSONResponse, error) {
|
) (util.JSONResponse, error) {
|
||||||
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
var res api.QueryRoomsForUserResponse
|
||||||
if err != nil {
|
err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||||
return util.JSONResponse{
|
UserID: device.UserID,
|
||||||
Code: http.StatusInternalServerError,
|
WantMembership: "join",
|
||||||
JSON: spec.Unknown("internal server error"),
|
}, &res)
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rooms, err := rsAPI.QueryRoomsForUser(ctx, *deviceUserID, "join")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("QueryRoomsForUser failed")
|
util.GetLogger(ctx).WithError(err).Error("QueryRoomsForUser failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -268,11 +263,6 @@ func updateProfile(
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
roomIDStrs := make([]string, len(rooms))
|
|
||||||
for i, room := range rooms {
|
|
||||||
roomIDStrs[i] = room.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
@ -283,7 +273,7 @@ func updateProfile(
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := buildMembershipEvents(
|
events, err := buildMembershipEvents(
|
||||||
ctx, roomIDStrs, *profile, userID, evTime, rsAPI,
|
ctx, res.RoomIDs, *profile, userID, evTime, rsAPI,
|
||||||
)
|
)
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
|
@ -300,7 +290,7 @@ func updateProfile(
|
||||||
}, e
|
}, e
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.SendEvents(ctx, rsAPI, api.KindNew, events, device.UserDomain(), domain, domain, nil, false); err != nil {
|
if err := api.SendEvents(ctx, rsAPI, api.KindNew, events, device.UserDomain(), domain, domain, nil, true); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
|
@ -372,10 +362,8 @@ func buildMembershipEvents(
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if senderID == nil {
|
|
||||||
return nil, fmt.Errorf("sender ID not found for %s in %s", *fullUserID, *validRoomID)
|
|
||||||
}
|
}
|
||||||
senderIDString := string(*senderID)
|
senderIDString := string(senderID)
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
SenderID: senderIDString,
|
SenderID: senderIDString,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
|
|
|
@ -23,12 +23,13 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetReceipt(req *http.Request, userAPI userapi.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
|
func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
|
||||||
timestamp := spec.AsTimestamp(time.Now())
|
timestamp := spec.AsTimestamp(time.Now())
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"roomID": roomID,
|
"roomID": roomID,
|
||||||
|
@ -53,13 +54,13 @@ func SetReceipt(req *http.Request, userAPI userapi.ClientUserAPI, syncProducer *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dataReq := userapi.InputAccountDataRequest{
|
dataReq := api.InputAccountDataRequest{
|
||||||
UserID: device.UserID,
|
UserID: device.UserID,
|
||||||
DataType: "m.fully_read",
|
DataType: "m.fully_read",
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
AccountData: data,
|
AccountData: data,
|
||||||
}
|
}
|
||||||
dataRes := userapi.InputAccountDataResponse{}
|
dataRes := api.InputAccountDataResponse{}
|
||||||
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
|
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
|
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
|
|
|
@ -34,8 +34,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type redactionContent struct {
|
type redactionContent struct {
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
Redacts string `json:"redacts"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type redactionResponse struct {
|
type redactionResponse struct {
|
||||||
|
@ -75,16 +74,6 @@ func SendRedaction(
|
||||||
return *resErr
|
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 {
|
if txnID != nil {
|
||||||
// Try to fetch response from transactionsCache
|
// Try to fetch response from transactionsCache
|
||||||
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
||||||
|
@ -99,7 +88,7 @@ func SendRedaction(
|
||||||
JSON: spec.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().String() != roomID {
|
if ev.RoomID() != roomID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
JSON: spec.NotFound("cannot redact event in another room"),
|
JSON: spec.NotFound("cannot redact event in another room"),
|
||||||
|
@ -109,7 +98,7 @@ func SendRedaction(
|
||||||
// "Users may redact their own events, and any user with a power level greater than or equal
|
// "Users may redact their own events, and any user with a power level greater than or equal
|
||||||
// to the redact power level of the room may redact events there"
|
// to the redact power level of the room may redact events there"
|
||||||
// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||||
allowedToRedact := ev.SenderID() == *senderID
|
allowedToRedact := ev.SenderID() == senderID
|
||||||
if !allowedToRedact {
|
if !allowedToRedact {
|
||||||
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
||||||
EventType: spec.MRoomPowerLevels,
|
EventType: spec.MRoomPowerLevels,
|
||||||
|
@ -130,7 +119,7 @@ func SendRedaction(
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allowedToRedact = pl.UserLevel(*senderID) >= pl.Redact
|
allowedToRedact = pl.UserLevel(senderID) >= pl.Redact
|
||||||
}
|
}
|
||||||
if !allowedToRedact {
|
if !allowedToRedact {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -147,16 +136,11 @@ func SendRedaction(
|
||||||
|
|
||||||
// create the new event and set all the fields we can
|
// create the new event and set all the fields we can
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
SenderID: string(*senderID),
|
SenderID: string(senderID),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: spec.MRoomRedaction,
|
Type: spec.MRoomRedaction,
|
||||||
Redacts: eventID,
|
Redacts: eventID,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Room version 11 expects the "redacts" field on the
|
|
||||||
// content field, so add it here as well
|
|
||||||
r.Redacts = eventID
|
|
||||||
|
|
||||||
err = proto.SetContent(r)
|
err = proto.SetContent(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("proto.SetContent failed")
|
util.GetLogger(req.Context()).WithError(err).Error("proto.SetContent failed")
|
||||||
|
|
|
@ -236,7 +236,7 @@ type authDict struct {
|
||||||
// TODO: Lots of custom keys depending on the type
|
// TODO: Lots of custom keys depending on the type
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://spec.matrix.org/v1.7/client-server-api/#user-interactive-authentication-api
|
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#user-interactive-authentication-api
|
||||||
type userInteractiveResponse struct {
|
type userInteractiveResponse struct {
|
||||||
Flows []authtypes.Flow `json:"flows"`
|
Flows []authtypes.Flow `json:"flows"`
|
||||||
Completed []authtypes.LoginType `json:"completed"`
|
Completed []authtypes.LoginType `json:"completed"`
|
||||||
|
@ -256,7 +256,7 @@ func newUserInteractiveResponse(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3register
|
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
|
||||||
type registerResponse struct {
|
type registerResponse struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
AccessToken string `json:"access_token,omitempty"`
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
|
@ -462,7 +462,7 @@ func validateApplicationService(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register processes a /register request.
|
// Register processes a /register request.
|
||||||
// https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3register
|
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
|
||||||
func Register(
|
func Register(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
userAPI userapi.ClientUserAPI,
|
userAPI userapi.ClientUserAPI,
|
||||||
|
@ -630,7 +630,6 @@ func handleGuestRegistration(
|
||||||
AccessToken: token,
|
AccessToken: token,
|
||||||
IPAddr: req.RemoteAddr,
|
IPAddr: req.RemoteAddr,
|
||||||
UserAgent: req.UserAgent(),
|
UserAgent: req.UserAgent(),
|
||||||
FromRegistration: true,
|
|
||||||
}, &devRes)
|
}, &devRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -648,16 +647,6 @@ func handleGuestRegistration(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// localpartMatchesExclusiveNamespaces will check if a given username matches any
|
|
||||||
// application service's exclusive users namespace
|
|
||||||
func localpartMatchesExclusiveNamespaces(
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
localpart string,
|
|
||||||
) bool {
|
|
||||||
userID := userutil.MakeUserID(localpart, cfg.Matrix.ServerName)
|
|
||||||
return cfg.Derived.ExclusiveApplicationServicesUsernameRegexp.MatchString(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleRegistrationFlow will direct and complete registration flow stages
|
// handleRegistrationFlow will direct and complete registration flow stages
|
||||||
// that the client has requested.
|
// that the client has requested.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
|
@ -706,7 +695,7 @@ func handleRegistrationFlow(
|
||||||
// If an access token is provided, ignore this check this is an appservice
|
// If an access token is provided, ignore this check this is an appservice
|
||||||
// request and we will validate in validateApplicationService
|
// request and we will validate in validateApplicationService
|
||||||
if len(cfg.Derived.ApplicationServices) != 0 &&
|
if len(cfg.Derived.ApplicationServices) != 0 &&
|
||||||
localpartMatchesExclusiveNamespaces(cfg, r.Username) {
|
UsernameMatchesExclusiveNamespaces(cfg, r.Username) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.ASExclusive("This username is reserved by an application service."),
|
JSON: spec.ASExclusive("This username is reserved by an application service."),
|
||||||
|
@ -783,7 +772,7 @@ func handleApplicationServiceRegistration(
|
||||||
|
|
||||||
// Check application service register user request is valid.
|
// Check application service register user request is valid.
|
||||||
// The application service's ID is returned if so.
|
// The application service's ID is returned if so.
|
||||||
appserviceID, err := internal.ValidateApplicationServiceRequest(
|
appserviceID, err := validateApplicationService(
|
||||||
cfg, r.Username, accessToken,
|
cfg, r.Username, accessToken,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -920,7 +909,6 @@ func completeRegistration(
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
IPAddr: ipAddr,
|
IPAddr: ipAddr,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
FromRegistration: true,
|
|
||||||
}, &devRes)
|
}, &devRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
@ -298,29 +298,25 @@ func Test_register(t *testing.T) {
|
||||||
guestsDisabled bool
|
guestsDisabled bool
|
||||||
enableRecaptcha bool
|
enableRecaptcha bool
|
||||||
captchaBody string
|
captchaBody string
|
||||||
// in case of an error, the expected response
|
wantResponse util.JSONResponse
|
||||||
wantErrorResponse util.JSONResponse
|
|
||||||
// in case of success, the expected username assigned
|
|
||||||
wantUsername string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "disallow guests",
|
name: "disallow guests",
|
||||||
kind: "guest",
|
kind: "guest",
|
||||||
guestsDisabled: true,
|
guestsDisabled: true,
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Forbidden(`Guest registration is disabled on "test"`),
|
JSON: spec.Forbidden(`Guest registration is disabled on "test"`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "allow guests",
|
name: "allow guests",
|
||||||
kind: "guest",
|
kind: "guest",
|
||||||
wantUsername: "1",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unknown login type",
|
name: "unknown login type",
|
||||||
loginType: "im.not.known",
|
loginType: "im.not.known",
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusNotImplemented,
|
Code: http.StatusNotImplemented,
|
||||||
JSON: spec.Unknown("unknown/unimplemented auth type"),
|
JSON: spec.Unknown("unknown/unimplemented auth type"),
|
||||||
},
|
},
|
||||||
|
@ -328,33 +324,25 @@ func Test_register(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "disabled registration",
|
name: "disabled registration",
|
||||||
registrationDisabled: true,
|
registrationDisabled: true,
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Forbidden(`Registration is disabled on "test"`),
|
JSON: spec.Forbidden(`Registration is disabled on "test"`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "successful registration, numeric ID",
|
name: "successful registration, numeric ID",
|
||||||
username: "",
|
username: "",
|
||||||
password: "someRandomPassword",
|
password: "someRandomPassword",
|
||||||
forceEmpty: true,
|
forceEmpty: true,
|
||||||
wantUsername: "2",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "successful registration",
|
name: "successful registration",
|
||||||
username: "success",
|
username: "success",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "successful registration, sequential numeric ID",
|
|
||||||
username: "",
|
|
||||||
password: "someRandomPassword",
|
|
||||||
forceEmpty: true,
|
|
||||||
wantUsername: "3",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "failing registration - user already exists",
|
name: "failing registration - user already exists",
|
||||||
username: "success",
|
username: "success",
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.UserInUse("Desired user ID is already taken."),
|
JSON: spec.UserInUse("Desired user ID is already taken."),
|
||||||
},
|
},
|
||||||
|
@ -364,14 +352,14 @@ func Test_register(t *testing.T) {
|
||||||
username: "LOWERCASED", // this is going to be lower-cased
|
username: "LOWERCASED", // this is going to be lower-cased
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid username",
|
name: "invalid username",
|
||||||
username: "#totalyNotValid",
|
username: "#totalyNotValid",
|
||||||
wantErrorResponse: *internal.UsernameResponse(internal.ErrUsernameInvalid),
|
wantResponse: *internal.UsernameResponse(internal.ErrUsernameInvalid),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "numeric username is forbidden",
|
name: "numeric username is forbidden",
|
||||||
username: "1337",
|
username: "1337",
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.InvalidUsername("Numeric user IDs are reserved"),
|
JSON: spec.InvalidUsername("Numeric user IDs are reserved"),
|
||||||
},
|
},
|
||||||
|
@ -379,7 +367,7 @@ func Test_register(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "disabled recaptcha login",
|
name: "disabled recaptcha login",
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: spec.Unknown(ErrCaptchaDisabled.Error()),
|
JSON: spec.Unknown(ErrCaptchaDisabled.Error()),
|
||||||
},
|
},
|
||||||
|
@ -388,7 +376,7 @@ func Test_register(t *testing.T) {
|
||||||
name: "enabled recaptcha, no response defined",
|
name: "enabled recaptcha, no response defined",
|
||||||
enableRecaptcha: true,
|
enableRecaptcha: true,
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.BadJSON(ErrMissingResponse.Error()),
|
JSON: spec.BadJSON(ErrMissingResponse.Error()),
|
||||||
},
|
},
|
||||||
|
@ -398,7 +386,7 @@ func Test_register(t *testing.T) {
|
||||||
enableRecaptcha: true,
|
enableRecaptcha: true,
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
captchaBody: `notvalid`,
|
captchaBody: `notvalid`,
|
||||||
wantErrorResponse: util.JSONResponse{
|
wantResponse: util.JSONResponse{
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusUnauthorized,
|
||||||
JSON: spec.BadJSON(ErrInvalidCaptcha.Error()),
|
JSON: spec.BadJSON(ErrInvalidCaptcha.Error()),
|
||||||
},
|
},
|
||||||
|
@ -410,11 +398,11 @@ func Test_register(t *testing.T) {
|
||||||
captchaBody: `success`,
|
captchaBody: `success`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "captcha invalid from remote",
|
name: "captcha invalid from remote",
|
||||||
enableRecaptcha: true,
|
enableRecaptcha: true,
|
||||||
loginType: authtypes.LoginTypeRecaptcha,
|
loginType: authtypes.LoginTypeRecaptcha,
|
||||||
captchaBody: `i should fail for other reasons`,
|
captchaBody: `i should fail for other reasons`,
|
||||||
wantErrorResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}},
|
wantResponse: util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,7 +416,7 @@ func Test_register(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
@ -498,8 +486,8 @@ func Test_register(t *testing.T) {
|
||||||
t.Fatalf("unexpected registration flows: %+v, want %+v", r.Flows, cfg.Derived.Registration.Flows)
|
t.Fatalf("unexpected registration flows: %+v, want %+v", r.Flows, cfg.Derived.Registration.Flows)
|
||||||
}
|
}
|
||||||
case spec.MatrixError:
|
case spec.MatrixError:
|
||||||
if !reflect.DeepEqual(tc.wantErrorResponse, resp) {
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantErrorResponse)
|
t.Fatalf("(%s), unexpected response: %+v, want: %+v", tc.name, resp, tc.wantResponse)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case registerResponse:
|
case registerResponse:
|
||||||
|
@ -517,13 +505,6 @@ func Test_register(t *testing.T) {
|
||||||
if r.DeviceID == "" {
|
if r.DeviceID == "" {
|
||||||
t.Fatalf("missing deviceID in response")
|
t.Fatalf("missing deviceID in response")
|
||||||
}
|
}
|
||||||
// if an expected username is provided, assert that it is a match
|
|
||||||
if tc.wantUsername != "" {
|
|
||||||
wantUserID := strings.ToLower(fmt.Sprintf("@%s:%s", tc.wantUsername, "test"))
|
|
||||||
if wantUserID != r.UserID {
|
|
||||||
t.Fatalf("unexpected userID: %s, want %s", r.UserID, wantUserID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
t.Logf("Got response: %T", resp.JSON)
|
t.Logf("Got response: %T", resp.JSON)
|
||||||
|
@ -560,29 +541,44 @@ func Test_register(t *testing.T) {
|
||||||
|
|
||||||
resp = Register(req, userAPI, &cfg.ClientAPI)
|
resp = Register(req, userAPI, &cfg.ClientAPI)
|
||||||
|
|
||||||
switch rr := resp.JSON.(type) {
|
switch resp.JSON.(type) {
|
||||||
case spec.InternalServerError, spec.MatrixError, util.JSONResponse:
|
case spec.InternalServerError:
|
||||||
if !reflect.DeepEqual(tc.wantErrorResponse, resp) {
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantErrorResponse)
|
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case registerResponse:
|
case spec.MatrixError:
|
||||||
// validate the response
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
if tc.wantUsername != "" {
|
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
||||||
// if an expected username is provided, assert that it is a match
|
|
||||||
wantUserID := strings.ToLower(fmt.Sprintf("@%s:%s", tc.wantUsername, "test"))
|
|
||||||
if wantUserID != rr.UserID {
|
|
||||||
t.Fatalf("unexpected userID: %s, want %s", rr.UserID, wantUserID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if rr.DeviceID != *reg.DeviceID {
|
return
|
||||||
t.Fatalf("unexpected deviceID: %s, want %s", rr.DeviceID, *reg.DeviceID)
|
case util.JSONResponse:
|
||||||
|
if !reflect.DeepEqual(tc.wantResponse, resp) {
|
||||||
|
t.Fatalf("unexpected response: %+v, want: %+v", resp, tc.wantResponse)
|
||||||
}
|
}
|
||||||
if rr.AccessToken == "" {
|
return
|
||||||
t.Fatalf("missing accessToken in response")
|
}
|
||||||
}
|
|
||||||
default:
|
rr, ok := resp.JSON.(registerResponse)
|
||||||
t.Fatalf("expected one of internalservererror, matrixerror, jsonresponse, registerresponse, got %T", resp.JSON)
|
if !ok {
|
||||||
|
t.Fatalf("expected a registerresponse, got %T", resp.JSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate the response
|
||||||
|
if tc.forceEmpty {
|
||||||
|
// when not supplying a username, one will be generated. Given this _SHOULD_ be
|
||||||
|
// the second user, set the username accordingly
|
||||||
|
reg.Username = "2"
|
||||||
|
}
|
||||||
|
wantUserID := strings.ToLower(fmt.Sprintf("@%s:%s", reg.Username, "test"))
|
||||||
|
if wantUserID != rr.UserID {
|
||||||
|
t.Fatalf("unexpected userID: %s, want %s", rr.UserID, wantUserID)
|
||||||
|
}
|
||||||
|
if rr.DeviceID != *reg.DeviceID {
|
||||||
|
t.Fatalf("unexpected deviceID: %s, want %s", rr.DeviceID, *reg.DeviceID)
|
||||||
|
}
|
||||||
|
if rr.AccessToken == "" {
|
||||||
|
t.Fatalf("missing accessToken in response")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -600,7 +596,7 @@ func TestRegisterUserWithDisplayName(t *testing.T) {
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
deviceName, deviceID := "deviceName", "deviceID"
|
deviceName, deviceID := "deviceName", "deviceID"
|
||||||
expectedDisplayName := "DisplayName"
|
expectedDisplayName := "DisplayName"
|
||||||
response := completeRegistration(
|
response := completeRegistration(
|
||||||
|
@ -641,7 +637,7 @@ func TestRegisterAdminUsingSharedSecret(t *testing.T) {
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.DisableMetrics)
|
||||||
rsAPI.SetFederationAPI(nil, nil)
|
rsAPI.SetFederationAPI(nil, nil)
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil, caching.DisableMetrics, testIsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, nil)
|
||||||
|
|
||||||
expectedDisplayName := "rabbit"
|
expectedDisplayName := "rabbit"
|
||||||
jsonStr := []byte(`{"admin":true,"mac":"24dca3bba410e43fe64b9b5c28306693bf3baa9f","nonce":"759f047f312b99ff428b21d581256f8592b8976e58bc1b543972dc6147e529a79657605b52d7becd160ff5137f3de11975684319187e06901955f79e5a6c5a79","password":"wonderland","username":"alice","displayname":"rabbit"}`)
|
jsonStr := []byte(`{"admin":true,"mac":"24dca3bba410e43fe64b9b5c28306693bf3baa9f","nonce":"759f047f312b99ff428b21d581256f8592b8976e58bc1b543972dc6147e529a79657605b52d7becd160ff5137f3de11975684319187e06901955f79e5a6c5a79","password":"wonderland","username":"alice","displayname":"rabbit"}`)
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
// 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"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
userAPI "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type reportEventRequest struct {
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Score int64 `json:"score"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReportEvent(
|
|
||||||
req *http.Request,
|
|
||||||
device *userAPI.Device,
|
|
||||||
roomID, eventID string,
|
|
||||||
rsAPI api.ClientRoomserverAPI,
|
|
||||||
) util.JSONResponse {
|
|
||||||
defer req.Body.Close() // nolint: errcheck
|
|
||||||
|
|
||||||
deviceUserID, err := spec.NewUserID(device.UserID, true)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
JSON: spec.NotFound("You don't have permission to report this event, bad userID"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The requesting user must be a member of the room
|
|
||||||
errRes := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
|
||||||
if errRes != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound, // Spec demands this...
|
|
||||||
JSON: spec.NotFound("The event was not found or you are not joined to the room."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the request
|
|
||||||
report := reportEventRequest{}
|
|
||||||
if resErr := httputil.UnmarshalJSONRequest(req, &report); resErr != nil {
|
|
||||||
return *resErr
|
|
||||||
}
|
|
||||||
|
|
||||||
queryRes := &api.QueryEventsByIDResponse{}
|
|
||||||
if err = rsAPI.QueryEventsByID(req.Context(), &api.QueryEventsByIDRequest{
|
|
||||||
RoomID: roomID,
|
|
||||||
EventIDs: []string{eventID},
|
|
||||||
}, queryRes); err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{Err: err.Error()},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No event was found or it was already redacted
|
|
||||||
if len(queryRes.Events) == 0 || queryRes.Events[0].Redacted() {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: spec.NotFound("The event was not found or you are not joined to the room."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = rsAPI.InsertReportedEvent(req.Context(), roomID, eventID, device.UserID, report.Reason, report.Score)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{Err: err.Error()},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: struct{}{},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -138,7 +138,7 @@ func QueryRoomHierarchy(req *http.Request, device *userapi.Device, roomIDStr str
|
||||||
walker = *cachedWalker
|
walker = *cachedWalker
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveredRooms, _, nextWalker, err := rsAPI.QueryNextRoomHierarchyPage(req.Context(), walker, limit)
|
discoveredRooms, nextWalker, err := rsAPI.QueryNextRoomHierarchyPage(req.Context(), walker, limit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
|
|
|
@ -44,19 +44,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"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
|
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
||||||
// to clients which need to make outbound HTTP requests.
|
// to clients which need to make outbound HTTP requests.
|
||||||
//
|
//
|
||||||
|
@ -109,22 +96,20 @@ func Setup(
|
||||||
|
|
||||||
if cfg.Matrix.WellKnownClientName != "" {
|
if cfg.Matrix.WellKnownClientName != "" {
|
||||||
logrus.Infof("Setting m.homeserver base_url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownClientName)
|
logrus.Infof("Setting m.homeserver base_url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownClientName)
|
||||||
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 {
|
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{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: response,
|
JSON: struct {
|
||||||
|
HomeserverName struct {
|
||||||
|
BaseUrl string `json:"base_url"`
|
||||||
|
} `json:"m.homeserver"`
|
||||||
|
}{
|
||||||
|
HomeserverName: struct {
|
||||||
|
BaseUrl string `json:"base_url"`
|
||||||
|
}{
|
||||||
|
BaseUrl: cfg.Matrix.WellKnownClientName,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})).Methods(http.MethodGet, http.MethodOptions)
|
})).Methods(http.MethodGet, http.MethodOptions)
|
||||||
}
|
}
|
||||||
|
@ -255,7 +240,7 @@ func Setup(
|
||||||
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
|
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
|
||||||
serverNotificationSender, err := getSenderDevice(context.Background(), rsAPI, userAPI, cfg)
|
serverNotificationSender, err := getSenderDevice(context.Background(), rsAPI, userAPI, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("unable to get account for sending server notices")
|
logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
|
||||||
}
|
}
|
||||||
|
|
||||||
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
|
synapseAdminRouter.Handle("/admin/v1/send_server_notice/{txnID}",
|
||||||
|
@ -1271,7 +1256,7 @@ func Setup(
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
return *r
|
return *r
|
||||||
}
|
}
|
||||||
return GetCapabilities(rsAPI)
|
return GetCapabilities()
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
@ -1448,7 +1433,7 @@ func Setup(
|
||||||
// Cross-signing device keys
|
// Cross-signing device keys
|
||||||
|
|
||||||
postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
return UploadCrossSigningDeviceKeys(req, userAPI, device, userAPI, cfg)
|
return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, userAPI, device, userAPI, cfg)
|
||||||
})
|
})
|
||||||
|
|
||||||
postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
@ -1513,58 +1498,4 @@ func Setup(
|
||||||
return GetPresence(req, device, natsClient, cfg.Matrix.JetStream.Prefixed(jetstream.RequestPresence), vars["userId"])
|
return GetPresence(req, device, natsClient, cfg.Matrix.JetStream.Prefixed(jetstream.RequestPresence), vars["userId"])
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/joined_members",
|
|
||||||
httputil.MakeAuthAPI("rooms_members", 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 GetJoinedMembers(req, device, vars["roomID"], rsAPI)
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/report/{eventID}",
|
|
||||||
httputil.MakeAuthAPI("report_event", 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 ReportEvent(req, device, vars["roomID"], vars["eventID"], rsAPI)
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
|
||||||
|
|
||||||
synapseAdminRouter.Handle("/admin/v1/event_reports",
|
|
||||||
httputil.MakeAdminAPI("admin_report_events", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
|
||||||
from := parseUint64OrDefault(req.URL.Query().Get("from"), 0)
|
|
||||||
limit := parseUint64OrDefault(req.URL.Query().Get("limit"), 100)
|
|
||||||
dir := req.URL.Query().Get("dir")
|
|
||||||
userID := req.URL.Query().Get("user_id")
|
|
||||||
roomID := req.URL.Query().Get("room_id")
|
|
||||||
|
|
||||||
// Go backwards if direction is empty or "b"
|
|
||||||
backwards := dir == "" || dir == "b"
|
|
||||||
return GetEventReports(req, rsAPI, from, limit, backwards, userID, roomID)
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
|
||||||
|
|
||||||
synapseAdminRouter.Handle("/admin/v1/event_reports/{reportID}",
|
|
||||||
httputil.MakeAdminAPI("admin_report_event", 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 GetEventReport(req, rsAPI, vars["reportID"])
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
|
||||||
|
|
||||||
synapseAdminRouter.Handle("/admin/v1/event_reports/{reportID}",
|
|
||||||
httputil.MakeAdminAPI("admin_report_event_delete", 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 DeleteEventReport(req, rsAPI, vars["reportID"])
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodDelete, http.MethodOptions)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/syncapi/synctypes"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
@ -93,30 +92,6 @@ 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
|
// 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
|
// this avoids a situation where events that are received in quick succession are sent to the roomserver in a jumbled order
|
||||||
userID := device.UserID
|
userID := device.UserID
|
||||||
|
@ -224,7 +199,7 @@ func SendEvent(
|
||||||
req.Context(), rsAPI,
|
req.Context(), rsAPI,
|
||||||
api.KindNew,
|
api.KindNew,
|
||||||
[]*types.HeaderedEvent{
|
[]*types.HeaderedEvent{
|
||||||
{PDU: e},
|
&types.HeaderedEvent{PDU: e},
|
||||||
},
|
},
|
||||||
device.UserDomain(),
|
device.UserDomain(),
|
||||||
domain,
|
domain,
|
||||||
|
@ -263,11 +238,7 @@ func SendEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePowerLevels(req *http.Request, r map[string]interface{}, roomID string, rsAPI api.ClientRoomserverAPI) error {
|
func updatePowerLevels(req *http.Request, r map[string]interface{}, roomID string, rsAPI api.ClientRoomserverAPI) error {
|
||||||
users, ok := r["users"]
|
userMap := r["users"].(map[string]interface{})
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
userMap := users.(map[string]interface{})
|
|
||||||
validRoomID, err := spec.NewRoomID(roomID)
|
validRoomID, err := spec.NewRoomID(roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -280,11 +251,8 @@ func updatePowerLevels(req *http.Request, r map[string]interface{}, roomID strin
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *uID)
|
senderID, err := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *uID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if senderID == nil {
|
|
||||||
util.GetLogger(req.Context()).Warnf("sender ID not found for %s in %s", uID, *validRoomID)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
userMap[string(*senderID)] = level
|
userMap[string(senderID)] = level
|
||||||
delete(userMap, user)
|
delete(userMap, user)
|
||||||
}
|
}
|
||||||
r["users"] = userMap
|
r["users"] = userMap
|
||||||
|
@ -348,21 +316,14 @@ func generateSendEvent(
|
||||||
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
senderID, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *fullUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusNotFound,
|
||||||
JSON: spec.NotFound("internal server error"),
|
JSON: spec.NotFound("Unable to find senderID for user"),
|
||||||
}
|
|
||||||
} 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
|
// create the new event and set all the fields we can
|
||||||
proto := gomatrixserverlib.ProtoEvent{
|
proto := gomatrixserverlib.ProtoEvent{
|
||||||
SenderID: string(*senderID),
|
SenderID: string(senderID),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
StateKey: stateKey,
|
StateKey: stateKey,
|
||||||
|
@ -442,7 +403,7 @@ func generateSendEvent(
|
||||||
JSON: spec.BadJSON("Cannot unmarshal the event content."),
|
JSON: spec.BadJSON("Cannot unmarshal the event content."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if content["replacement_room"] == e.RoomID().String() {
|
if content["replacement_room"] == e.RoomID() {
|
||||||
return nil, &util.JSONResponse{
|
return nil, &util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.InvalidParam("Cannot send tombstone event that points to the same room."),
|
JSON: spec.InvalidParam("Cannot send tombstone event that points to the same room."),
|
||||||
|
|
|
@ -1,275 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
@ -94,42 +95,34 @@ func SendServerNotice(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// get rooms for specified user
|
||||||
allUserRooms := []spec.RoomID{}
|
allUserRooms := []string{}
|
||||||
|
userRooms := api.QueryRoomsForUserResponse{}
|
||||||
// Get rooms the user is either joined, invited or has left.
|
// Get rooms the user is either joined, invited or has left.
|
||||||
for _, membership := range []string{"join", "invite", "leave"} {
|
for _, membership := range []string{"join", "invite", "leave"} {
|
||||||
userRooms, queryErr := rsAPI.QueryRoomsForUser(ctx, *userID, membership)
|
if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||||
if queryErr != nil {
|
UserID: r.UserID,
|
||||||
|
WantMembership: membership,
|
||||||
|
}, &userRooms); err != nil {
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
allUserRooms = append(allUserRooms, userRooms...)
|
allUserRooms = append(allUserRooms, userRooms.RoomIDs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get rooms of the sender
|
// get rooms of the sender
|
||||||
senderUserID, err := spec.NewUserID(fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName), true)
|
senderUserID := fmt.Sprintf("@%s:%s", cfgNotices.LocalPart, cfgClient.Matrix.ServerName)
|
||||||
if err != nil {
|
senderRooms := api.QueryRoomsForUserResponse{}
|
||||||
return util.JSONResponse{
|
if err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||||
Code: http.StatusInternalServerError,
|
UserID: senderUserID,
|
||||||
JSON: spec.Unknown("internal server error"),
|
WantMembership: "join",
|
||||||
}
|
}, &senderRooms); err != nil {
|
||||||
}
|
|
||||||
senderRooms, err := rsAPI.QueryRoomsForUser(ctx, *senderUserID, "join")
|
|
||||||
if err != nil {
|
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we have rooms in common
|
// check if we have rooms in common
|
||||||
commonRooms := []spec.RoomID{}
|
commonRooms := []string{}
|
||||||
for _, userRoomID := range allUserRooms {
|
for _, userRoomID := range allUserRooms {
|
||||||
for _, senderRoomID := range senderRooms {
|
for _, senderRoomID := range senderRooms.RoomIDs {
|
||||||
if userRoomID == senderRoomID {
|
if userRoomID == senderRoomID {
|
||||||
commonRooms = append(commonRooms, senderRoomID)
|
commonRooms = append(commonRooms, senderRoomID)
|
||||||
}
|
}
|
||||||
|
@ -142,12 +135,12 @@ func SendServerNotice(
|
||||||
|
|
||||||
var (
|
var (
|
||||||
roomID string
|
roomID string
|
||||||
roomVersion = rsAPI.DefaultRoomVersion()
|
roomVersion = version.DefaultRoomVersion()
|
||||||
)
|
)
|
||||||
|
|
||||||
// create a new room for the user
|
// create a new room for the user
|
||||||
if len(commonRooms) == 0 {
|
if len(commonRooms) == 0 {
|
||||||
powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID.String())
|
powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID)
|
||||||
powerLevelContent.Users[r.UserID] = -10 // taken from Synapse
|
powerLevelContent.Users[r.UserID] = -10 // taken from Synapse
|
||||||
pl, err := json.Marshal(powerLevelContent)
|
pl, err := json.Marshal(powerLevelContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -203,7 +196,7 @@ func SendServerNotice(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roomID = commonRooms[0].String()
|
roomID = commonRooms[0]
|
||||||
membershipRes := api.QueryMembershipForUserResponse{}
|
membershipRes := api.QueryMembershipForUserResponse{}
|
||||||
err = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: *deviceUserID, RoomID: roomID}, &membershipRes)
|
err = rsAPI.QueryMembershipForUser(ctx, &api.QueryMembershipForUserRequest{UserID: *deviceUserID, RoomID: roomID}, &membershipRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -215,7 +208,7 @@ func SendServerNotice(
|
||||||
}
|
}
|
||||||
if !membershipRes.IsInRoom {
|
if !membershipRes.IsInRoom {
|
||||||
// re-invite the user
|
// re-invite the user
|
||||||
res, err := sendInvite(ctx, senderDevice, roomID, r.UserID, "Server notice room", cfgClient, rsAPI, time.Now())
|
res, err := sendInvite(ctx, userAPI, senderDevice, roomID, r.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,16 +172,28 @@ func OnIncomingStateRequest(ctx context.Context, device *userapi.Device, rsAPI a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, ev := range stateAfterRes.StateEvents {
|
for _, ev := range stateAfterRes.StateEvents {
|
||||||
clientEvent, err := synctypes.ToClientEvent(ev, synctypes.FormatAll, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
sender := spec.UserID{}
|
||||||
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
evRoomID, err := spec.NewRoomID(ev.RoomID())
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("Failed converting to ClientEvent")
|
util.GetLogger(ctx).WithError(err).Error("Event roomID is invalid")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
userID, err := rsAPI.QueryUserIDForSender(ctx, *evRoomID, ev.SenderID())
|
||||||
|
if err == nil && userID != nil {
|
||||||
|
sender = *userID
|
||||||
|
}
|
||||||
|
|
||||||
|
sk := ev.StateKey()
|
||||||
|
if sk != nil && *sk != "" {
|
||||||
|
skUserID, err := rsAPI.QueryUserIDForSender(ctx, *evRoomID, spec.SenderID(*ev.StateKey()))
|
||||||
|
if err == nil && skUserID != nil {
|
||||||
|
skString := skUserID.String()
|
||||||
|
sk = &skString
|
||||||
|
}
|
||||||
|
}
|
||||||
stateEvents = append(
|
stateEvents = append(
|
||||||
stateEvents,
|
stateEvents,
|
||||||
*clientEvent,
|
synctypes.ToClientEvent(ev, synctypes.FormatAll, sender, sk),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,37 +217,6 @@ func OnIncomingStateTypeRequest(
|
||||||
var worldReadable bool
|
var worldReadable bool
|
||||||
var wantLatestState 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
|
// 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.
|
// the latest events or the last event from when the user was joined.
|
||||||
// Then include the requested event type and state key, assuming it
|
// Then include the requested event type and state key, assuming it
|
||||||
|
|
|
@ -1,253 +0,0 @@
|
||||||
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}
|
|
||||||
}
|
|
|
@ -366,11 +366,9 @@ func emit3PIDInviteEvent(
|
||||||
sender, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
sender, err := rsAPI.QuerySenderIDForUser(ctx, *validRoomID, *userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if sender == nil {
|
|
||||||
return fmt.Errorf("sender ID not found for %s in %s", *userID, *validRoomID)
|
|
||||||
}
|
}
|
||||||
proto := &gomatrixserverlib.ProtoEvent{
|
proto := &gomatrixserverlib.ProtoEvent{
|
||||||
SenderID: string(*sender),
|
SenderID: string(sender),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Type: "m.room.third_party_invite",
|
Type: "m.room.third_party_invite",
|
||||||
StateKey: &res.Token,
|
StateKey: &res.Token,
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (p *P2PMonolith) SetupDendrite(
|
||||||
)
|
)
|
||||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, enableMetrics, fsAPI.IsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation)
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||||
|
|
||||||
|
@ -221,8 +221,8 @@ func (p *P2PMonolith) closeAllResources() {
|
||||||
p.httpServerMu.Lock()
|
p.httpServerMu.Lock()
|
||||||
if p.httpServer != nil {
|
if p.httpServer != nil {
|
||||||
_ = p.httpServer.Shutdown(context.Background())
|
_ = p.httpServer.Shutdown(context.Background())
|
||||||
|
p.httpServerMu.Unlock()
|
||||||
}
|
}
|
||||||
p.httpServerMu.Unlock()
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case p.stopHandlingEvents <- true:
|
case p.stopHandlingEvents <- true:
|
||||||
|
|
|
@ -17,13 +17,13 @@ package relay
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
relayServerAPI "github.com/matrix-org/dendrite/relayapi/api"
|
relayServerAPI "github.com/matrix-org/dendrite/relayapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -54,7 +54,7 @@ func NewRelayServerRetriever(
|
||||||
federationAPI: federationAPI,
|
federationAPI: federationAPI,
|
||||||
relayAPI: relayAPI,
|
relayAPI: relayAPI,
|
||||||
relayServersQueried: make(map[spec.ServerName]bool),
|
relayServersQueried: make(map[spec.ServerName]bool),
|
||||||
running: atomic.Bool{},
|
running: *atomic.NewBool(false),
|
||||||
quit: quit,
|
quit: quit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Yggdrasil Demo
|
# Yggdrasil Demo
|
||||||
|
|
||||||
This is the Dendrite Yggdrasil demo! It's easy to get started - all you need is Go 1.20 or later.
|
This is the Dendrite Yggdrasil demo! It's easy to get started - all you need is Go 1.18 or later.
|
||||||
|
|
||||||
To run the homeserver, start at the root of the Dendrite repository and run:
|
To run the homeserver, start at the root of the Dendrite repository and run:
|
||||||
|
|
||||||
|
|
|
@ -213,14 +213,13 @@ func main() {
|
||||||
natsInstance := jetstream.NATSInstance{}
|
natsInstance := jetstream.NATSInstance{}
|
||||||
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.EnableMetrics)
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, &natsInstance, caches, caching.EnableMetrics)
|
||||||
|
|
||||||
fsAPI := federationapi.NewInternalAPI(
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation)
|
||||||
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
|
|
||||||
)
|
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, caching.EnableMetrics, fsAPI.IsBlacklistedOrBackingOff)
|
|
||||||
|
|
||||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||||
rsAPI.SetAppserviceAPI(asAPI)
|
rsAPI.SetAppserviceAPI(asAPI)
|
||||||
|
fsAPI := federationapi.NewInternalAPI(
|
||||||
|
processCtx, cfg, cm, &natsInstance, federation, rsAPI, caches, keyRing, true,
|
||||||
|
)
|
||||||
|
|
||||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -54,7 +55,7 @@ var latest, _ = semver.NewVersion("v6.6.6") // Dummy version, used as "HEAD"
|
||||||
// due to the error:
|
// due to the error:
|
||||||
// When using COPY with more than one source file, the destination must be a directory and end with a /
|
// 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.
|
// We need to run a postgres anyway, so use the dockerfile associated with Complement instead.
|
||||||
const DockerfilePostgreSQL = `FROM golang:1.20-bookworm as build
|
const DockerfilePostgreSQL = `FROM golang:1.18-buster as build
|
||||||
RUN apt-get update && apt-get install -y postgresql
|
RUN apt-get update && apt-get install -y postgresql
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
ARG BINARY
|
ARG BINARY
|
||||||
|
@ -73,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
|
# 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
|
RUN sed -i "s%connection_string:.*$%connection_string: postgresql://postgres@localhost/postgres?sslmode=disable%g" dendrite.yaml
|
||||||
# No password when connecting over localhost
|
# No password when connecting over localhost
|
||||||
RUN sed -i "s%127.0.0.1/32 scram-sha-256%127.0.0.1/32 trust%g" /etc/postgresql/15/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
|
# Bump up max conns for moar concurrency
|
||||||
RUN sed -i 's/max_connections = 100/max_connections = 2000/g' /etc/postgresql/15/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
|
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
|
# This entry script starts postgres, waits for it to be up then starts dendrite
|
||||||
RUN echo '\
|
RUN echo '\
|
||||||
#!/bin/bash -eu \n\
|
#!/bin/bash -eu \n\
|
||||||
pg_lsclusters \n\
|
pg_lsclusters \n\
|
||||||
pg_ctlcluster 15 main start \n\
|
pg_ctlcluster 11 main start \n\
|
||||||
\n\
|
\n\
|
||||||
until pg_isready \n\
|
until pg_isready \n\
|
||||||
do \n\
|
do \n\
|
||||||
|
@ -100,7 +101,7 @@ ENV BINARY=dendrite
|
||||||
EXPOSE 8008 8448
|
EXPOSE 8008 8448
|
||||||
CMD /build/run_dendrite.sh`
|
CMD /build/run_dendrite.sh`
|
||||||
|
|
||||||
const DockerfileSQLite = `FROM golang:1.20-bookworm as build
|
const DockerfileSQLite = `FROM golang:1.18-buster as build
|
||||||
RUN apt-get update && apt-get install -y postgresql
|
RUN apt-get update && apt-get install -y postgresql
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
ARG BINARY
|
ARG BINARY
|
||||||
|
@ -118,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
|
# 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
|
# the postgresql folder so let's just use that for simplicity
|
||||||
RUN sed -i "s%connection_string:.file:%connection_string: file:\/var\/lib\/postgresql\/15\/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
|
# This entry script starts postgres, waits for it to be up then starts dendrite
|
||||||
RUN echo '\
|
RUN echo '\
|
||||||
|
@ -401,7 +402,7 @@ func runImage(dockerClient *client.Client, volumeName string, branchNameToImageI
|
||||||
{
|
{
|
||||||
Type: mount.TypeVolume,
|
Type: mount.TypeVolume,
|
||||||
Source: volumeName,
|
Source: volumeName,
|
||||||
Target: "/var/lib/postgresql/15/main",
|
Target: "/var/lib/postgresql/11/main",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil, nil, "dendrite_upgrade_test_"+branchName)
|
}, nil, nil, "dendrite_upgrade_test_"+branchName)
|
||||||
|
@ -514,7 +515,7 @@ func testCreateAccount(dockerClient *client.Client, version *semver.Version, con
|
||||||
}
|
}
|
||||||
defer response.Close()
|
defer response.Close()
|
||||||
|
|
||||||
data, err := io.ReadAll(response.Reader)
|
data, err := ioutil.ReadAll(response.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -556,8 +557,8 @@ func cleanup(dockerClient *client.Client) {
|
||||||
})
|
})
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
log.Printf("Removing container: %v %v\n", c.ID, c.Names)
|
log.Printf("Removing container: %v %v\n", c.ID, c.Names)
|
||||||
timeout := 1
|
s := time.Second
|
||||||
_ = dockerClient.ContainerStop(context.Background(), c.ID, container.StopOptions{Timeout: &timeout})
|
_ = dockerClient.ContainerStop(context.Background(), c.ID, &s)
|
||||||
_ = dockerClient.ContainerRemove(context.Background(), c.ID, types.ContainerRemoveOptions{
|
_ = dockerClient.ContainerRemove(context.Background(), c.ID, types.ContainerRemoveOptions{
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
})
|
||||||
|
@ -591,7 +592,7 @@ func main() {
|
||||||
branchToImageID := buildDendriteImages(httpClient, dockerClient, *flagTempDir, *flagBuildConcurrency, versions)
|
branchToImageID := buildDendriteImages(httpClient, dockerClient, *flagTempDir, *flagBuildConcurrency, versions)
|
||||||
|
|
||||||
// make a shared postgres volume
|
// make a shared postgres volume
|
||||||
volume, err := dockerClient.VolumeCreate(context.Background(), volume.CreateOptions{
|
volume, err := dockerClient.VolumeCreate(context.Background(), volume.VolumeCreateBody{
|
||||||
Name: "dendrite_upgrade_test",
|
Name: "dendrite_upgrade_test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
dendriteUpgradeTestLabel: "yes",
|
dendriteUpgradeTestLabel: "yes",
|
||||||
|
|
|
@ -162,7 +162,7 @@ func main() {
|
||||||
// dependency. Other components also need updating after their dependencies are up.
|
// dependency. Other components also need updating after their dependencies are up.
|
||||||
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
rsAPI.SetFederationAPI(fsAPI, keyRing)
|
||||||
|
|
||||||
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federationClient, caching.EnableMetrics, fsAPI.IsBlacklistedOrBackingOff)
|
userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federationClient)
|
||||||
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI)
|
||||||
|
|
||||||
rsAPI.SetAppserviceAPI(asAPI)
|
rsAPI.SetAppserviceAPI(asAPI)
|
||||||
|
|
|
@ -11,11 +11,13 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver"
|
||||||
"github.com/matrix-org/dendrite/roomserver/state"
|
"github.com/matrix-org/dendrite/roomserver/state"
|
||||||
"github.com/matrix-org/dendrite/roomserver/storage"
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup"
|
"github.com/matrix-org/dendrite/setup"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
@ -33,19 +35,6 @@ var roomVersion = flag.String("roomversion", "5", "the room version to parse eve
|
||||||
var filterType = flag.String("filtertype", "", "the event types to filter on")
|
var filterType = flag.String("filtertype", "", "the event types to filter on")
|
||||||
var difference = flag.Bool("difference", false, "whether to calculate the difference between snapshots")
|
var difference = flag.Bool("difference", false, "whether to calculate the difference between snapshots")
|
||||||
|
|
||||||
// dummyQuerier implements QuerySenderIDAPI. Does **NOT** do any "magic" for pseudoID rooms
|
|
||||||
// to avoid having to "start" a full roomserver API.
|
|
||||||
type dummyQuerier struct{}
|
|
||||||
|
|
||||||
func (d dummyQuerier) QuerySenderIDForUser(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
|
|
||||||
s := spec.SenderIDFromUserID(userID)
|
|
||||||
return &s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dummyQuerier) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
|
||||||
return senderID.ToUserID(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:gocyclo
|
// nolint:gocyclo
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -67,32 +56,27 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Fetching", len(snapshotNIDs), "snapshot NIDs")
|
||||||
|
|
||||||
processCtx := process.NewProcessContext()
|
processCtx := process.NewProcessContext()
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
||||||
|
|
||||||
dbOpts := cfg.RoomServer.Database
|
|
||||||
if dbOpts.ConnectionString == "" {
|
|
||||||
dbOpts = cfg.Global.DatabaseOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Opening database")
|
|
||||||
roomserverDB, err := storage.Open(
|
roomserverDB, err := storage.Open(
|
||||||
processCtx.Context(), cm, &dbOpts,
|
processCtx.Context(), cm, &cfg.RoomServer.Database,
|
||||||
caching.NewRistrettoCache(8*1024*1024, time.Minute*5, caching.DisableMetrics),
|
caching.NewRistrettoCache(128*1024*1024, time.Hour, true),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rsAPI := dummyQuerier{}
|
natsInstance := &jetstream.NATSInstance{}
|
||||||
|
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm,
|
||||||
|
natsInstance, caching.NewRistrettoCache(128*1024*1024, time.Hour, true), false)
|
||||||
|
|
||||||
roomInfo := &types.RoomInfo{
|
roomInfo := &types.RoomInfo{
|
||||||
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
RoomVersion: gomatrixserverlib.RoomVersion(*roomVersion),
|
||||||
}
|
}
|
||||||
stateres := state.NewStateResolution(roomserverDB, roomInfo, rsAPI)
|
stateres := state.NewStateResolution(roomserverDB, roomInfo, rsAPI)
|
||||||
|
|
||||||
fmt.Println("Fetching", len(snapshotNIDs), "snapshot NIDs")
|
|
||||||
|
|
||||||
if *difference {
|
if *difference {
|
||||||
if len(snapshotNIDs) != 2 {
|
if len(snapshotNIDs) != 2 {
|
||||||
panic("need exactly two state snapshot NIDs to calculate difference")
|
panic("need exactly two state snapshot NIDs to calculate difference")
|
||||||
|
@ -202,25 +186,12 @@ func main() {
|
||||||
authEvents[i] = authEventEntries[i].PDU
|
authEvents[i] = authEventEntries[i].PDU
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the roomNID
|
|
||||||
roomInfo, err = roomserverDB.RoomInfo(ctx, authEvents[0].RoomID().String())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Resolving state")
|
fmt.Println("Resolving state")
|
||||||
var resolved Events
|
var resolved Events
|
||||||
resolved, err = gomatrixserverlib.ResolveConflicts(
|
resolved, err = gomatrixserverlib.ResolveConflicts(
|
||||||
gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
return rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
},
|
},
|
||||||
func(eventID string) bool {
|
|
||||||
isRejected, rejectedErr := roomserverDB.IsEventRejected(ctx, roomInfo.RoomNID, eventID)
|
|
||||||
if rejectedErr != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return isRejected
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -72,10 +72,6 @@ global:
|
||||||
# The base URL to delegate client-server communications to e.g. https://localhost
|
# The base URL to delegate client-server communications to e.g. https://localhost
|
||||||
well_known_client_name: ""
|
well_known_client_name: ""
|
||||||
|
|
||||||
# The server name to delegate sliding sync communications to, with optional port.
|
|
||||||
# Requires `well_known_client_name` to also be configured.
|
|
||||||
well_known_sliding_sync_proxy: ""
|
|
||||||
|
|
||||||
# Lists of domains that the server will trust as identity servers to verify third
|
# Lists of domains that the server will trust as identity servers to verify third
|
||||||
# party identifiers such as phone numbers and email addresses.
|
# party identifiers such as phone numbers and email addresses.
|
||||||
trusted_third_party_id_servers:
|
trusted_third_party_id_servers:
|
||||||
|
@ -154,13 +150,6 @@ app_service_api:
|
||||||
# to be sent to an insecure endpoint.
|
# to be sent to an insecure endpoint.
|
||||||
disable_tls_validation: false
|
disable_tls_validation: false
|
||||||
|
|
||||||
# Send the access_token query parameter with appservice requests in addition
|
|
||||||
# to the Authorization header. This can cause hs_tokens to be saved to logs,
|
|
||||||
# so it should not be enabled unless absolutely necessary.
|
|
||||||
legacy_auth: false
|
|
||||||
# Use the legacy unprefixed paths for appservice requests.
|
|
||||||
legacy_paths: false
|
|
||||||
|
|
||||||
# Appservice configuration files to load into this homeserver.
|
# Appservice configuration files to load into this homeserver.
|
||||||
config_files:
|
config_files:
|
||||||
# - /path/to/appservice_registration.yaml
|
# - /path/to/appservice_registration.yaml
|
||||||
|
@ -332,10 +321,6 @@ user_api:
|
||||||
auto_join_rooms:
|
auto_join_rooms:
|
||||||
# - "#main:matrix.org"
|
# - "#main:matrix.org"
|
||||||
|
|
||||||
# The number of workers to start for the DeviceListUpdater. Defaults to 8.
|
|
||||||
# This only needs updating if the "InputDeviceListUpdate" stream keeps growing indefinitely.
|
|
||||||
# worker_count: 8
|
|
||||||
|
|
||||||
# Configuration for Opentracing.
|
# Configuration for Opentracing.
|
||||||
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
|
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
|
||||||
# how this works and how to set it up.
|
# how this works and how to set it up.
|
||||||
|
|
|
@ -24,7 +24,7 @@ No, although a good portion of the Matrix specification has been implemented. Mo
|
||||||
|
|
||||||
Dendrite development is currently supported by a small team of developers and due to those limited resources, the majority of the effort is focused on getting Dendrite to be
|
Dendrite development is currently supported by a small team of developers and due to those limited resources, the majority of the effort is focused on getting Dendrite to be
|
||||||
specification complete. If there are major features you're requesting (e.g. new administration endpoints), we'd like to strongly encourage you to join the community in supporting
|
specification complete. If there are major features you're requesting (e.g. new administration endpoints), we'd like to strongly encourage you to join the community in supporting
|
||||||
the development efforts through [contributing](./development/CONTRIBUTING.md).
|
the development efforts through [contributing](../development/contributing).
|
||||||
|
|
||||||
## Is there a migration path from Synapse to Dendrite?
|
## Is there a migration path from Synapse to Dendrite?
|
||||||
|
|
||||||
|
@ -105,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?
|
## How do I reset somebody's password on my server?
|
||||||
|
|
||||||
Use the admin endpoint [resetpassword](./administration/4_adminapi.md#post-_dendriteadminresetpassworduserid)
|
Use the admin endpoint [resetpassword](./administration/adminapi#post-_dendriteadminresetpassworduserid)
|
||||||
|
|
||||||
## Should I use PostgreSQL or SQLite for my databases?
|
## Should I use PostgreSQL or SQLite for my databases?
|
||||||
|
|
||||||
|
@ -117,7 +117,6 @@ The list of files that need to be stored is:
|
||||||
- matrix-key.pem
|
- matrix-key.pem
|
||||||
- dendrite.yaml
|
- dendrite.yaml
|
||||||
- the postgres or sqlite DB
|
- the postgres or sqlite DB
|
||||||
- the jetstream directory
|
|
||||||
- the media store
|
- the media store
|
||||||
- the search index (although this can be regenerated)
|
- the search index (although this can be regenerated)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ GEM
|
||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.11.1)
|
coffee-script-source (1.11.1)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
commonmarker (0.23.10)
|
commonmarker (0.23.9)
|
||||||
concurrent-ruby (1.2.0)
|
concurrent-ruby (1.2.0)
|
||||||
dnsruby (1.61.9)
|
dnsruby (1.61.9)
|
||||||
simpleidn (~> 0.1)
|
simpleidn (~> 0.1)
|
||||||
|
@ -231,9 +231,9 @@ GEM
|
||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.17.0)
|
minitest (5.17.0)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
nokogiri (1.16.2-arm64-darwin)
|
nokogiri (1.14.3-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.2-x86_64-linux)
|
nokogiri (1.14.3-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
octokit (4.22.0)
|
octokit (4.22.0)
|
||||||
faraday (>= 0.9)
|
faraday (>= 0.9)
|
||||||
|
@ -241,7 +241,7 @@ GEM
|
||||||
pathutil (0.16.2)
|
pathutil (0.16.2)
|
||||||
forwardable-extended (~> 2.6)
|
forwardable-extended (~> 2.6)
|
||||||
public_suffix (4.0.7)
|
public_suffix (4.0.7)
|
||||||
racc (1.7.3)
|
racc (1.6.2)
|
||||||
rb-fsevent (0.11.1)
|
rb-fsevent (0.11.1)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
|
|
|
@ -75,7 +75,7 @@ This endpoint instructs Dendrite to immediately query `/devices/{userID}` on a f
|
||||||
|
|
||||||
## POST `/_dendrite/admin/purgeRoom/{roomID}`
|
## 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.
|
This endpoint instructs Dendrite to remove the given room from its database. Before doing so, it will evacuate all local users from the room. It does **NOT** remove media files. Depending on the size of the room, this may take a while. Will return an empty JSON once other components were instructed to delete the room.
|
||||||
|
|
||||||
## POST `/_synapse/admin/v1/send_server_notice`
|
## POST `/_synapse/admin/v1/send_server_notice`
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ Consider enabling the DNS cache by modifying the `global` section of your config
|
||||||
## Time synchronisation
|
## Time synchronisation
|
||||||
|
|
||||||
Matrix relies heavily on TLS which requires the system time to be correct. If the clock
|
Matrix relies heavily on TLS which requires the system time to be correct. If the clock
|
||||||
drifts then you may find that federation will not work reliably (or at all) and clients may
|
drifts then you may find that federation no works reliably (or at all) and clients may
|
||||||
struggle to connect to your Dendrite server.
|
struggle to connect to your Dendrite server.
|
||||||
|
|
||||||
Ensure that the time is synchronised on your system by enabling NTP sync.
|
Ensure that the time is synchronised on your system by enabling NTP sync.
|
||||||
|
|
|
@ -109,7 +109,7 @@ To configure the connection to a remote Postgres, you can use the following envi
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
POSTGRES_USER=postgres
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=yourPostgresPassword
|
POSTGERS_PASSWORD=yourPostgresPassword
|
||||||
POSTGRES_HOST=localhost
|
POSTGRES_HOST=localhost
|
||||||
POSTGRES_DB=postgres # the superuser database to use
|
POSTGRES_DB=postgres # the superuser database to use
|
||||||
```
|
```
|
||||||
|
|
|
@ -59,7 +59,7 @@ In order to install Dendrite, you will need to satisfy the following dependencie
|
||||||
|
|
||||||
### Go
|
### Go
|
||||||
|
|
||||||
At this time, Dendrite supports being built with Go 1.20 or later. We do not support building
|
At this time, Dendrite supports being built with Go 1.18 or later. We do not support building
|
||||||
Dendrite with older versions of Go than this. If you are installing Go using a package manager,
|
Dendrite with older versions of Go than this. If you are installing Go using a package manager,
|
||||||
you should check (by running `go version`) that you are using a suitable version before you start.
|
you should check (by running `go version`) that you are using a suitable version before you start.
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,6 @@ docker run --rm --entrypoint="/usr/bin/generate-keys" \
|
||||||
-v $(pwd)/config:/mnt \
|
-v $(pwd)/config:/mnt \
|
||||||
matrixdotorg/dendrite-monolith:latest \
|
matrixdotorg/dendrite-monolith:latest \
|
||||||
-private-key /mnt/matrix_key.pem
|
-private-key /mnt/matrix_key.pem
|
||||||
|
|
||||||
# Windows equivalent: docker run --rm --entrypoint="/usr/bin/generate-keys" -v %cd%/config:/mnt matrixdotorg/dendrite-monolith:latest -private-key /mnt/matrix_key.pem
|
|
||||||
```
|
```
|
||||||
(**NOTE**: This only needs to be executed **once**, as you otherwise overwrite the key)
|
(**NOTE**: This only needs to be executed **once**, as you otherwise overwrite the key)
|
||||||
|
|
||||||
|
@ -46,8 +44,6 @@ docker run --rm --entrypoint="/bin/sh" \
|
||||||
-dir /var/dendrite/ \
|
-dir /var/dendrite/ \
|
||||||
-db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable \
|
-db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable \
|
||||||
-server YourDomainHere > /mnt/dendrite.yaml"
|
-server YourDomainHere > /mnt/dendrite.yaml"
|
||||||
|
|
||||||
# Windows equivalent: docker run --rm --entrypoint="/bin/sh" -v %cd%/config:/mnt matrixdotorg/dendrite-monolith:latest -c "/usr/bin/generate-config -dir /var/dendrite/ -db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable -server YourDomainHere > /mnt/dendrite.yaml"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then change `config/dendrite.yaml` to your liking.
|
You can then change `config/dendrite.yaml` to your liking.
|
||||||
|
|
|
@ -74,6 +74,7 @@ type RoomserverFederationAPI interface {
|
||||||
GetEventAuth(ctx context.Context, origin, s spec.ServerName, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) (res fclient.RespEventAuth, err error)
|
GetEventAuth(ctx context.Context, origin, s spec.ServerName, roomVersion gomatrixserverlib.RoomVersion, roomID, eventID string) (res fclient.RespEventAuth, err error)
|
||||||
GetEvent(ctx context.Context, origin, s spec.ServerName, eventID string) (res gomatrixserverlib.Transaction, err error)
|
GetEvent(ctx context.Context, origin, s spec.ServerName, eventID string) (res gomatrixserverlib.Transaction, err error)
|
||||||
LookupMissingEvents(ctx context.Context, origin, s spec.ServerName, roomID string, missing fclient.MissingEvents, roomVersion gomatrixserverlib.RoomVersion) (res fclient.RespMissingEvents, err error)
|
LookupMissingEvents(ctx context.Context, origin, s spec.ServerName, roomID string, missing fclient.MissingEvents, roomVersion gomatrixserverlib.RoomVersion) (res fclient.RespMissingEvents, err error)
|
||||||
|
CurrentMembership(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (string, error)
|
||||||
|
|
||||||
RoomHierarchies(ctx context.Context, origin, dst spec.ServerName, roomID string, suggestedOnly bool) (res fclient.RoomHierarchyResponse, err error)
|
RoomHierarchies(ctx context.Context, origin, dst spec.ServerName, roomID string, suggestedOnly bool) (res fclient.RoomHierarchyResponse, err error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,27 +117,19 @@ func (t *KeyChangeConsumer) onDeviceKeyMessage(m api.DeviceMessage) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := spec.NewUserID(m.UserID, true)
|
var queryRes roomserverAPI.QueryRoomsForUserResponse
|
||||||
if err != nil {
|
err = t.rsAPI.QueryRoomsForUser(t.ctx, &roomserverAPI.QueryRoomsForUserRequest{
|
||||||
sentry.CaptureException(err)
|
UserID: m.UserID,
|
||||||
logger.WithError(err).Error("invalid user ID")
|
WantMembership: "join",
|
||||||
return true
|
}, &queryRes)
|
||||||
}
|
|
||||||
|
|
||||||
roomIDs, err := t.rsAPI.QueryRoomsForUser(t.ctx, *userID, "join")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sentry.CaptureException(err)
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("failed to calculate joined rooms for user")
|
logger.WithError(err).Error("failed to calculate joined rooms for user")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
roomIDStrs := make([]string, len(roomIDs))
|
|
||||||
for i, room := range roomIDs {
|
|
||||||
roomIDStrs[i] = room.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// send this key change to all servers who share rooms with this user.
|
// send this key change to all servers who share rooms with this user.
|
||||||
destinations, err := t.db.GetJoinedHostsForRooms(t.ctx, roomIDStrs, true, true)
|
destinations, err := t.db.GetJoinedHostsForRooms(t.ctx, queryRes.RoomIDs, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sentry.CaptureException(err)
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("failed to calculate joined hosts for rooms user is in")
|
logger.WithError(err).Error("failed to calculate joined hosts for rooms user is in")
|
||||||
|
@ -187,27 +179,18 @@ func (t *KeyChangeConsumer) onCrossSigningMessage(m api.DeviceMessage) bool {
|
||||||
}
|
}
|
||||||
logger := logrus.WithField("user_id", output.UserID)
|
logger := logrus.WithField("user_id", output.UserID)
|
||||||
|
|
||||||
outputUserID, err := spec.NewUserID(output.UserID, true)
|
var queryRes roomserverAPI.QueryRoomsForUserResponse
|
||||||
if err != nil {
|
err = t.rsAPI.QueryRoomsForUser(t.ctx, &roomserverAPI.QueryRoomsForUserRequest{
|
||||||
sentry.CaptureException(err)
|
UserID: output.UserID,
|
||||||
logrus.WithError(err).Errorf("invalid user ID")
|
WantMembership: "join",
|
||||||
return true
|
}, &queryRes)
|
||||||
}
|
|
||||||
|
|
||||||
rooms, err := t.rsAPI.QueryRoomsForUser(t.ctx, *outputUserID, "join")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sentry.CaptureException(err)
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("fedsender key change consumer: failed to calculate joined rooms for user")
|
logger.WithError(err).Error("fedsender key change consumer: failed to calculate joined rooms for user")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
roomIDStrs := make([]string, len(rooms))
|
|
||||||
for i, room := range rooms {
|
|
||||||
roomIDStrs[i] = room.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// send this key change to all servers who share rooms with this user.
|
// send this key change to all servers who share rooms with this user.
|
||||||
destinations, err := t.db.GetJoinedHostsForRooms(t.ctx, roomIDStrs, true, true)
|
destinations, err := t.db.GetJoinedHostsForRooms(t.ctx, queryRes.RoomIDs, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sentry.CaptureException(err)
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("fedsender key change consumer: failed to calculate joined hosts for rooms user is in")
|
logger.WithError(err).Error("fedsender key change consumer: failed to calculate joined hosts for rooms user is in")
|
||||||
|
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
@ -95,23 +94,16 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedUserID, err := spec.NewUserID(userID, true)
|
var queryRes roomserverAPI.QueryRoomsForUserResponse
|
||||||
if err != nil {
|
err = t.rsAPI.QueryRoomsForUser(t.ctx, &roomserverAPI.QueryRoomsForUserRequest{
|
||||||
util.GetLogger(ctx).WithError(err).WithField("user_id", userID).Error("invalid user ID")
|
UserID: userID,
|
||||||
return true
|
WantMembership: "join",
|
||||||
}
|
}, &queryRes)
|
||||||
|
|
||||||
roomIDs, err := t.rsAPI.QueryRoomsForUser(t.ctx, *parsedUserID, "join")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("failed to calculate joined rooms for user")
|
log.WithError(err).Error("failed to calculate joined rooms for user")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
roomIDStrs := make([]string, len(roomIDs))
|
|
||||||
for i, roomID := range roomIDs {
|
|
||||||
roomIDStrs[i] = roomID.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
presence := msg.Header.Get("presence")
|
presence := msg.Header.Get("presence")
|
||||||
|
|
||||||
ts, err := strconv.Atoi(msg.Header.Get("last_active_ts"))
|
ts, err := strconv.Atoi(msg.Header.Get("last_active_ts"))
|
||||||
|
@ -120,7 +112,7 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// send this presence to all servers who share rooms with this user.
|
// send this presence to all servers who share rooms with this user.
|
||||||
joined, err := t.db.GetJoinedHostsForRooms(t.ctx, roomIDStrs, true, true)
|
joined, err := t.db.GetJoinedHostsForRooms(t.ctx, queryRes.RoomIDs, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("failed to get joined hosts")
|
log.WithError(err).Error("failed to get joined hosts")
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -176,7 +176,7 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent, rew
|
||||||
// Finally, work out if there are any more events missing.
|
// Finally, work out if there are any more events missing.
|
||||||
if len(missingEventIDs) > 0 {
|
if len(missingEventIDs) > 0 {
|
||||||
eventsReq := &api.QueryEventsByIDRequest{
|
eventsReq := &api.QueryEventsByIDRequest{
|
||||||
RoomID: ore.Event.RoomID().String(),
|
RoomID: ore.Event.RoomID(),
|
||||||
EventIDs: missingEventIDs,
|
EventIDs: missingEventIDs,
|
||||||
}
|
}
|
||||||
eventsRes := &api.QueryEventsByIDResponse{}
|
eventsRes := &api.QueryEventsByIDResponse{}
|
||||||
|
@ -205,7 +205,7 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent, rew
|
||||||
// talking to the roomserver
|
// talking to the roomserver
|
||||||
oldJoinedHosts, err := s.db.UpdateRoom(
|
oldJoinedHosts, err := s.db.UpdateRoom(
|
||||||
s.ctx,
|
s.ctx,
|
||||||
ore.Event.RoomID().String(),
|
ore.Event.RoomID(),
|
||||||
addsJoinedHosts,
|
addsJoinedHosts,
|
||||||
ore.RemovesStateEventIDs,
|
ore.RemovesStateEventIDs,
|
||||||
rewritesState, // if we're re-writing state, nuke all joined hosts before adding
|
rewritesState, // if we're re-writing state, nuke all joined hosts before adding
|
||||||
|
@ -218,7 +218,7 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent, rew
|
||||||
if s.cfg.Matrix.Presence.EnableOutbound && len(addsJoinedHosts) > 0 && ore.Event.Type() == spec.MRoomMember && ore.Event.StateKey() != nil {
|
if s.cfg.Matrix.Presence.EnableOutbound && len(addsJoinedHosts) > 0 && ore.Event.Type() == spec.MRoomMember && ore.Event.StateKey() != nil {
|
||||||
membership, _ := ore.Event.Membership()
|
membership, _ := ore.Event.Membership()
|
||||||
if membership == spec.Join {
|
if membership == spec.Join {
|
||||||
s.sendPresence(ore.Event.RoomID().String(), addsJoinedHosts)
|
s.sendPresence(ore.Event.RoomID(), addsJoinedHosts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +376,7 @@ func (s *OutputRoomEventConsumer) joinedHostsAtEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle peeking hosts
|
// handle peeking hosts
|
||||||
inboundPeeks, err := s.db.GetInboundPeeks(s.ctx, ore.Event.PDU.RoomID().String())
|
inboundPeeks, err := s.db.GetInboundPeeks(s.ctx, ore.Event.PDU.RoomID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -409,8 +409,12 @@ func JoinedHostsFromEvents(ctx context.Context, evs []gomatrixserverlib.PDU, rsA
|
||||||
if membership != spec.Join {
|
if membership != spec.Join {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
validRoomID, err := spec.NewRoomID(ev.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var domain spec.ServerName
|
var domain spec.ServerName
|
||||||
userID, err := rsAPI.QueryUserIDForSender(ctx, ev.RoomID(), spec.SenderID(*ev.StateKey()))
|
userID, err := rsAPI.QueryUserIDForSender(ctx, *validRoomID, spec.SenderID(*ev.StateKey()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.As(err, new(base64.CorruptInputError)) {
|
if errors.As(err, new(base64.CorruptInputError)) {
|
||||||
// Fallback to using the "old" way of getting the user domain, avoids
|
// Fallback to using the "old" way of getting the user domain, avoids
|
||||||
|
@ -506,7 +510,7 @@ func (s *OutputRoomEventConsumer) lookupStateEvents(
|
||||||
// At this point the missing events are neither the event itself nor are
|
// At this point the missing events are neither the event itself nor are
|
||||||
// they present in our local database. Our only option is to fetch them
|
// they present in our local database. Our only option is to fetch them
|
||||||
// from the roomserver using the query API.
|
// from the roomserver using the query API.
|
||||||
eventReq := api.QueryEventsByIDRequest{EventIDs: missing, RoomID: event.RoomID().String()}
|
eventReq := api.QueryEventsByIDRequest{EventIDs: missing, RoomID: event.RoomID()}
|
||||||
var eventResp api.QueryEventsByIDResponse
|
var eventResp api.QueryEventsByIDResponse
|
||||||
if err := s.rsAPI.QueryEventsByID(s.ctx, &eventReq, &eventResp); err != nil {
|
if err := s.rsAPI.QueryEventsByID(s.ctx, &eventReq, &eventResp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/federationapi/consumers"
|
"github.com/matrix-org/dendrite/federationapi/consumers"
|
||||||
"github.com/matrix-org/dendrite/federationapi/internal"
|
"github.com/matrix-org/dendrite/federationapi/internal"
|
||||||
|
@ -101,7 +102,7 @@ func NewInternalAPI(
|
||||||
caches *caching.Caches,
|
caches *caching.Caches,
|
||||||
keyRing *gomatrixserverlib.KeyRing,
|
keyRing *gomatrixserverlib.KeyRing,
|
||||||
resetBlacklist bool,
|
resetBlacklist bool,
|
||||||
) *internal.FederationInternalAPI {
|
) api.FederationInternalAPI {
|
||||||
cfg := &dendriteCfg.FederationAPI
|
cfg := &dendriteCfg.FederationAPI
|
||||||
|
|
||||||
federationDB, err := storage.NewDatabase(processContext.Context(), cm, &cfg.Database, caches, dendriteCfg.Global.IsLocalServerName)
|
federationDB, err := storage.NewDatabase(processContext.Context(), cm, &cfg.Database, caches, dendriteCfg.Global.IsLocalServerName)
|
||||||
|
@ -113,7 +114,10 @@ func NewInternalAPI(
|
||||||
_ = federationDB.RemoveAllServersFromBlacklist()
|
_ = federationDB.RemoveAllServersFromBlacklist()
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := statistics.NewStatistics(federationDB, cfg.FederationMaxRetries+1, cfg.P2PFederationRetriesUntilAssumedOffline+1, cfg.EnableRelays)
|
stats := statistics.NewStatistics(
|
||||||
|
federationDB,
|
||||||
|
cfg.FederationMaxRetries+1,
|
||||||
|
cfg.P2PFederationRetriesUntilAssumedOffline+1)
|
||||||
|
|
||||||
js, nats := natsInstance.Prepare(processContext, &cfg.Matrix.JetStream)
|
js, nats := natsInstance.Prepare(processContext, &cfg.Matrix.JetStream)
|
||||||
|
|
||||||
|
@ -122,7 +126,7 @@ func NewInternalAPI(
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
federationDB, processContext,
|
federationDB, processContext,
|
||||||
cfg.Matrix.DisableFederation,
|
cfg.Matrix.DisableFederation,
|
||||||
cfg.Matrix.ServerName, federation, &stats,
|
cfg.Matrix.ServerName, federation, rsAPI, &stats,
|
||||||
signingInfo,
|
signingInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,11 @@ import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/routing"
|
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
@ -20,10 +17,7 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi"
|
"github.com/matrix-org/dendrite/federationapi"
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
|
@ -39,16 +33,15 @@ import (
|
||||||
type fedRoomserverAPI struct {
|
type fedRoomserverAPI struct {
|
||||||
rsapi.FederationRoomserverAPI
|
rsapi.FederationRoomserverAPI
|
||||||
inputRoomEvents func(ctx context.Context, req *rsapi.InputRoomEventsRequest, res *rsapi.InputRoomEventsResponse)
|
inputRoomEvents func(ctx context.Context, req *rsapi.InputRoomEventsRequest, res *rsapi.InputRoomEventsResponse)
|
||||||
queryRoomsForUser func(ctx context.Context, userID spec.UserID, desiredMembership string) ([]spec.RoomID, error)
|
queryRoomsForUser func(ctx context.Context, req *rsapi.QueryRoomsForUserRequest, res *rsapi.QueryRoomsForUserResponse) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fedRoomserverAPI) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
func (f *fedRoomserverAPI) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
return spec.NewUserID(string(senderID), true)
|
return spec.NewUserID(string(senderID), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fedRoomserverAPI) QuerySenderIDForUser(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (*spec.SenderID, error) {
|
func (f *fedRoomserverAPI) QuerySenderIDForUser(ctx context.Context, roomID spec.RoomID, userID spec.UserID) (spec.SenderID, error) {
|
||||||
senderID := spec.SenderID(userID.String())
|
return spec.SenderID(userID.String()), nil
|
||||||
return &senderID, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformJoin will call this function
|
// PerformJoin will call this function
|
||||||
|
@ -60,11 +53,11 @@ func (f *fedRoomserverAPI) InputRoomEvents(ctx context.Context, req *rsapi.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
// keychange consumer calls this
|
// keychange consumer calls this
|
||||||
func (f *fedRoomserverAPI) QueryRoomsForUser(ctx context.Context, userID spec.UserID, desiredMembership string) ([]spec.RoomID, error) {
|
func (f *fedRoomserverAPI) QueryRoomsForUser(ctx context.Context, req *rsapi.QueryRoomsForUserRequest, res *rsapi.QueryRoomsForUserResponse) error {
|
||||||
if f.queryRoomsForUser == nil {
|
if f.queryRoomsForUser == nil {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
return f.queryRoomsForUser(ctx, userID, desiredMembership)
|
return f.queryRoomsForUser(ctx, req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This struct isn't generic, only works for TestFederationAPIJoinThenKeyUpdate
|
// TODO: This struct isn't generic, only works for TestFederationAPIJoinThenKeyUpdate
|
||||||
|
@ -152,7 +145,7 @@ func (f *fedClient) SendJoin(ctx context.Context, origin, s spec.ServerName, eve
|
||||||
f.fedClientMutex.Lock()
|
f.fedClientMutex.Lock()
|
||||||
defer f.fedClientMutex.Unlock()
|
defer f.fedClientMutex.Unlock()
|
||||||
for _, r := range f.allowJoins {
|
for _, r := range f.allowJoins {
|
||||||
if r.ID == event.RoomID().String() {
|
if r.ID == event.RoomID() {
|
||||||
r.InsertEvent(f.t, &types.HeaderedEvent{PDU: event})
|
r.InsertEvent(f.t, &types.HeaderedEvent{PDU: event})
|
||||||
f.t.Logf("Join event: %v", event.EventID())
|
f.t.Logf("Join event: %v", event.EventID())
|
||||||
res.StateEvents = types.NewEventJSONsFromHeaderedEvents(r.CurrentState())
|
res.StateEvents = types.NewEventJSONsFromHeaderedEvents(r.CurrentState())
|
||||||
|
@ -205,22 +198,18 @@ func testFederationAPIJoinThenKeyUpdate(t *testing.T, dbType test.DBType) {
|
||||||
fmt.Printf("creator: %v joining user: %v\n", creator.ID, joiningUser.ID)
|
fmt.Printf("creator: %v joining user: %v\n", creator.ID, joiningUser.ID)
|
||||||
room := test.NewRoom(t, creator)
|
room := test.NewRoom(t, creator)
|
||||||
|
|
||||||
roomID, err := spec.NewRoomID(room.ID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Invalid room ID: %q", roomID)
|
|
||||||
}
|
|
||||||
|
|
||||||
rsapi := &fedRoomserverAPI{
|
rsapi := &fedRoomserverAPI{
|
||||||
inputRoomEvents: func(ctx context.Context, req *rsapi.InputRoomEventsRequest, res *rsapi.InputRoomEventsResponse) {
|
inputRoomEvents: func(ctx context.Context, req *rsapi.InputRoomEventsRequest, res *rsapi.InputRoomEventsResponse) {
|
||||||
if req.Asynchronous {
|
if req.Asynchronous {
|
||||||
t.Errorf("InputRoomEvents from PerformJoin MUST be synchronous")
|
t.Errorf("InputRoomEvents from PerformJoin MUST be synchronous")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
queryRoomsForUser: func(ctx context.Context, userID spec.UserID, desiredMembership string) ([]spec.RoomID, error) {
|
queryRoomsForUser: func(ctx context.Context, req *rsapi.QueryRoomsForUserRequest, res *rsapi.QueryRoomsForUserResponse) error {
|
||||||
if userID.String() == joiningUser.ID && desiredMembership == "join" {
|
if req.UserID == joiningUser.ID && req.WantMembership == "join" {
|
||||||
return []spec.RoomID{*roomID}, nil
|
res.RoomIDs = []string{room.ID}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected queryRoomsForUser: %v, %v", userID, desiredMembership)
|
return fmt.Errorf("unexpected queryRoomsForUser: %+v", *req)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fc := &fedClient{
|
fc := &fedClient{
|
||||||
|
@ -368,126 +357,3 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotaryServer(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
httpBody string
|
|
||||||
pubKeyRequest *gomatrixserverlib.PublicKeyNotaryLookupRequest
|
|
||||||
validateFunc func(t *testing.T, response util.JSONResponse)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty httpBody",
|
|
||||||
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
|
||||||
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
|
||||||
nk, ok := resp.JSON.(spec.MatrixError)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, spec.ErrorBadJSON, nk.ErrCode)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid but empty httpBody",
|
|
||||||
httpBody: "{}",
|
|
||||||
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
|
||||||
want := util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: routing.NotaryKeysResponse{ServerKeys: []json.RawMessage{}},
|
|
||||||
}
|
|
||||||
assert.Equal(t, want, resp)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request all keys using an empty criteria",
|
|
||||||
httpBody: `{"server_keys":{"servera":{}}}`,
|
|
||||||
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, "servera", gjson.GetBytes(nk.ServerKeys[0], "server_name").Str)
|
|
||||||
assert.True(t, gjson.GetBytes(nk.ServerKeys[0], "verify_keys.ed25519:someID").Exists())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request all keys using null as the criteria",
|
|
||||||
httpBody: `{"server_keys":{"servera":null}}`,
|
|
||||||
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, "servera", gjson.GetBytes(nk.ServerKeys[0], "server_name").Str)
|
|
||||||
assert.True(t, gjson.GetBytes(nk.ServerKeys[0], "verify_keys.ed25519:someID").Exists())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request specific key",
|
|
||||||
httpBody: `{"server_keys":{"servera":{"ed25519:someID":{}}}}`,
|
|
||||||
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, "servera", gjson.GetBytes(nk.ServerKeys[0], "server_name").Str)
|
|
||||||
assert.True(t, gjson.GetBytes(nk.ServerKeys[0], "verify_keys.ed25519:someID").Exists())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request multiple servers",
|
|
||||||
httpBody: `{"server_keys":{"servera":{"ed25519:someID":{}},"serverb":{"ed25519:someID":{}}}}`,
|
|
||||||
validateFunc: func(t *testing.T, resp util.JSONResponse) {
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
nk, ok := resp.JSON.(routing.NotaryKeysResponse)
|
|
||||||
assert.True(t, ok)
|
|
||||||
wantServers := map[string]struct{}{
|
|
||||||
"servera": {},
|
|
||||||
"serverb": {},
|
|
||||||
}
|
|
||||||
for _, js := range nk.ServerKeys {
|
|
||||||
serverName := gjson.GetBytes(js, "server_name").Str
|
|
||||||
_, ok = wantServers[serverName]
|
|
||||||
assert.True(t, ok, "unexpected servername: %s", serverName)
|
|
||||||
delete(wantServers, serverName)
|
|
||||||
assert.True(t, gjson.GetBytes(js, "verify_keys.ed25519:someID").Exists())
|
|
||||||
}
|
|
||||||
if len(wantServers) > 0 {
|
|
||||||
t.Fatalf("expected response to also contain: %#v", wantServers)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
||||||
cfg, processCtx, close := testrig.CreateConfig(t, dbType)
|
|
||||||
defer close()
|
|
||||||
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
||||||
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
|
|
||||||
natsInstance := jetstream.NATSInstance{}
|
|
||||||
fc := &fedClient{
|
|
||||||
keys: map[spec.ServerName]struct {
|
|
||||||
key ed25519.PrivateKey
|
|
||||||
keyID gomatrixserverlib.KeyID
|
|
||||||
}{
|
|
||||||
"servera": {
|
|
||||||
key: test.PrivateKeyA,
|
|
||||||
keyID: "ed25519:someID",
|
|
||||||
},
|
|
||||||
"serverb": {
|
|
||||||
key: test.PrivateKeyB,
|
|
||||||
keyID: "ed25519:someID",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fedAPI := federationapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, fc, nil, caches, nil, true)
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.httpBody))
|
|
||||||
req.Host = string(cfg.Global.ServerName)
|
|
||||||
|
|
||||||
resp := routing.NotaryKeys(req, &cfg.FederationAPI, fedAPI, tc.pubKeyRequest)
|
|
||||||
// assert that we received the expected response
|
|
||||||
tc.validateFunc(t, resp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -112,7 +113,7 @@ func NewFederationInternalAPI(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *FederationInternalAPI) IsBlacklistedOrBackingOff(s spec.ServerName) (*statistics.ServerStatistics, error) {
|
func (a *FederationInternalAPI) isBlacklistedOrBackingOff(s spec.ServerName) (*statistics.ServerStatistics, error) {
|
||||||
stats := a.statistics.ForServer(s)
|
stats := a.statistics.ForServer(s)
|
||||||
if stats.Blacklisted() {
|
if stats.Blacklisted() {
|
||||||
return stats, &api.FederationClientError{
|
return stats, &api.FederationClientError{
|
||||||
|
@ -151,7 +152,7 @@ func failBlacklistableError(err error, stats *statistics.ServerStatistics) (unti
|
||||||
func (a *FederationInternalAPI) doRequestIfNotBackingOffOrBlacklisted(
|
func (a *FederationInternalAPI) doRequestIfNotBackingOffOrBlacklisted(
|
||||||
s spec.ServerName, request func() (interface{}, error),
|
s spec.ServerName, request func() (interface{}, error),
|
||||||
) (interface{}, error) {
|
) (interface{}, error) {
|
||||||
stats, err := a.IsBlacklistedOrBackingOff(s)
|
stats, err := a.isBlacklistedOrBackingOff(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -185,3 +186,7 @@ func (a *FederationInternalAPI) doRequestIfNotBlacklisted(
|
||||||
}
|
}
|
||||||
return request()
|
return request()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *FederationInternalAPI) CurrentMembership(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (string, error) {
|
||||||
|
return a.rsAPI.CurrentMembership(ctx, roomID, senderID)
|
||||||
|
}
|
||||||
|
|
|
@ -61,11 +61,11 @@ func TestFederationClientQueryKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedapi := FederationInternalAPI{
|
fedapi := FederationInternalAPI{
|
||||||
|
@ -92,11 +92,11 @@ func TestFederationClientQueryKeysBlacklisted(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedapi := FederationInternalAPI{
|
fedapi := FederationInternalAPI{
|
||||||
|
@ -122,11 +122,11 @@ func TestFederationClientQueryKeysFailure(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{shouldFail: true}
|
fedClient := &testFedClient{shouldFail: true}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedapi := FederationInternalAPI{
|
fedapi := FederationInternalAPI{
|
||||||
|
@ -152,11 +152,11 @@ func TestFederationClientClaimKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedapi := FederationInternalAPI{
|
fedapi := FederationInternalAPI{
|
||||||
|
@ -183,11 +183,11 @@ func TestFederationClientClaimKeysBlacklisted(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedapi := FederationInternalAPI{
|
fedapi := FederationInternalAPI{
|
||||||
|
|
|
@ -167,7 +167,7 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
KeyRing: r.keyRing,
|
KeyRing: r.keyRing,
|
||||||
EventProvider: federatedEventProvider(ctx, r.federation, r.keyRing, user.Domain(), serverName, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
EventProvider: federatedEventProvider(ctx, r.federation, r.keyRing, user.Domain(), serverName, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
return r.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
return r.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
}),
|
}, r.rsAPI),
|
||||||
UserIDQuerier: func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
UserIDQuerier: func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
return r.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
return r.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
},
|
},
|
||||||
|
@ -190,6 +190,7 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
}
|
}
|
||||||
return r.rsAPI.StoreUserRoomPublicKey(ctx, senderID, *storeUserID, roomID)
|
return r.rsAPI.StoreUserRoomPublicKey(ctx, senderID, *storeUserID, roomID)
|
||||||
},
|
},
|
||||||
|
MembershipQuerier: r.rsAPI,
|
||||||
}
|
}
|
||||||
response, joinErr := gomatrixserverlib.PerformJoin(ctx, r, joinInput)
|
response, joinErr := gomatrixserverlib.PerformJoin(ctx, r, joinInput)
|
||||||
|
|
||||||
|
@ -387,7 +388,7 @@ func (r *FederationInternalAPI) performOutboundPeekUsingServer(
|
||||||
return r.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
return r.rsAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
}
|
}
|
||||||
authEvents, stateEvents, err := gomatrixserverlib.CheckStateResponse(
|
authEvents, stateEvents, err := gomatrixserverlib.CheckStateResponse(
|
||||||
ctx, &respPeek, respPeek.RoomVersion, r.keyRing, federatedEventProvider(ctx, r.federation, r.keyRing, r.cfg.Matrix.ServerName, serverName, userIDProvider), userIDProvider,
|
ctx, &respPeek, respPeek.RoomVersion, r.keyRing, federatedEventProvider(ctx, r.federation, r.keyRing, r.cfg.Matrix.ServerName, serverName, userIDProvider, r.rsAPI), userIDProvider, r.rsAPI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error checking state returned from peeking: %w", err)
|
return fmt.Errorf("error checking state returned from peeking: %w", err)
|
||||||
|
@ -481,10 +482,8 @@ func (r *FederationInternalAPI) PerformLeave(
|
||||||
senderID, err := r.rsAPI.QuerySenderIDForUser(ctx, *roomID, *userID)
|
senderID, err := r.rsAPI.QuerySenderIDForUser(ctx, *roomID, *userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if senderID == nil {
|
|
||||||
return fmt.Errorf("sender ID not found for %s in %s", *userID, *roomID)
|
|
||||||
}
|
}
|
||||||
senderIDString := string(*senderID)
|
senderIDString := string(senderID)
|
||||||
respMakeLeave.LeaveEvent.Type = spec.MRoomMember
|
respMakeLeave.LeaveEvent.Type = spec.MRoomMember
|
||||||
respMakeLeave.LeaveEvent.SenderID = senderIDString
|
respMakeLeave.LeaveEvent.SenderID = senderIDString
|
||||||
respMakeLeave.LeaveEvent.StateKey = &senderIDString
|
respMakeLeave.LeaveEvent.StateKey = &senderIDString
|
||||||
|
@ -548,7 +547,11 @@ func (r *FederationInternalAPI) SendInvite(
|
||||||
event gomatrixserverlib.PDU,
|
event gomatrixserverlib.PDU,
|
||||||
strippedState []gomatrixserverlib.InviteStrippedState,
|
strippedState []gomatrixserverlib.InviteStrippedState,
|
||||||
) (gomatrixserverlib.PDU, error) {
|
) (gomatrixserverlib.PDU, error) {
|
||||||
inviter, err := r.rsAPI.QueryUserIDForSender(ctx, event.RoomID(), event.SenderID())
|
validRoomID, err := spec.NewRoomID(event.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inviter, err := r.rsAPI.QueryUserIDForSender(ctx, *validRoomID, event.SenderID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -571,7 +574,7 @@ func (r *FederationInternalAPI) SendInvite(
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"event_id": event.EventID(),
|
"event_id": event.EventID(),
|
||||||
"user_id": *event.StateKey(),
|
"user_id": *event.StateKey(),
|
||||||
"room_id": event.RoomID().String(),
|
"room_id": event.RoomID(),
|
||||||
"room_version": event.Version(),
|
"room_version": event.Version(),
|
||||||
"destination": destination,
|
"destination": destination,
|
||||||
}).Info("Sending invite")
|
}).Info("Sending invite")
|
||||||
|
@ -726,7 +729,7 @@ func checkEventsContainCreateEvent(events []gomatrixserverlib.PDU) error {
|
||||||
func federatedEventProvider(
|
func federatedEventProvider(
|
||||||
ctx context.Context, federation fclient.FederationClient,
|
ctx context.Context, federation fclient.FederationClient,
|
||||||
keyRing gomatrixserverlib.JSONVerifier, origin, server spec.ServerName,
|
keyRing gomatrixserverlib.JSONVerifier, origin, server spec.ServerName,
|
||||||
userIDForSender spec.UserIDForSender,
|
userIDForSender spec.UserIDForSender, rsAPI gomatrixserverlib.MembershipQuerier,
|
||||||
) gomatrixserverlib.EventProvider {
|
) gomatrixserverlib.EventProvider {
|
||||||
// A list of events that we have retried, if they were not included in
|
// A list of events that we have retried, if they were not included in
|
||||||
// the auth events supplied in the send_join.
|
// the auth events supplied in the send_join.
|
||||||
|
@ -776,7 +779,7 @@ func federatedEventProvider(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the signatures of the event.
|
// Check the signatures of the event.
|
||||||
if err := gomatrixserverlib.VerifyEventSignatures(ctx, ev, keyRing, userIDForSender); err != nil {
|
if err := gomatrixserverlib.VerifyEventSignatures(ctx, ev, keyRing, userIDForSender, rsAPI); err != nil {
|
||||||
return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err)
|
return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,11 +66,11 @@ func TestPerformWakeupServers(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, true)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedAPI := NewFederationInternalAPI(
|
fedAPI := NewFederationInternalAPI(
|
||||||
|
@ -112,11 +112,11 @@ func TestQueryRelayServers(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedAPI := NewFederationInternalAPI(
|
fedAPI := NewFederationInternalAPI(
|
||||||
|
@ -153,11 +153,11 @@ func TestRemoveRelayServers(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedAPI := NewFederationInternalAPI(
|
fedAPI := NewFederationInternalAPI(
|
||||||
|
@ -193,11 +193,11 @@ func TestPerformDirectoryLookup(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedAPI := NewFederationInternalAPI(
|
fedAPI := NewFederationInternalAPI(
|
||||||
|
@ -232,11 +232,11 @@ func TestPerformDirectoryLookupRelaying(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fedClient := &testFedClient{}
|
fedClient := &testFedClient{}
|
||||||
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, true)
|
stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline)
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
testDB, process.NewProcessContext(),
|
testDB, process.NewProcessContext(),
|
||||||
false,
|
false,
|
||||||
cfg.Matrix.ServerName, fedClient, &stats,
|
cfg.Matrix.ServerName, fedClient, nil, &stats,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
fedAPI := NewFederationInternalAPI(
|
fedAPI := NewFederationInternalAPI(
|
||||||
|
|
|
@ -43,15 +43,6 @@ func (a *FederationInternalAPI) fetchServerKeysFromCache(
|
||||||
ctx context.Context, req *api.QueryServerKeysRequest,
|
ctx context.Context, req *api.QueryServerKeysRequest,
|
||||||
) ([]gomatrixserverlib.ServerKeys, error) {
|
) ([]gomatrixserverlib.ServerKeys, error) {
|
||||||
var results []gomatrixserverlib.ServerKeys
|
var results []gomatrixserverlib.ServerKeys
|
||||||
|
|
||||||
// We got a request for _all_ server keys, return them.
|
|
||||||
if len(req.KeyIDToCriteria) == 0 {
|
|
||||||
serverKeysResponses, _ := a.db.GetNotaryKeys(ctx, req.ServerName, []gomatrixserverlib.KeyID{})
|
|
||||||
if len(serverKeysResponses) == 0 {
|
|
||||||
return nil, fmt.Errorf("failed to find server key response for server %s", req.ServerName)
|
|
||||||
}
|
|
||||||
return serverKeysResponses, nil
|
|
||||||
}
|
|
||||||
for keyID, criteria := range req.KeyIDToCriteria {
|
for keyID, criteria := range req.KeyIDToCriteria {
|
||||||
serverKeysResponses, _ := a.db.GetNotaryKeys(ctx, req.ServerName, []gomatrixserverlib.KeyID{keyID})
|
serverKeysResponses, _ := a.db.GetNotaryKeys(ctx, req.ServerName, []gomatrixserverlib.KeyID{keyID})
|
||||||
if len(serverKeysResponses) == 0 {
|
if len(serverKeysResponses) == 0 {
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
|
@ -27,10 +26,12 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
"github.com/matrix-org/dendrite/federationapi/statistics"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage/shared/receipt"
|
"github.com/matrix-org/dendrite/federationapi/storage/shared/receipt"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
)
|
)
|
||||||
|
@ -52,6 +53,7 @@ type destinationQueue struct {
|
||||||
db storage.Database
|
db storage.Database
|
||||||
process *process.ProcessContext
|
process *process.ProcessContext
|
||||||
signing map[spec.ServerName]*fclient.SigningIdentity
|
signing map[spec.ServerName]*fclient.SigningIdentity
|
||||||
|
rsAPI api.FederationRoomserverAPI
|
||||||
client fclient.FederationClient // federation client
|
client fclient.FederationClient // federation client
|
||||||
origin spec.ServerName // origin of requests
|
origin spec.ServerName // origin of requests
|
||||||
destination spec.ServerName // destination of requests
|
destination spec.ServerName // destination of requests
|
||||||
|
@ -294,10 +296,6 @@ func (oq *destinationQueue) checkNotificationsOnClose() {
|
||||||
|
|
||||||
// backgroundSend is the worker goroutine for sending events.
|
// backgroundSend is the worker goroutine for sending events.
|
||||||
func (oq *destinationQueue) backgroundSend() {
|
func (oq *destinationQueue) backgroundSend() {
|
||||||
// Don't try to send transactions if we are shutting down.
|
|
||||||
if oq.process.Context().Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check if a worker is already running, and if it isn't, then
|
// Check if a worker is already running, and if it isn't, then
|
||||||
// mark it as started.
|
// mark it as started.
|
||||||
if !oq.running.CompareAndSwap(false, true) {
|
if !oq.running.CompareAndSwap(false, true) {
|
||||||
|
|
|
@ -27,10 +27,12 @@ import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
"github.com/matrix-org/dendrite/federationapi/statistics"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage/shared/receipt"
|
"github.com/matrix-org/dendrite/federationapi/storage/shared/receipt"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
)
|
)
|
||||||
|
@ -41,6 +43,7 @@ type OutgoingQueues struct {
|
||||||
db storage.Database
|
db storage.Database
|
||||||
process *process.ProcessContext
|
process *process.ProcessContext
|
||||||
disabled bool
|
disabled bool
|
||||||
|
rsAPI api.FederationRoomserverAPI
|
||||||
origin spec.ServerName
|
origin spec.ServerName
|
||||||
client fclient.FederationClient
|
client fclient.FederationClient
|
||||||
statistics *statistics.Statistics
|
statistics *statistics.Statistics
|
||||||
|
@ -87,6 +90,7 @@ func NewOutgoingQueues(
|
||||||
disabled bool,
|
disabled bool,
|
||||||
origin spec.ServerName,
|
origin spec.ServerName,
|
||||||
client fclient.FederationClient,
|
client fclient.FederationClient,
|
||||||
|
rsAPI api.FederationRoomserverAPI,
|
||||||
statistics *statistics.Statistics,
|
statistics *statistics.Statistics,
|
||||||
signing []*fclient.SigningIdentity,
|
signing []*fclient.SigningIdentity,
|
||||||
) *OutgoingQueues {
|
) *OutgoingQueues {
|
||||||
|
@ -94,6 +98,7 @@ func NewOutgoingQueues(
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
process: process,
|
process: process,
|
||||||
db: db,
|
db: db,
|
||||||
|
rsAPI: rsAPI,
|
||||||
origin: origin,
|
origin: origin,
|
||||||
client: client,
|
client: client,
|
||||||
statistics: statistics,
|
statistics: statistics,
|
||||||
|
@ -157,6 +162,7 @@ func (oqs *OutgoingQueues) getQueue(destination spec.ServerName) *destinationQue
|
||||||
queues: oqs,
|
queues: oqs,
|
||||||
db: oqs.db,
|
db: oqs.db,
|
||||||
process: oqs.process,
|
process: oqs.process,
|
||||||
|
rsAPI: oqs.rsAPI,
|
||||||
origin: oqs.origin,
|
origin: oqs.origin,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
client: oqs.client,
|
client: oqs.client,
|
||||||
|
@ -207,6 +213,18 @@ func (oqs *OutgoingQueues) SendEvent(
|
||||||
delete(destmap, local)
|
delete(destmap, local)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if any of the destinations are prohibited by server ACLs.
|
||||||
|
for destination := range destmap {
|
||||||
|
if api.IsServerBannedFromRoom(
|
||||||
|
oqs.process.Context(),
|
||||||
|
oqs.rsAPI,
|
||||||
|
ev.RoomID(),
|
||||||
|
destination,
|
||||||
|
) {
|
||||||
|
delete(destmap, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no remaining destinations then give up.
|
// If there are no remaining destinations then give up.
|
||||||
if len(destmap) == 0 {
|
if len(destmap) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -285,6 +303,24 @@ func (oqs *OutgoingQueues) SendEDU(
|
||||||
delete(destmap, local)
|
delete(destmap, local)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is absolutely no guarantee that the EDU will have a room_id
|
||||||
|
// field, as it is not required by the spec. However, if it *does*
|
||||||
|
// (e.g. typing notifications) then we should try to make sure we don't
|
||||||
|
// bother sending them to servers that are prohibited by the server
|
||||||
|
// ACLs.
|
||||||
|
if result := gjson.GetBytes(e.Content, "room_id"); result.Exists() {
|
||||||
|
for destination := range destmap {
|
||||||
|
if api.IsServerBannedFromRoom(
|
||||||
|
oqs.process.Context(),
|
||||||
|
oqs.rsAPI,
|
||||||
|
result.Str,
|
||||||
|
destination,
|
||||||
|
) {
|
||||||
|
delete(destmap, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no remaining destinations then give up.
|
// If there are no remaining destinations then give up.
|
||||||
if len(destmap) == 0 {
|
if len(destmap) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -27,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/test/testrig"
|
"github.com/matrix-org/dendrite/test/testrig"
|
||||||
"github.com/matrix-org/gomatrixserverlib/fclient"
|
"github.com/matrix-org/gomatrixserverlib/fclient"
|
||||||
"github.com/matrix-org/gomatrixserverlib/spec"
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
"go.uber.org/atomic"
|
||||||
"gotest.tools/v3/poll"
|
"gotest.tools/v3/poll"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
@ -34,6 +34,7 @@ import (
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/statistics"
|
"github.com/matrix-org/dendrite/federationapi/statistics"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
|
rsapi "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
|
@ -64,6 +65,15 @@ func mustCreateFederationDatabase(t *testing.T, dbType test.DBType, realDatabase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubFederationRoomServerAPI struct {
|
||||||
|
rsapi.FederationRoomserverAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *stubFederationRoomServerAPI) QueryServerBannedFromRoom(ctx context.Context, req *rsapi.QueryServerBannedFromRoomRequest, res *rsapi.QueryServerBannedFromRoomResponse) error {
|
||||||
|
res.Banned = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type stubFederationClient struct {
|
type stubFederationClient struct {
|
||||||
fclient.FederationClient
|
fclient.FederationClient
|
||||||
shouldTxSucceed bool
|
shouldTxSucceed bool
|
||||||
|
@ -94,7 +104,7 @@ func (f *stubFederationClient) P2PSendTransactionToRelay(ctx context.Context, u
|
||||||
|
|
||||||
func mustCreatePDU(t *testing.T) *types.HeaderedEvent {
|
func mustCreatePDU(t *testing.T) *types.HeaderedEvent {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
content := `{"type":"m.room.message", "room_id":"!room:a"}`
|
content := `{"type":"m.room.message"}`
|
||||||
ev, err := gomatrixserverlib.MustGetRoomVersion(gomatrixserverlib.RoomVersionV10).NewEventFromTrustedJSON([]byte(content), false)
|
ev, err := gomatrixserverlib.MustGetRoomVersion(gomatrixserverlib.RoomVersionV10).NewEventFromTrustedJSON([]byte(content), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create event: %v", err)
|
t.Fatalf("failed to create event: %v", err)
|
||||||
|
@ -113,11 +123,12 @@ func testSetup(failuresUntilBlacklist uint32, failuresUntilAssumedOffline uint32
|
||||||
fc := &stubFederationClient{
|
fc := &stubFederationClient{
|
||||||
shouldTxSucceed: shouldTxSucceed,
|
shouldTxSucceed: shouldTxSucceed,
|
||||||
shouldTxRelaySucceed: shouldTxRelaySucceed,
|
shouldTxRelaySucceed: shouldTxRelaySucceed,
|
||||||
txCount: atomic.Uint32{},
|
txCount: *atomic.NewUint32(0),
|
||||||
txRelayCount: atomic.Uint32{},
|
txRelayCount: *atomic.NewUint32(0),
|
||||||
}
|
}
|
||||||
|
rs := &stubFederationRoomServerAPI{}
|
||||||
|
|
||||||
stats := statistics.NewStatistics(db, failuresUntilBlacklist, failuresUntilAssumedOffline, false)
|
stats := statistics.NewStatistics(db, failuresUntilBlacklist, failuresUntilAssumedOffline)
|
||||||
signingInfo := []*fclient.SigningIdentity{
|
signingInfo := []*fclient.SigningIdentity{
|
||||||
{
|
{
|
||||||
KeyID: "ed21019:auto",
|
KeyID: "ed21019:auto",
|
||||||
|
@ -125,7 +136,7 @@ func testSetup(failuresUntilBlacklist uint32, failuresUntilAssumedOffline uint32
|
||||||
ServerName: "localhost",
|
ServerName: "localhost",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
queues := NewOutgoingQueues(db, processContext, false, "localhost", fc, &stats, signingInfo)
|
queues := NewOutgoingQueues(db, processContext, false, "localhost", fc, rs, &stats, signingInfo)
|
||||||
|
|
||||||
return db, fc, queues, processContext, close
|
return db, fc, queues, processContext, close
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,12 +95,6 @@ func Backfill(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce a limit of 100 events, as not to hit the DB to hard.
|
|
||||||
// Synapse has a hard limit of 100 events as well.
|
|
||||||
if req.Limit > 100 {
|
|
||||||
req.Limit = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the Roomserver.
|
// Query the Roomserver.
|
||||||
if err = rsAPI.PerformBackfill(httpReq.Context(), &req, &res); err != nil {
|
if err = rsAPI.PerformBackfill(httpReq.Context(), &req, &res); err != nil {
|
||||||
util.GetLogger(httpReq.Context()).WithError(err).Error("query.PerformBackfill failed")
|
util.GetLogger(httpReq.Context()).WithError(err).Error("query.PerformBackfill failed")
|
||||||
|
@ -115,7 +109,7 @@ func Backfill(
|
||||||
|
|
||||||
var ev *types.HeaderedEvent
|
var ev *types.HeaderedEvent
|
||||||
for _, ev = range res.Events {
|
for _, ev = range res.Events {
|
||||||
if ev.RoomID().String() == roomID {
|
if ev.RoomID() == roomID {
|
||||||
evs = append(evs, ev.PDU)
|
evs = append(evs, ev.PDU)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,10 @@ func GetEventAuth(
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.RoomID().String() != roomID {
|
if event.RoomID() != roomID {
|
||||||
return util.JSONResponse{Code: http.StatusNotFound, JSON: spec.NotFound("event does not belong to this room")}
|
return util.JSONResponse{Code: http.StatusNotFound, JSON: spec.NotFound("event does not belong to this room")}
|
||||||
}
|
}
|
||||||
resErr = allowedToSeeEvent(ctx, request.Origin(), rsAPI, eventID, event.RoomID().String())
|
resErr = allowedToSeeEvent(ctx, request.Origin(), rsAPI, eventID, event.RoomID())
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func GetEvent(
|
||||||
return *err
|
return *err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = allowedToSeeEvent(ctx, request.Origin(), rsAPI, eventID, event.RoomID().String())
|
err = allowedToSeeEvent(ctx, request.Origin(), rsAPI, eventID, event.RoomID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return *err
|
return *err
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ func MakeJoin(
|
||||||
Roomserver: rsAPI,
|
Roomserver: rsAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
senderIDPtr, err := rsAPI.QuerySenderIDForUser(httpReq.Context(), roomID, userID)
|
senderID, err := rsAPI.QuerySenderIDForUser(httpReq.Context(), roomID, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(httpReq.Context()).WithError(err).Error("rsAPI.QuerySenderIDForUser failed")
|
util.GetLogger(httpReq.Context()).WithError(err).Error("rsAPI.QuerySenderIDForUser failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
@ -108,11 +108,8 @@ func MakeJoin(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var senderID spec.SenderID
|
if senderID == "" {
|
||||||
if senderIDPtr == nil {
|
|
||||||
senderID = spec.SenderID(userID.String())
|
senderID = spec.SenderID(userID.String())
|
||||||
} else {
|
|
||||||
senderID = *senderIDPtr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input := gomatrixserverlib.HandleMakeJoinInput{
|
input := gomatrixserverlib.HandleMakeJoinInput{
|
||||||
|
|
|
@ -197,10 +197,6 @@ func localKeys(cfg *config.FederationAPI, serverName spec.ServerName) (*gomatrix
|
||||||
return &keys, err
|
return &keys, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotaryKeysResponse struct {
|
|
||||||
ServerKeys []json.RawMessage `json:"server_keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotaryKeys(
|
func NotaryKeys(
|
||||||
httpReq *http.Request, cfg *config.FederationAPI,
|
httpReq *http.Request, cfg *config.FederationAPI,
|
||||||
fsAPI federationAPI.FederationInternalAPI,
|
fsAPI federationAPI.FederationInternalAPI,
|
||||||
|
@ -221,9 +217,10 @@ func NotaryKeys(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response := NotaryKeysResponse{
|
var response struct {
|
||||||
ServerKeys: []json.RawMessage{},
|
ServerKeys []json.RawMessage `json:"server_keys"`
|
||||||
}
|
}
|
||||||
|
response.ServerKeys = []json.RawMessage{}
|
||||||
|
|
||||||
for serverName, kidToCriteria := range req.ServerKeys {
|
for serverName, kidToCriteria := range req.ServerKeys {
|
||||||
var keyList []gomatrixserverlib.ServerKeys
|
var keyList []gomatrixserverlib.ServerKeys
|
||||||
|
|
|
@ -94,17 +94,11 @@ func MakeLeave(
|
||||||
Code: http.StatusInternalServerError,
|
Code: http.StatusInternalServerError,
|
||||||
JSON: spec.InternalServerError{},
|
JSON: spec.InternalServerError{},
|
||||||
}
|
}
|
||||||
} else if senderID == nil {
|
|
||||||
util.GetLogger(httpReq.Context()).WithField("roomID", roomID).WithField("userID", userID).Error("rsAPI.QuerySenderIDForUser returned nil sender ID")
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
JSON: spec.InternalServerError{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input := gomatrixserverlib.HandleMakeLeaveInput{
|
input := gomatrixserverlib.HandleMakeLeaveInput{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
SenderID: *senderID,
|
SenderID: senderID,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
RoomVersion: roomVersion,
|
RoomVersion: roomVersion,
|
||||||
RequestOrigin: request.Origin(),
|
RequestOrigin: request.Origin(),
|
||||||
|
@ -211,7 +205,7 @@ func SendLeave(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the room ID is correct.
|
// Check that the room ID is correct.
|
||||||
if event.RoomID().String() != roomID {
|
if event.RoomID() != roomID {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: spec.BadJSON("The room ID in the request path must match the room ID in the leave event JSON"),
|
JSON: spec.BadJSON("The room ID in the request path must match the room ID in the leave event JSON"),
|
||||||
|
@ -242,7 +236,14 @@ func SendLeave(
|
||||||
// Check that the sender belongs to the server that is sending us
|
// Check that the sender belongs to the server that is sending us
|
||||||
// the request. By this point we've already asserted that the sender
|
// the request. By this point we've already asserted that the sender
|
||||||
// and the state key are equal so we don't need to check both.
|
// and the state key are equal so we don't need to check both.
|
||||||
sender, err := rsAPI.QueryUserIDForSender(httpReq.Context(), event.RoomID(), event.SenderID())
|
validRoomID, err := spec.NewRoomID(event.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.BadJSON("Room ID is invalid."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sender, err := rsAPI.QueryUserIDForSender(httpReq.Context(), *validRoomID, event.SenderID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
|
|
|
@ -87,7 +87,7 @@ func filterEvents(
|
||||||
) []*types.HeaderedEvent {
|
) []*types.HeaderedEvent {
|
||||||
ref := events[:0]
|
ref := events[:0]
|
||||||
for _, ev := range events {
|
for _, ev := range events {
|
||||||
if ev.RoomID().String() == roomID {
|
if ev.RoomID() == roomID {
|
||||||
ref = append(ref, ev)
|
ref = append(ref, ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||||
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
||||||
|
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
|
||||||
"github.com/matrix-org/dendrite/federationapi/routing"
|
"github.com/matrix-org/dendrite/federationapi/routing"
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
|
@ -66,8 +67,11 @@ func TestHandleQueryProfile(t *testing.T) {
|
||||||
keyRing := serverKeyAPI.KeyRing()
|
keyRing := serverKeyAPI.KeyRing()
|
||||||
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true)
|
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true)
|
||||||
userapi := fakeUserAPI{}
|
userapi := fakeUserAPI{}
|
||||||
|
r, ok := fedapi.(*fedInternal.FederationInternalAPI)
|
||||||
routing.Setup(routers, cfg, nil, fedapi, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
|
if !ok {
|
||||||
|
panic("This is a programming error.")
|
||||||
|
}
|
||||||
|
routing.Setup(routers, cfg, nil, r, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
handler := fedMux.Get(routing.QueryProfileRouteName).GetHandler().ServeHTTP
|
handler := fedMux.Get(routing.QueryProfileRouteName).GetHandler().ServeHTTP
|
||||||
_, sk, _ := ed25519.GenerateKey(nil)
|
_, sk, _ := ed25519.GenerateKey(nil)
|
||||||
|
|
|
@ -146,7 +146,7 @@ func QueryRoomHierarchy(httpReq *http.Request, request *fclient.FederationReques
|
||||||
}
|
}
|
||||||
|
|
||||||
walker := roomserverAPI.NewRoomHierarchyWalker(types.NewServerNameNotDevice(request.Origin()), roomID, suggestedOnly, 1)
|
walker := roomserverAPI.NewRoomHierarchyWalker(types.NewServerNameNotDevice(request.Origin()), roomID, suggestedOnly, 1)
|
||||||
discoveredRooms, inaccessibleRooms, _, err := rsAPI.QueryNextRoomHierarchyPage(httpReq.Context(), walker, -1)
|
discoveredRooms, _, err := rsAPI.QueryNextRoomHierarchyPage(httpReq.Context(), walker, -1)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
|
@ -175,9 +175,8 @@ func QueryRoomHierarchy(httpReq *http.Request, request *fclient.FederationReques
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
JSON: fclient.RoomHierarchyResponse{
|
JSON: fclient.RoomHierarchyResponse{
|
||||||
Room: discoveredRooms[0],
|
Room: discoveredRooms[0],
|
||||||
Children: discoveredRooms[1:],
|
Children: discoveredRooms[1:],
|
||||||
InaccessibleChildren: inaccessibleRooms,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||||
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
||||||
|
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
|
||||||
"github.com/matrix-org/dendrite/federationapi/routing"
|
"github.com/matrix-org/dendrite/federationapi/routing"
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
|
@ -64,8 +65,11 @@ func TestHandleQueryDirectory(t *testing.T) {
|
||||||
keyRing := serverKeyAPI.KeyRing()
|
keyRing := serverKeyAPI.KeyRing()
|
||||||
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true)
|
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, &fedClient, nil, nil, keyRing, true)
|
||||||
userapi := fakeUserAPI{}
|
userapi := fakeUserAPI{}
|
||||||
|
r, ok := fedapi.(*fedInternal.FederationInternalAPI)
|
||||||
routing.Setup(routers, cfg, nil, fedapi, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
|
if !ok {
|
||||||
|
panic("This is a programming error.")
|
||||||
|
}
|
||||||
|
routing.Setup(routers, cfg, nil, r, keyRing, &fedClient, &userapi, &cfg.MSCs, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
handler := fedMux.Get(routing.QueryDirectoryRouteName).GetHandler().ServeHTTP
|
handler := fedMux.Get(routing.QueryDirectoryRouteName).GetHandler().ServeHTTP
|
||||||
_, sk, _ := ed25519.GenerateKey(nil)
|
_, sk, _ := ed25519.GenerateKey(nil)
|
||||||
|
|
|
@ -647,8 +647,6 @@ func MakeFedAPI(
|
||||||
// add the user to Sentry, if enabled
|
// add the user to Sentry, if enabled
|
||||||
hub := sentry.GetHubFromContext(req.Context())
|
hub := sentry.GetHubFromContext(req.Context())
|
||||||
if hub != nil {
|
if hub != nil {
|
||||||
// clone the hub, so we don't send garbage events with e.g. mismatching rooms/event_ids
|
|
||||||
hub = hub.Clone()
|
|
||||||
hub.Scope().SetTag("origin", string(fedReq.Origin()))
|
hub.Scope().SetTag("origin", string(fedReq.Origin()))
|
||||||
hub.Scope().SetTag("uri", fedReq.RequestURI())
|
hub.Scope().SetTag("uri", fedReq.RequestURI())
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
"github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing"
|
||||||
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
fedAPI "github.com/matrix-org/dendrite/federationapi"
|
||||||
|
fedInternal "github.com/matrix-org/dendrite/federationapi/internal"
|
||||||
"github.com/matrix-org/dendrite/federationapi/routing"
|
"github.com/matrix-org/dendrite/federationapi/routing"
|
||||||
"github.com/matrix-org/dendrite/internal/caching"
|
"github.com/matrix-org/dendrite/internal/caching"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
|
@ -61,8 +62,11 @@ func TestHandleSend(t *testing.T) {
|
||||||
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, nil, nil, nil, true)
|
fedapi := fedAPI.NewInternalAPI(processCtx, cfg, cm, &natsInstance, nil, nil, nil, nil, true)
|
||||||
serverKeyAPI := &signing.YggdrasilKeys{}
|
serverKeyAPI := &signing.YggdrasilKeys{}
|
||||||
keyRing := serverKeyAPI.KeyRing()
|
keyRing := serverKeyAPI.KeyRing()
|
||||||
|
r, ok := fedapi.(*fedInternal.FederationInternalAPI)
|
||||||
routing.Setup(routers, cfg, nil, fedapi, keyRing, nil, nil, &cfg.MSCs, nil, caching.DisableMetrics)
|
if !ok {
|
||||||
|
panic("This is a programming error.")
|
||||||
|
}
|
||||||
|
routing.Setup(routers, cfg, nil, r, keyRing, nil, nil, &cfg.MSCs, nil, caching.DisableMetrics)
|
||||||
|
|
||||||
handler := fedMux.Get(routing.SendRouteName).GetHandler().ServeHTTP
|
handler := fedMux.Get(routing.SendRouteName).GetHandler().ServeHTTP
|
||||||
_, sk, _ := ed25519.GenerateKey(nil)
|
_, sk, _ := ed25519.GenerateKey(nil)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue