mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-21 04:53:14 -06:00
Merge remote-tracking branch 'origin/main' into loginsso
This commit is contained in:
commit
f7a977c91c
23
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
23
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
|
|
@ -7,24 +7,28 @@ about: Create a report to help us improve
|
||||||
<!--
|
<!--
|
||||||
All bug reports must provide the following background information
|
All bug reports must provide the following background information
|
||||||
Text between <!-- and --> marks will be invisible in the report.
|
Text between <!-- and --> marks will be invisible in the report.
|
||||||
|
|
||||||
|
IF YOUR ISSUE IS CONSIDERED A SECURITY VULNERABILITY THEN PLEASE STOP
|
||||||
|
AND DO NOT POST IT AS A GITHUB ISSUE! Please report the issue responsibly by
|
||||||
|
disclosing in private by email to security@matrix.org instead. For more details, please
|
||||||
|
see: https://www.matrix.org/security-disclosure-policy/
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Background information
|
### Background information
|
||||||
<!-- Please include versions of all software when known e.g database versions, docker versions, client versions -->
|
<!-- Please include versions of all software when known e.g database versions, docker versions, client versions -->
|
||||||
- **Dendrite version or git SHA**:
|
- **Dendrite version or git SHA**:
|
||||||
- **Monolith or Polylith?**:
|
- **Monolith or Polylith?**:
|
||||||
- **SQLite3 or Postgres?**:
|
- **SQLite3 or Postgres?**:
|
||||||
- **Running in Docker?**:
|
- **Running in Docker?**:
|
||||||
- **`go version`**:
|
- **`go version`**:
|
||||||
- **Client used (if applicable)**:
|
- **Client used (if applicable)**:
|
||||||
|
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
|
|
||||||
- **What** is the problem:
|
- **What** is the problem:
|
||||||
- **Who** is affected:
|
- **Who** is affected:
|
||||||
- **How** is this bug manifesting:
|
- **How** is this bug manifesting:
|
||||||
- **When** did this first appear:
|
- **When** did this first appear:
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Examples of good descriptions:
|
Examples of good descriptions:
|
||||||
|
|
@ -38,7 +42,6 @@ Examples of good descriptions:
|
||||||
- How: "Lots of logs about device change updates"
|
- How: "Lots of logs about device change updates"
|
||||||
- When: "After my server joined Matrix HQ"
|
- When: "After my server joined Matrix HQ"
|
||||||
|
|
||||||
|
|
||||||
Examples of bad descriptions:
|
Examples of bad descriptions:
|
||||||
- What: "Can't send messages" - This is bad because it isn't specfic enough. Which endpoint isn't working and what is the response code? Does the message send but encryption fail?
|
- What: "Can't send messages" - This is bad because it isn't specfic enough. Which endpoint isn't working and what is the response code? Does the message send but encryption fail?
|
||||||
- Who: "Me" - Who are you? Running the server or a user on a Dendrite server?
|
- Who: "Me" - Who are you? Running the server or a user on a Dendrite server?
|
||||||
|
|
|
||||||
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,8 +1,8 @@
|
||||||
### Pull Request Checklist
|
### Pull Request Checklist
|
||||||
|
|
||||||
<!-- Please read docs/CONTRIBUTING.md before submitting your pull request -->
|
<!-- Please read https://matrix-org.github.io/dendrite/development/contributing before submitting your pull request -->
|
||||||
|
|
||||||
* [ ] I have added tests for PR _or_ I have justified why this PR doesn't need tests.
|
* [ ] I have added Go unit tests or [Complement integration tests](https://github.com/matrix-org/complement) for this PR _or_ I have justified why this PR doesn't need tests
|
||||||
* [ ] Pull request includes a [sign off](https://github.com/matrix-org/dendrite/blob/main/docs/CONTRIBUTING.md#sign-off)
|
* [ ] Pull request includes a [sign off below using a legally identifiable name](https://matrix-org.github.io/dendrite/development/contributing#sign-off) _or_ I have already signed off privately
|
||||||
|
|
||||||
Signed-off-by: `Your Name <your@email.example.org>`
|
Signed-off-by: `Your Name <your@email.example.org>`
|
||||||
|
|
|
||||||
40
.github/workflows/dendrite.yml
vendored
40
.github/workflows/dendrite.yml
vendored
|
|
@ -109,6 +109,11 @@ jobs:
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
- name: Set up gotestfmt
|
||||||
|
uses: gotesttools/gotestfmt-action@v2
|
||||||
|
with:
|
||||||
|
# Optional: pass GITHUB_TOKEN to avoid rate limiting.
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -117,7 +122,7 @@ jobs:
|
||||||
key: ${{ runner.os }}-go${{ matrix.go }}-test-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go${{ matrix.go }}-test-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go${{ matrix.go }}-test-
|
${{ runner.os }}-go${{ matrix.go }}-test-
|
||||||
- run: go test ./...
|
- run: go test -json -v ./... 2>&1 | gotestfmt
|
||||||
env:
|
env:
|
||||||
POSTGRES_HOST: localhost
|
POSTGRES_HOST: localhost
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
|
@ -264,11 +269,18 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- label: SQLite
|
- label: SQLite native
|
||||||
|
|
||||||
- label: SQLite, full HTTP APIs
|
- label: SQLite Cgo
|
||||||
|
cgo: 1
|
||||||
|
|
||||||
|
- label: SQLite native, full HTTP APIs
|
||||||
api: full-http
|
api: full-http
|
||||||
|
|
||||||
|
- label: SQLite Cgo, full HTTP APIs
|
||||||
|
api: full-http
|
||||||
|
cgo: 1
|
||||||
|
|
||||||
- label: PostgreSQL
|
- label: PostgreSQL
|
||||||
postgres: postgres
|
postgres: postgres
|
||||||
|
|
||||||
|
|
@ -283,8 +295,9 @@ jobs:
|
||||||
POSTGRES: ${{ matrix.postgres && 1}}
|
POSTGRES: ${{ matrix.postgres && 1}}
|
||||||
API: ${{ matrix.api && 1 }}
|
API: ${{ matrix.api && 1 }}
|
||||||
SYTEST_BRANCH: ${{ github.head_ref }}
|
SYTEST_BRANCH: ${{ github.head_ref }}
|
||||||
|
CGO_ENABLED: ${{ matrix.cgo && 1 }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Run Sytest
|
- name: Run Sytest
|
||||||
run: /bootstrap.sh dendrite
|
run: /bootstrap.sh dendrite
|
||||||
working-directory: /src
|
working-directory: /src
|
||||||
|
|
@ -318,11 +331,18 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- label: SQLite
|
- label: SQLite native
|
||||||
|
|
||||||
- label: SQLite, full HTTP APIs
|
- label: SQLite Cgo
|
||||||
|
cgo: 1
|
||||||
|
|
||||||
|
- label: SQLite native, full HTTP APIs
|
||||||
api: full-http
|
api: full-http
|
||||||
|
|
||||||
|
- label: SQLite Cgo, full HTTP APIs
|
||||||
|
api: full-http
|
||||||
|
cgo: 1
|
||||||
|
|
||||||
- label: PostgreSQL
|
- label: PostgreSQL
|
||||||
postgres: Postgres
|
postgres: Postgres
|
||||||
|
|
||||||
|
|
@ -342,10 +362,10 @@ 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 get -v github.com/haveyoudebuggedit/gotestfmt/v2/cmd/gotestfmt@latest
|
go get -v github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest
|
||||||
|
|
||||||
- name: Run actions/checkout@v2 for dendrite
|
- name: Run actions/checkout@v3 for dendrite
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: dendrite
|
path: dendrite
|
||||||
|
|
||||||
|
|
@ -388,6 +408,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
COMPLEMENT_BASE_IMAGE: complement-dendrite:latest
|
COMPLEMENT_BASE_IMAGE: complement-dendrite:latest
|
||||||
API: ${{ matrix.api && 1 }}
|
API: ${{ matrix.api && 1 }}
|
||||||
|
CGO_ENABLED: ${{ matrix.cgo && 1 }}
|
||||||
working-directory: complement
|
working-directory: complement
|
||||||
|
|
||||||
integration-tests-done:
|
integration-tests-done:
|
||||||
|
|
@ -413,6 +434,7 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
|
security-events: write # To upload Trivy sarif files
|
||||||
if: github.repository == 'matrix-org/dendrite' && github.ref_name == 'main'
|
if: github.repository == 'matrix-org/dendrite' && github.ref_name == 'main'
|
||||||
needs: [integration-tests-done]
|
needs: [integration-tests-done]
|
||||||
uses: matrix-org/dendrite/.github/workflows/docker.yml@main
|
uses: matrix-org/dendrite/.github/workflows/docker.yml@main
|
||||||
|
|
|
||||||
182
.github/workflows/docker.yml
vendored
182
.github/workflows/docker.yml
vendored
|
|
@ -24,23 +24,29 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
security-events: write # To upload Trivy sarif files
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Get release tag
|
- 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: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: |
|
||||||
|
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@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
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@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|
@ -49,27 +55,41 @@ jobs:
|
||||||
- name: Build main monolith image
|
- name: Build main monolith image
|
||||||
if: github.ref_name == 'main'
|
if: github.ref_name == 'main'
|
||||||
id: docker_build_monolith
|
id: docker_build_monolith
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
file: ./build/docker/Dockerfile.monolith
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
|
target: monolith
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
|
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
|
||||||
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
|
||||||
|
format: "sarif"
|
||||||
|
output: "trivy-results.sarif"
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: "trivy-results.sarif"
|
||||||
|
|
||||||
- name: Build release monolith image
|
- name: Build release monolith image
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
id: docker_build_monolith_release
|
id: docker_build_monolith_release
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
file: ./build/docker/Dockerfile.monolith
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
|
target: monolith
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
|
@ -84,23 +104,29 @@ jobs:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
security-events: write # To upload Trivy sarif files
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Get release tag
|
- 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: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: |
|
||||||
|
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@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
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@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|
@ -109,27 +135,41 @@ jobs:
|
||||||
- name: Build main polylith image
|
- name: Build main polylith image
|
||||||
if: github.ref_name == 'main'
|
if: github.ref_name == 'main'
|
||||||
id: docker_build_polylith
|
id: docker_build_polylith
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
file: ./build/docker/Dockerfile.polylith
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
|
target: polylith
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
|
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
|
||||||
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
|
||||||
|
format: "sarif"
|
||||||
|
output: "trivy-results.sarif"
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: "trivy-results.sarif"
|
||||||
|
|
||||||
- name: Build release polylith image
|
- name: Build release polylith image
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
id: docker_build_polylith_release
|
id: docker_build_polylith_release
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
context: .
|
context: .
|
||||||
file: ./build/docker/Dockerfile.polylith
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
|
target: polylith
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
|
@ -146,34 +186,40 @@ jobs:
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Get release tag
|
- 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: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: |
|
||||||
|
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@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
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@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build main pinecone demo image
|
- name: Build main Pinecone demo image
|
||||||
if: github.ref_name == 'main'
|
if: github.ref_name == 'main'
|
||||||
id: docker_build_demo_pinecone
|
id: docker_build_demo_pinecone
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
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
|
||||||
|
|
@ -181,19 +227,87 @@ jobs:
|
||||||
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-pinecone:${{ github.ref_name }}
|
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-pinecone:${{ github.ref_name }}
|
||||||
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-pinecone:${{ github.ref_name }}
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-pinecone:${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Build release pinecone demo image
|
- name: Build release Pinecone demo image
|
||||||
if: github.event_name == 'release' # Only for GitHub releases
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
id: docker_build_demo_pinecone_release
|
id: docker_build_demo_pinecone_release
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
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
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-pinecone:latest
|
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-yggdrasil:latest
|
||||||
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-pinecone:${{ env.RELEASE_VERSION }}
|
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-yggdrasil:${{ env.RELEASE_VERSION }}
|
||||||
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-pinecone:latest
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-yggdrasil:latest
|
||||||
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-pinecone:${{ env.RELEASE_VERSION }}
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-yggdrasil:${{ env.RELEASE_VERSION }}
|
||||||
|
|
||||||
|
demo-yggdrasil:
|
||||||
|
name: Yggdrasil demo image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Get release tag & build flags
|
||||||
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
|
run: |
|
||||||
|
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
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ env.DOCKER_HUB_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
- name: Login to GitHub Containers
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build main Yggdrasil demo image
|
||||||
|
if: github.ref_name == 'main'
|
||||||
|
id: docker_build_demo_yggdrasil
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
context: .
|
||||||
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
|
file: ./build/docker/Dockerfile.demo-yggdrasil
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-yggdrasil:${{ github.ref_name }}
|
||||||
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-yggdrasil:${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Build release Yggdrasil demo image
|
||||||
|
if: github.event_name == 'release' # Only for GitHub releases
|
||||||
|
id: docker_build_demo_yggdrasil_release
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
context: .
|
||||||
|
build-args: FLAGS=-X github.com/matrix-org/dendrite/internal.branch=${{ env.BRANCH }} -X github.com/matrix-org/dendrite/internal.build=${{ env.BUILD }}
|
||||||
|
file: ./build/docker/Dockerfile.demo-yggdrasil
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-yggdrasil:latest
|
||||||
|
${{ env.DOCKER_NAMESPACE }}/dendrite-demo-yggdrasil:${{ env.RELEASE_VERSION }}
|
||||||
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-yggdrasil:latest
|
||||||
|
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-yggdrasil:${{ env.RELEASE_VERSION }}
|
||||||
|
|
|
||||||
128
.github/workflows/schedules.yaml
vendored
Normal file
128
.github/workflows/schedules.yaml
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
name: Scheduled
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *' # every day at midnight
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# run go test with different go versions
|
||||||
|
test:
|
||||||
|
timeout-minutes: 20
|
||||||
|
name: Unit tests (Go ${{ matrix.go }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Service containers to run with `container-job`
|
||||||
|
services:
|
||||||
|
# Label used to access the service container
|
||||||
|
postgres:
|
||||||
|
# Docker Hub image
|
||||||
|
image: postgres:13-alpine
|
||||||
|
# Provide the password for postgres
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: dendrite
|
||||||
|
ports:
|
||||||
|
# Maps tcp port 5432 on service container to the host
|
||||||
|
- 5432:5432
|
||||||
|
# Set health checks to wait until postgres has started
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
go: ["1.18", "1.19"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Setup go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go${{ matrix.go }}-test-race-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go${{ matrix.go }}-test-race-
|
||||||
|
- run: go test -race ./...
|
||||||
|
env:
|
||||||
|
POSTGRES_HOST: localhost
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: dendrite
|
||||||
|
|
||||||
|
# Dummy step to gate other tests on without repeating the whole list
|
||||||
|
initial-tests-done:
|
||||||
|
name: Initial tests passed
|
||||||
|
needs: [test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !cancelled() }} # Run this even if prior jobs were skipped
|
||||||
|
steps:
|
||||||
|
- name: Check initial tests passed
|
||||||
|
uses: re-actors/alls-green@release/v1
|
||||||
|
with:
|
||||||
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
|
||||||
|
# run Sytest in different variations
|
||||||
|
sytest:
|
||||||
|
timeout-minutes: 60
|
||||||
|
needs: initial-tests-done
|
||||||
|
name: "Sytest (${{ matrix.label }})"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- label: SQLite
|
||||||
|
|
||||||
|
- label: SQLite, full HTTP APIs
|
||||||
|
api: full-http
|
||||||
|
|
||||||
|
- label: PostgreSQL
|
||||||
|
postgres: postgres
|
||||||
|
|
||||||
|
- label: PostgreSQL, full HTTP APIs
|
||||||
|
postgres: postgres
|
||||||
|
api: full-http
|
||||||
|
container:
|
||||||
|
image: matrixdotorg/sytest-dendrite:latest
|
||||||
|
volumes:
|
||||||
|
- ${{ github.workspace }}:/src
|
||||||
|
env:
|
||||||
|
POSTGRES: ${{ matrix.postgres && 1}}
|
||||||
|
API: ${{ matrix.api && 1 }}
|
||||||
|
SYTEST_BRANCH: ${{ github.head_ref }}
|
||||||
|
RACE_DETECTION: 1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Run Sytest
|
||||||
|
run: /bootstrap.sh dendrite
|
||||||
|
working-directory: /src
|
||||||
|
- name: Summarise results.tap
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
|
||||||
|
- name: Sytest List Maintenance
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: /src/show-expected-fail-tests.sh /logs/results.tap /src/sytest-whitelist /src/sytest-blacklist
|
||||||
|
continue-on-error: true # not fatal
|
||||||
|
- name: Are We Synapse Yet?
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: /src/are-we-synapse-yet.py /logs/results.tap -v
|
||||||
|
continue-on-error: true # not fatal
|
||||||
|
- name: Upload Sytest logs
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: ${{ always() }}
|
||||||
|
with:
|
||||||
|
name: Sytest Logs - ${{ job.status }} - (Dendrite, ${{ join(matrix.*, ', ') }})
|
||||||
|
path: |
|
||||||
|
/logs/results.tap
|
||||||
|
/logs/**/*.log*
|
||||||
141
CHANGES.md
141
CHANGES.md
|
|
@ -1,5 +1,146 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Dendrite 0.10.7 (2022-11-04)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Dendrite will now use a native SQLite port when building with `CGO_ENABLED=0`
|
||||||
|
* A number of `thirdparty` endpoints have been added, improving support for appservices
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* The `"state"` section of the `/sync` response is no longer limited, so state events should not be dropped unexpectedly
|
||||||
|
* The deduplication of the `"timeline"` and `"state"` sections in `/sync` is now performed after applying history visibility, so state events should not be dropped unexpectedly
|
||||||
|
* The `prev_batch` token returned by `/sync` is now calculated after applying history visibility, so that the pagination boundaries are correct
|
||||||
|
* The room summary membership counts in `/sync` should now be calculated properly in more cases
|
||||||
|
* A false membership leave event should no longer be sent down `/sync` as a result of retiring an accepted invite (contributed by [tak-hntlabs](https://github.com/tak-hntlabs))
|
||||||
|
* Presence updates are now only sent to other servers for which the user shares rooms
|
||||||
|
* A bug which could cause a panic when converting events into the `ClientEvent` format has been fixed
|
||||||
|
|
||||||
|
## Dendrite 0.10.6 (2022-11-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* History visibility checks have been optimised, which should speed up response times on a variety of endpoints (including `/sync`, `/messages`, `/context` and others) and reduce database load
|
||||||
|
* The built-in NATS Server has been updated to version 2.9.4
|
||||||
|
* Some other minor dependencies have been updated
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* A panic has been fixed in the sync API PDU stream which could cause requests to fail
|
||||||
|
* The `/members` response now contains the `room_id` field, which may fix some E2EE problems with clients using the JS SDK (contributed by [ashkitten](https://github.com/ashkitten))
|
||||||
|
* The auth difference calculation in state resolution v2 has been tweaked for clarity (and moved into gomatrixserverlib with the rest of the state resolution code)
|
||||||
|
|
||||||
|
## Dendrite 0.10.5 (2022-10-31)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* It is now possible to use hCaptcha instead of reCAPTCHA for protecting registration
|
||||||
|
* A new `auto_join_rooms` configuration option has been added for automatically joining new users to a set of rooms
|
||||||
|
* A new `/_dendrite/admin/downloadState/{serverName}/{roomID}` endpoint has been added, which allows a server administrator to attempt to repair a room with broken room state by downloading a state snapshot from another federated server in the room
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Querying cross-signing keys for users should now be considerably faster
|
||||||
|
* A bug in state resolution where some events were not correctly selected for third-party invites has been fixed
|
||||||
|
* A bug in state resolution which could result in `not in room` event rejections has been fixed
|
||||||
|
* When accepting a DM invite, it should now be possible to see messages that were sent before the invite was accepted
|
||||||
|
* Claiming remote E2EE one-time keys has been refactored and should be more reliable now
|
||||||
|
* Various fixes have been made to the `/members` endpoint, which may help with E2EE reliability and clients rendering memberships
|
||||||
|
* A race condition in the federation API destination queues has been fixed when associating queued events with remote server destinations
|
||||||
|
* A bug in the sync API where too many events were selected resulting in high CPU usage has been fixed
|
||||||
|
* Configuring the avatar URL for the Server Notices user should work correctly now
|
||||||
|
|
||||||
|
## Dendrite 0.10.4 (2022-10-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Various tables belonging to the user API will be renamed so that they are namespaced with the `userapi_` prefix
|
||||||
|
* Note that, after upgrading to this version, you should not revert to an older version of Dendrite as the database changes **will not** be reverted automatically
|
||||||
|
* The backoff and retry behaviour in the federation API has been refactored and improved
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Private read receipt support is now advertised in the client `/versions` endpoint
|
||||||
|
* Private read receipts will now clear notification counts properly
|
||||||
|
* A bug where a false `leave` membership transition was inserted into the timeline after accepting an invite has been fixed
|
||||||
|
* Some panics caused by concurrent map writes in the key server have been fixed
|
||||||
|
* The sync API now calculates membership transitions from state deltas more accurately
|
||||||
|
* Transaction IDs are now scoped to endpoints, which should fix some bugs where transaction ID reuse could cause nonsensical cached responses from some endpoints
|
||||||
|
* The length of the `type`, `sender`, `state_key` and `room_id` fields in events are now verified by number of bytes rather than codepoints after a spec clarification, reverting a change made in Dendrite 0.9.6
|
||||||
|
|
||||||
|
## Dendrite 0.10.3 (2022-10-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Event relations are now tracked and support for the `/room/{roomID}/relations/...` client API endpoints have been added
|
||||||
|
* Support has been added for private read receipts
|
||||||
|
* The built-in NATS Server has been updated to version 2.9.3
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* The `unread_notifications` are now always populated in joined room responses
|
||||||
|
* The `/get_missing_events` federation API endpoint should now work correctly for rooms with `joined` and `invited` visibility settings, returning redacted events for events that other servers are not allowed to see
|
||||||
|
* The `/event` client API endpoint now applies history visibility correctly
|
||||||
|
* Read markers should now be updated much more reliably
|
||||||
|
* A rare bug in the sync API which could cause some `join` memberships to be incorrectly overwritten by other memberships when working out which rooms to populate has been fixed
|
||||||
|
* The federation API now correctly updates the joined hosts table during a state rewrite
|
||||||
|
|
||||||
|
## Dendrite 0.10.2 (2022-10-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Dendrite will now fail to start if there is an obvious problem with the configured `max_open_conns` when using PostgreSQL database backends, since this can lead to instability and performance issues
|
||||||
|
* More information on this is available [in the documentation](https://matrix-org.github.io/dendrite/installation/start/optimisation#postgresql-connection-limit)
|
||||||
|
* Unnecessary/empty fields will no longer be sent in `/sync` responses
|
||||||
|
* It is now possible to configure `old_private_keys` from previous Matrix installations on the same domain if only public key is known, to make it easier to expire old keys correctly
|
||||||
|
* You can configure either just the `private_key` path, or you can supply both the `public_key` and `key_id`
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* The sync transaction behaviour has been modified further so that errors in one stream should not propagate to other streams unnecessarily
|
||||||
|
* Rooms should now be classified as DM rooms correctly by passing through `is_direct` and unsigned hints
|
||||||
|
* A bug which caused marking device lists as stale to consume lots of CPU has been fixed
|
||||||
|
* Users accepting invites should no longer cause unnecessary federated joins if there are already other local users in the room
|
||||||
|
* The sync API state range queries have been optimised by adding missing indexes
|
||||||
|
* It should now be possible to configure non-English languages for full-text search in `search.language`
|
||||||
|
* The roomserver will no longer attempt to perform federated requests to the local server when trying to fetch missing events
|
||||||
|
* The `/keys/upload` endpoint will now always return the `one_time_keys_counts`, which may help with E2EE reliability
|
||||||
|
* The sync API will now retrieve the latest stream position before processing each stream rather than at the beginning of the request, to hopefully reduce the number of round-trips to `/sync`
|
||||||
|
|
||||||
|
## Dendrite 0.10.1 (2022-09-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* The built-in NATS Server has been updated to version 2.9.2
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* A regression introduced in 0.10.0 in `/sync` as a result of transaction errors has been fixed
|
||||||
|
* Account data updates will no longer send duplicate output events
|
||||||
|
|
||||||
|
## Dendrite 0.10.0 (2022-09-30)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* High performance full-text searching has been added to Dendrite
|
||||||
|
* Search must be enabled in the [`search` section of the `sync_api` config](https://github.com/matrix-org/dendrite/blob/6348486a1365c7469a498101f5035a9b6bd16d22/dendrite-sample.monolith.yaml#L279-L290) before it can be used
|
||||||
|
* The search index is stored on the filesystem rather than the sync API database, so a path to a suitable storage location on disk must be configured
|
||||||
|
* Sync requests should now complete faster and use considerably less database connections as a result of better transactional isolation
|
||||||
|
* The notifications code has been refactored to hopefully make notifications more reliable
|
||||||
|
* A new `/_dendrite/admin/refreshDevices/{userID}` admin endpoint has been added for forcing a refresh of a remote user's device lists without having to modify the database by hand
|
||||||
|
* A new `/_dendrite/admin/fulltext/reindex` admin endpoint has been added for rebuilding the search index (although this may take some time)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* A number of bugs in the device list updater have been fixed, which should help considerably with federated device list synchronisation and E2EE reliability
|
||||||
|
* A state resolution bug has been fixed which should help to prevent unexpected state resets
|
||||||
|
* The deprecated `"origin"` field in events will now be correctly ignored in all cases
|
||||||
|
* Room versions 8 and 9 will now correctly evaluate `"knock"` join rules and membership states
|
||||||
|
* A database index has been added to speed up finding room memberships in the sync API (contributed by [PiotrKozimor](https://github.com/PiotrKozimor))
|
||||||
|
* The client API will now return an `M_UNRECOGNIZED` error for unknown endpoints/methods, which should help with client error handling
|
||||||
|
* A bug has been fixed when updating push rules which could result in `database is locked` on SQLite
|
||||||
|
|
||||||
## Dendrite 0.9.9 (2022-09-22)
|
## Dendrite 0.9.9 (2022-09-22)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
||||||
92
Dockerfile
Normal file
92
Dockerfile
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
#syntax=docker/dockerfile:1.2
|
||||||
|
|
||||||
|
#
|
||||||
|
# base installs required dependencies and runs go mod download to cache dependencies
|
||||||
|
#
|
||||||
|
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.19-alpine AS base
|
||||||
|
RUN apk --update --no-cache add bash build-base curl
|
||||||
|
|
||||||
|
#
|
||||||
|
# build creates all needed binaries
|
||||||
|
#
|
||||||
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
|
WORKDIR /src
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG FLAGS
|
||||||
|
RUN --mount=target=. \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
--mount=type=cache,target=/go/pkg/mod \
|
||||||
|
USERARCH=`go env GOARCH` \
|
||||||
|
GOARCH="$TARGETARCH" \
|
||||||
|
GOOS="linux" \
|
||||||
|
CGO_ENABLED=$([ "$TARGETARCH" = "$USERARCH" ] && echo "1" || echo "0") \
|
||||||
|
go build -v -ldflags="${FLAGS}" -trimpath -o /out/ ./cmd/...
|
||||||
|
|
||||||
|
#
|
||||||
|
# The dendrite base image
|
||||||
|
#
|
||||||
|
FROM alpine:latest AS dendrite-base
|
||||||
|
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
|
||||||
|
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||||
|
LABEL org.opencontainers.image.documentation="https://matrix-org.github.io/dendrite/"
|
||||||
|
LABEL org.opencontainers.image.vendor="The Matrix.org Foundation C.I.C."
|
||||||
|
|
||||||
|
#
|
||||||
|
# Builds the polylith image and only contains the polylith binary
|
||||||
|
#
|
||||||
|
FROM dendrite-base AS polylith
|
||||||
|
LABEL org.opencontainers.image.title="Dendrite (Polylith)"
|
||||||
|
|
||||||
|
COPY --from=build /out/dendrite-polylith-multi /usr/bin/
|
||||||
|
|
||||||
|
VOLUME /etc/dendrite
|
||||||
|
WORKDIR /etc/dendrite
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/dendrite-polylith-multi"]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Builds the monolith image and contains all required binaries
|
||||||
|
#
|
||||||
|
FROM dendrite-base AS monolith
|
||||||
|
LABEL org.opencontainers.image.title="Dendrite (Monolith)"
|
||||||
|
|
||||||
|
COPY --from=build /out/create-account /usr/bin/create-account
|
||||||
|
COPY --from=build /out/generate-config /usr/bin/generate-config
|
||||||
|
COPY --from=build /out/generate-keys /usr/bin/generate-keys
|
||||||
|
COPY --from=build /out/dendrite-monolith-server /usr/bin/dendrite-monolith-server
|
||||||
|
|
||||||
|
VOLUME /etc/dendrite
|
||||||
|
WORKDIR /etc/dendrite
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/dendrite-monolith-server"]
|
||||||
|
EXPOSE 8008 8448
|
||||||
|
|
||||||
|
#
|
||||||
|
# Builds the Complement image, used for integration tests
|
||||||
|
#
|
||||||
|
FROM base AS complement
|
||||||
|
LABEL org.opencontainers.image.title="Dendrite (Complement)"
|
||||||
|
RUN apk add --no-cache sqlite openssl ca-certificates
|
||||||
|
|
||||||
|
COPY --from=build /out/generate-config /usr/bin/generate-config
|
||||||
|
COPY --from=build /out/generate-keys /usr/bin/generate-keys
|
||||||
|
COPY --from=build /out/dendrite-monolith-server /usr/bin/dendrite-monolith-server
|
||||||
|
|
||||||
|
WORKDIR /dendrite
|
||||||
|
RUN /usr/bin/generate-keys --private-key matrix_key.pem && \
|
||||||
|
mkdir /ca && \
|
||||||
|
openssl genrsa -out /ca/ca.key 2048 && \
|
||||||
|
openssl req -new -x509 -key /ca/ca.key -days 3650 -subj "/C=GB/ST=London/O=matrix.org/CN=Complement CA" -out /ca/ca.crt
|
||||||
|
|
||||||
|
ENV SERVER_NAME=localhost
|
||||||
|
ENV API=0
|
||||||
|
EXPOSE 8008 8448
|
||||||
|
|
||||||
|
# At runtime, generate TLS cert based on the CA now mounted at /ca
|
||||||
|
# At runtime, replace the SERVER_NAME with what we are told
|
||||||
|
CMD /usr/bin/generate-keys --server $SERVER_NAME --tls-cert server.crt --tls-key server.key --tls-authority-cert /ca/ca.crt --tls-authority-key /ca/ca.key && \
|
||||||
|
/usr/bin/generate-config -server $SERVER_NAME --ci > dendrite.yaml && \
|
||||||
|
cp /ca/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates && \
|
||||||
|
/usr/bin/dendrite-monolith-server --really-enable-open-registration --tls-cert server.crt --tls-key server.key --config dendrite.yaml -api=${API:-0}
|
||||||
|
|
@ -79,7 +79,7 @@ $ ./bin/dendrite-monolith-server --tls-cert server.crt --tls-key server.key --co
|
||||||
|
|
||||||
# Create an user account (add -admin for an admin user).
|
# Create an user account (add -admin for an admin user).
|
||||||
# Specify the localpart only, e.g. 'alice' for '@alice:domain.com'
|
# Specify the localpart only, e.g. 'alice' for '@alice:domain.com'
|
||||||
$ ./bin/create-account --config dendrite.yaml --url http://localhost:8008 --username alice
|
$ ./bin/create-account --config dendrite.yaml --username alice
|
||||||
```
|
```
|
||||||
|
|
||||||
Then point your favourite Matrix client at `http://localhost:8008` or `https://localhost:8448`.
|
Then point your favourite Matrix client at `http://localhost:8008` or `https://localhost:8448`.
|
||||||
|
|
@ -90,7 +90,7 @@ We use a script called Are We Synapse Yet which checks Sytest compliance rates.
|
||||||
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it
|
||||||
updates with CI. As of August 2022 we're at around 90% CS API coverage and 95% Federation coverage, though check
|
updates with CI. As of August 2022 we're at around 90% CS API coverage and 95% Federation coverage, though check
|
||||||
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse
|
||||||
servers such as matrix.org reasonably well, although there are still some missing features (like Search).
|
servers such as matrix.org reasonably well, although there are still some missing features (like SSO and Third-party ID APIs).
|
||||||
|
|
||||||
We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather
|
We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather
|
||||||
than features that massive deployments may be interested in (OpenID, Guests, Admin APIs, AS API).
|
than features that massive deployments may be interested in (OpenID, Guests, Admin APIs, AS API).
|
||||||
|
|
@ -112,6 +112,7 @@ This means Dendrite supports amongst others:
|
||||||
- Guests
|
- Guests
|
||||||
- User Directory
|
- User Directory
|
||||||
- Presence
|
- Presence
|
||||||
|
- Fulltext search
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppServiceInternalAPI is used to query user and room alias data from application
|
// AppServiceInternalAPI is used to query user and room alias data from application
|
||||||
|
|
@ -41,6 +43,10 @@ type AppServiceInternalAPI interface {
|
||||||
req *UserIDExistsRequest,
|
req *UserIDExistsRequest,
|
||||||
resp *UserIDExistsResponse,
|
resp *UserIDExistsResponse,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
|
Locations(ctx context.Context, req *LocationRequest, resp *LocationResponse) error
|
||||||
|
User(ctx context.Context, request *UserRequest, response *UserResponse) error
|
||||||
|
Protocols(ctx context.Context, req *ProtocolRequest, resp *ProtocolResponse) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomAliasExistsRequest is a request to an application service
|
// RoomAliasExistsRequest is a request to an application service
|
||||||
|
|
@ -77,6 +83,73 @@ type UserIDExistsResponse struct {
|
||||||
UserIDExists bool `json:"exists"`
|
UserIDExists bool `json:"exists"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ASProtocolPath = "/_matrix/app/unstable/thirdparty/protocol/"
|
||||||
|
ASUserPath = "/_matrix/app/unstable/thirdparty/user"
|
||||||
|
ASLocationPath = "/_matrix/app/unstable/thirdparty/location"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtocolRequest struct {
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolResponse struct {
|
||||||
|
Protocols map[string]ASProtocolResponse `json:"protocols"`
|
||||||
|
Exists bool `json:"exists"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ASProtocolResponse struct {
|
||||||
|
FieldTypes map[string]FieldType `json:"field_types,omitempty"` // NOTSPEC: field_types is required by the spec
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
Instances []ProtocolInstance `json:"instances"`
|
||||||
|
LocationFields []string `json:"location_fields"`
|
||||||
|
UserFields []string `json:"user_fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldType struct {
|
||||||
|
Placeholder string `json:"placeholder"`
|
||||||
|
Regexp string `json:"regexp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProtocolInstance struct {
|
||||||
|
Description string `json:"desc"`
|
||||||
|
Icon string `json:"icon,omitempty"`
|
||||||
|
NetworkID string `json:"network_id,omitempty"` // NOTSPEC: network_id is required by the spec
|
||||||
|
Fields json.RawMessage `json:"fields,omitempty"` // NOTSPEC: fields is required by the spec
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRequest struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Params string `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserResponse struct {
|
||||||
|
Users []ASUserResponse `json:"users,omitempty"`
|
||||||
|
Exists bool `json:"exists,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ASUserResponse struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
UserID string `json:"userid"`
|
||||||
|
Fields json.RawMessage `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationRequest struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Params string `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationResponse struct {
|
||||||
|
Locations []ASLocationResponse `json:"locations,omitempty"`
|
||||||
|
Exists bool `json:"exists,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ASLocationResponse struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Fields json.RawMessage `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
// RetrieveUserProfile is a wrapper that queries both the local database and
|
// RetrieveUserProfile is a wrapper that queries both the local database and
|
||||||
// application services for a given user's profile
|
// application services for a given user's profile
|
||||||
// TODO: Remove this, it's called from federationapi and clientapi but is a pure function
|
// TODO: Remove this, it's called from federationapi and clientapi but is a pure function
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
@ -58,8 +59,10 @@ func NewInternalAPI(
|
||||||
// Create appserivce query API with an HTTP client that will be used for all
|
// Create appserivce query API with an HTTP client that will be used for all
|
||||||
// outbound and inbound requests (inbound only for the internal API)
|
// outbound and inbound requests (inbound only for the internal API)
|
||||||
appserviceQueryAPI := &query.AppServiceQueryAPI{
|
appserviceQueryAPI := &query.AppServiceQueryAPI{
|
||||||
HTTPClient: client,
|
HTTPClient: client,
|
||||||
Cfg: &base.Cfg.AppServiceAPI,
|
Cfg: &base.Cfg.AppServiceAPI,
|
||||||
|
ProtocolCache: map[string]appserviceAPI.ASProtocolResponse{},
|
||||||
|
CacheMu: sync.Mutex{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(base.Cfg.Derived.ApplicationServices) == 0 {
|
if len(base.Cfg.Derived.ApplicationServices) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,11 @@ func (s *OutputRoomEventConsumer) onMessage(
|
||||||
log.WithField("appservice", state.ID).Tracef("Appservice worker received %d message(s) from roomserver", len(msgs))
|
log.WithField("appservice", state.ID).Tracef("Appservice worker received %d message(s) from roomserver", len(msgs))
|
||||||
events := make([]*gomatrixserverlib.HeaderedEvent, 0, len(msgs))
|
events := make([]*gomatrixserverlib.HeaderedEvent, 0, len(msgs))
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
|
// Only handle events we care about
|
||||||
|
receivedType := api.OutputType(msg.Header.Get(jetstream.RoomEventType))
|
||||||
|
if receivedType != api.OutputTypeNewRoomEvent && receivedType != api.OutputTypeNewInviteEvent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Parse out the event JSON
|
// Parse out the event JSON
|
||||||
var output api.OutputEvent
|
var output api.OutputEvent
|
||||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ import (
|
||||||
const (
|
const (
|
||||||
AppServiceRoomAliasExistsPath = "/appservice/RoomAliasExists"
|
AppServiceRoomAliasExistsPath = "/appservice/RoomAliasExists"
|
||||||
AppServiceUserIDExistsPath = "/appservice/UserIDExists"
|
AppServiceUserIDExistsPath = "/appservice/UserIDExists"
|
||||||
|
AppServiceLocationsPath = "/appservice/locations"
|
||||||
|
AppServiceUserPath = "/appservice/users"
|
||||||
|
AppServiceProtocolsPath = "/appservice/protocols"
|
||||||
)
|
)
|
||||||
|
|
||||||
// httpAppServiceQueryAPI contains the URL to an appservice query API and a
|
// httpAppServiceQueryAPI contains the URL to an appservice query API and a
|
||||||
|
|
@ -58,3 +61,24 @@ func (h *httpAppServiceQueryAPI) UserIDExists(
|
||||||
h.httpClient, ctx, request, response,
|
h.httpClient, ctx, request, response,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpAppServiceQueryAPI) Locations(ctx context.Context, request *api.LocationRequest, response *api.LocationResponse) error {
|
||||||
|
return httputil.CallInternalRPCAPI(
|
||||||
|
"ASLocation", h.appserviceURL+AppServiceLocationsPath,
|
||||||
|
h.httpClient, ctx, request, response,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpAppServiceQueryAPI) User(ctx context.Context, request *api.UserRequest, response *api.UserResponse) error {
|
||||||
|
return httputil.CallInternalRPCAPI(
|
||||||
|
"ASUser", h.appserviceURL+AppServiceUserPath,
|
||||||
|
h.httpClient, ctx, request, response,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpAppServiceQueryAPI) Protocols(ctx context.Context, request *api.ProtocolRequest, response *api.ProtocolResponse) error {
|
||||||
|
return httputil.CallInternalRPCAPI(
|
||||||
|
"ASProtocols", h.appserviceURL+AppServiceProtocolsPath,
|
||||||
|
h.httpClient, ctx, request, response,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package inthttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice/api"
|
"github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
)
|
)
|
||||||
|
|
@ -17,4 +18,19 @@ func AddRoutes(a api.AppServiceInternalAPI, internalAPIMux *mux.Router) {
|
||||||
AppServiceUserIDExistsPath,
|
AppServiceUserIDExistsPath,
|
||||||
httputil.MakeInternalRPCAPI("AppserviceUserIDExists", a.UserIDExists),
|
httputil.MakeInternalRPCAPI("AppserviceUserIDExists", a.UserIDExists),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internalAPIMux.Handle(
|
||||||
|
AppServiceProtocolsPath,
|
||||||
|
httputil.MakeInternalRPCAPI("AppserviceProtocols", a.Protocols),
|
||||||
|
)
|
||||||
|
|
||||||
|
internalAPIMux.Handle(
|
||||||
|
AppServiceLocationsPath,
|
||||||
|
httputil.MakeInternalRPCAPI("AppserviceLocations", a.Locations),
|
||||||
|
)
|
||||||
|
|
||||||
|
internalAPIMux.Handle(
|
||||||
|
AppServiceUserPath,
|
||||||
|
httputil.MakeInternalRPCAPI("AppserviceUser", a.User),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,18 @@ package query
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/opentracing/opentracing-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/appservice/api"
|
"github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const roomAliasExistsPath = "/rooms/"
|
const roomAliasExistsPath = "/rooms/"
|
||||||
|
|
@ -32,8 +37,10 @@ const userIDExistsPath = "/users/"
|
||||||
|
|
||||||
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
|
||||||
type AppServiceQueryAPI struct {
|
type AppServiceQueryAPI struct {
|
||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
Cfg *config.AppServiceAPI
|
Cfg *config.AppServiceAPI
|
||||||
|
ProtocolCache map[string]api.ASProtocolResponse
|
||||||
|
CacheMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomAliasExists performs a request to '/room/{roomAlias}' on all known
|
// RoomAliasExists performs a request to '/room/{roomAlias}' on all known
|
||||||
|
|
@ -165,3 +172,178 @@ func (a *AppServiceQueryAPI) UserIDExists(
|
||||||
response.UserIDExists = false
|
response.UserIDExists = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type thirdpartyResponses interface {
|
||||||
|
api.ASProtocolResponse | []api.ASUserResponse | []api.ASLocationResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestDo[T thirdpartyResponses](client *http.Client, url string, response *T) (err error) {
|
||||||
|
origURL := url
|
||||||
|
// try v1 and unstable appservice endpoints
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppServiceQueryAPI) Locations(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.LocationRequest,
|
||||||
|
resp *api.LocationResponse,
|
||||||
|
) error {
|
||||||
|
params, err := url.ParseQuery(req.Params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
|
var asLocations []api.ASLocationResponse
|
||||||
|
params.Set("access_token", as.HSToken)
|
||||||
|
|
||||||
|
url := as.URL + api.ASLocationPath
|
||||||
|
if req.Protocol != "" {
|
||||||
|
url += "/" + req.Protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := requestDo[[]api.ASLocationResponse](a.HTTPClient, url+"?"+params.Encode(), &asLocations); err != nil {
|
||||||
|
log.WithError(err).Error("unable to get 'locations' from application service")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Locations = append(resp.Locations, asLocations...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Locations) == 0 {
|
||||||
|
resp.Exists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp.Exists = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppServiceQueryAPI) User(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.UserRequest,
|
||||||
|
resp *api.UserResponse,
|
||||||
|
) error {
|
||||||
|
params, err := url.ParseQuery(req.Params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
|
var asUsers []api.ASUserResponse
|
||||||
|
params.Set("access_token", as.HSToken)
|
||||||
|
|
||||||
|
url := as.URL + api.ASUserPath
|
||||||
|
if req.Protocol != "" {
|
||||||
|
url += "/" + req.Protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := requestDo[[]api.ASUserResponse](a.HTTPClient, url+"?"+params.Encode(), &asUsers); err != nil {
|
||||||
|
log.WithError(err).Error("unable to get 'user' from application service")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Users = append(resp.Users, asUsers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Users) == 0 {
|
||||||
|
resp.Exists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp.Exists = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppServiceQueryAPI) Protocols(
|
||||||
|
ctx context.Context,
|
||||||
|
req *api.ProtocolRequest,
|
||||||
|
resp *api.ProtocolResponse,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
// get a single protocol response
|
||||||
|
if req.Protocol != "" {
|
||||||
|
|
||||||
|
a.CacheMu.Lock()
|
||||||
|
defer a.CacheMu.Unlock()
|
||||||
|
if proto, ok := a.ProtocolCache[req.Protocol]; ok {
|
||||||
|
resp.Exists = true
|
||||||
|
resp.Protocols = map[string]api.ASProtocolResponse{
|
||||||
|
req.Protocol: proto,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := api.ASProtocolResponse{}
|
||||||
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
|
var proto api.ASProtocolResponse
|
||||||
|
if err := requestDo[api.ASProtocolResponse](a.HTTPClient, as.URL+api.ASProtocolPath+req.Protocol, &proto); err != nil {
|
||||||
|
log.WithError(err).Error("unable to get 'protocol' from application service")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Instances) != 0 {
|
||||||
|
response.Instances = append(response.Instances, proto.Instances...)
|
||||||
|
} else {
|
||||||
|
response = proto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Instances) == 0 {
|
||||||
|
resp.Exists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Exists = true
|
||||||
|
resp.Protocols = map[string]api.ASProtocolResponse{
|
||||||
|
req.Protocol: response,
|
||||||
|
}
|
||||||
|
a.ProtocolCache[req.Protocol] = response
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make(map[string]api.ASProtocolResponse, len(a.Cfg.Derived.ApplicationServices))
|
||||||
|
|
||||||
|
for _, as := range a.Cfg.Derived.ApplicationServices {
|
||||||
|
for _, p := range as.Protocols {
|
||||||
|
var proto api.ASProtocolResponse
|
||||||
|
if err := requestDo[api.ASProtocolResponse](a.HTTPClient, as.URL+api.ASProtocolPath+p, &proto); err != nil {
|
||||||
|
log.WithError(err).Error("unable to get 'protocol' from application service")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existing, ok := response[p]
|
||||||
|
if !ok {
|
||||||
|
response[p] = proto
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existing.Instances = append(existing.Instances, proto.Instances...)
|
||||||
|
response[p] = existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response) == 0 {
|
||||||
|
resp.Exists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CacheMu.Lock()
|
||||||
|
defer a.CacheMu.Unlock()
|
||||||
|
a.ProtocolCache = response
|
||||||
|
|
||||||
|
resp.Exists = true
|
||||||
|
resp.Protocols = response
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -643,7 +643,7 @@ fed Inbound federation redacts events from erased users
|
||||||
fme Outbound federation can request missing events
|
fme Outbound federation can request missing events
|
||||||
fme Inbound federation can return missing events for world_readable visibility
|
fme Inbound federation can return missing events for world_readable visibility
|
||||||
fme Inbound federation can return missing events for shared visibility
|
fme Inbound federation can return missing events for shared visibility
|
||||||
fme Inbound federation can return missing events for invite visibility
|
fme Inbound federation can return missing events for invited visibility
|
||||||
fme Inbound federation can return missing events for joined visibility
|
fme Inbound federation can return missing events for joined visibility
|
||||||
fme outliers whose auth_events are in a different room are correctly rejected
|
fme outliers whose auth_events are in a different room are correctly rejected
|
||||||
fbk Outbound federation can backfill events
|
fbk Outbound federation can backfill events
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
FROM docker.io/golang:1.19-alpine AS base
|
FROM docker.io/golang:1.19-alpine AS base
|
||||||
|
|
||||||
|
#
|
||||||
|
# Needs to be separate from the main Dockerfile for OpenShift,
|
||||||
|
# as --target is not supported there.
|
||||||
|
#
|
||||||
|
|
||||||
RUN apk --update --no-cache add bash build-base
|
RUN apk --update --no-cache add bash build-base
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
FROM docker.io/golang:1.19-alpine AS base
|
FROM docker.io/golang:1.19-alpine AS base
|
||||||
|
|
||||||
|
#
|
||||||
|
# Needs to be separate from the main Dockerfile for OpenShift,
|
||||||
|
# as --target is not supported there.
|
||||||
|
#
|
||||||
|
|
||||||
RUN apk --update --no-cache add bash build-base
|
RUN apk --update --no-cache add bash build-base
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
@ -7,12 +12,12 @@ WORKDIR /build
|
||||||
COPY . /build
|
COPY . /build
|
||||||
|
|
||||||
RUN mkdir -p bin
|
RUN mkdir -p bin
|
||||||
RUN go build -trimpath -o bin/ ./cmd/dendrite-monolith-server
|
RUN go build -trimpath -o bin/ ./cmd/dendrite-demo-yggdrasil
|
||||||
RUN go build -trimpath -o bin/ ./cmd/create-account
|
RUN go build -trimpath -o bin/ ./cmd/create-account
|
||||||
RUN go build -trimpath -o bin/ ./cmd/generate-keys
|
RUN go build -trimpath -o bin/ ./cmd/generate-keys
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
LABEL org.opencontainers.image.title="Dendrite (Monolith)"
|
LABEL org.opencontainers.image.title="Dendrite (Yggdrasil demo)"
|
||||||
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
|
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
|
||||||
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
|
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
|
||||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||||
|
|
@ -22,4 +27,4 @@ COPY --from=base /build/bin/* /usr/bin/
|
||||||
VOLUME /etc/dendrite
|
VOLUME /etc/dendrite
|
||||||
WORKDIR /etc/dendrite
|
WORKDIR /etc/dendrite
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/dendrite-monolith-server"]
|
ENTRYPOINT ["/usr/bin/dendrite-demo-yggdrasil"]
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
FROM docker.io/golang:1.19-alpine AS base
|
|
||||||
|
|
||||||
RUN apk --update --no-cache add bash build-base
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
COPY . /build
|
|
||||||
|
|
||||||
RUN mkdir -p bin
|
|
||||||
RUN go build -trimpath -o bin/ ./cmd/dendrite-polylith-multi
|
|
||||||
RUN go build -trimpath -o bin/ ./cmd/create-account
|
|
||||||
RUN go build -trimpath -o bin/ ./cmd/generate-keys
|
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
LABEL org.opencontainers.image.title="Dendrite (Polylith)"
|
|
||||||
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
|
|
||||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
|
||||||
|
|
||||||
COPY --from=base /build/bin/* /usr/bin/
|
|
||||||
|
|
||||||
VOLUME /etc/dendrite
|
|
||||||
WORKDIR /etc/dendrite
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/dendrite-polylith-multi"]
|
|
||||||
|
|
@ -9,15 +9,20 @@ They can be found on Docker Hub:
|
||||||
|
|
||||||
## Dockerfiles
|
## Dockerfiles
|
||||||
|
|
||||||
The `Dockerfile` builds the base image which contains all of the Dendrite
|
The `Dockerfile` is a multistage file which can build all four Dendrite
|
||||||
components. The `Dockerfile.component` file takes the given component, as
|
images depending on the supplied `--target`. From the root of the Dendrite
|
||||||
specified with `--buildarg component=` from the base image and produce
|
repository, run:
|
||||||
smaller component-specific images, which are substantially smaller and do
|
|
||||||
not contain the Go toolchain etc.
|
```
|
||||||
|
docker build . --target monolith -t matrixdotorg/dendrite-monolith
|
||||||
|
docker build . --target polylith -t matrixdotorg/dendrite-monolith
|
||||||
|
docker build . --target demo-pinecone -t matrixdotorg/dendrite-demo-pinecone
|
||||||
|
docker build . --target demo-yggdrasil -t matrixdotorg/dendrite-demo-yggdrasil
|
||||||
|
```
|
||||||
|
|
||||||
## Compose files
|
## Compose files
|
||||||
|
|
||||||
There are three sample `docker-compose` files:
|
There are two sample `docker-compose` files:
|
||||||
|
|
||||||
- `docker-compose.monolith.yml` which runs a monolith Dendrite deployment
|
- `docker-compose.monolith.yml` which runs a monolith Dendrite deployment
|
||||||
- `docker-compose.polylith.yml` which runs a polylith Dendrite deployment
|
- `docker-compose.polylith.yml` which runs a polylith Dendrite deployment
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ TAG=${1:-latest}
|
||||||
|
|
||||||
echo "Building tag '${TAG}'"
|
echo "Building tag '${TAG}'"
|
||||||
|
|
||||||
docker build -t matrixdotorg/dendrite-monolith:${TAG} -f build/docker/Dockerfile.monolith .
|
docker build . --target monolith -t matrixdotorg/dendrite-monolith:${TAG}
|
||||||
docker build -t matrixdotorg/dendrite-polylith:${TAG} -f build/docker/Dockerfile.polylith .
|
docker build . --target polylith -t matrixdotorg/dendrite-monolith:${TAG}
|
||||||
|
docker build . --target demo-pinecone -t matrixdotorg/dendrite-demo-pinecone:${TAG}
|
||||||
|
docker build . --target demo-yggdrasil -t matrixdotorg/dendrite-demo-yggdrasil:${TAG}
|
||||||
|
|
@ -30,6 +30,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/appservice"
|
"github.com/matrix-org/dendrite/appservice"
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
|
|
@ -66,6 +68,7 @@ const (
|
||||||
PeerTypeRemote = pineconeRouter.PeerTypeRemote
|
PeerTypeRemote = pineconeRouter.PeerTypeRemote
|
||||||
PeerTypeMulticast = pineconeRouter.PeerTypeMulticast
|
PeerTypeMulticast = pineconeRouter.PeerTypeMulticast
|
||||||
PeerTypeBluetooth = pineconeRouter.PeerTypeBluetooth
|
PeerTypeBluetooth = pineconeRouter.PeerTypeBluetooth
|
||||||
|
PeerTypeBonjour = pineconeRouter.PeerTypeBonjour
|
||||||
)
|
)
|
||||||
|
|
||||||
type DendriteMonolith struct {
|
type DendriteMonolith struct {
|
||||||
|
|
@ -82,6 +85,10 @@ type DendriteMonolith struct {
|
||||||
userAPI userapiAPI.UserInternalAPI
|
userAPI userapiAPI.UserInternalAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *DendriteMonolith) PublicKey() string {
|
||||||
|
return m.PineconeRouter.PublicKey().String()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) BaseURL() string {
|
func (m *DendriteMonolith) BaseURL() string {
|
||||||
return fmt.Sprintf("http://%s", m.listener.Addr().String())
|
return fmt.Sprintf("http://%s", m.listener.Addr().String())
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +101,48 @@ func (m *DendriteMonolith) SessionCount() int {
|
||||||
return len(m.PineconeQUIC.Protocol("matrix").Sessions())
|
return len(m.PineconeQUIC.Protocol("matrix").Sessions())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InterfaceInfo struct {
|
||||||
|
Name string
|
||||||
|
Index int
|
||||||
|
Mtu int
|
||||||
|
Up bool
|
||||||
|
Broadcast bool
|
||||||
|
Loopback bool
|
||||||
|
PointToPoint bool
|
||||||
|
Multicast bool
|
||||||
|
Addrs string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InterfaceRetriever interface {
|
||||||
|
CacheCurrentInterfaces() int
|
||||||
|
GetCachedInterface(index int) *InterfaceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DendriteMonolith) RegisterNetworkCallback(intfCallback InterfaceRetriever) {
|
||||||
|
callback := func() []pineconeMulticast.InterfaceInfo {
|
||||||
|
count := intfCallback.CacheCurrentInterfaces()
|
||||||
|
intfs := []pineconeMulticast.InterfaceInfo{}
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
iface := intfCallback.GetCachedInterface(i)
|
||||||
|
if iface != nil {
|
||||||
|
intfs = append(intfs, pineconeMulticast.InterfaceInfo{
|
||||||
|
Name: iface.Name,
|
||||||
|
Index: iface.Index,
|
||||||
|
Mtu: iface.Mtu,
|
||||||
|
Up: iface.Up,
|
||||||
|
Broadcast: iface.Broadcast,
|
||||||
|
Loopback: iface.Loopback,
|
||||||
|
PointToPoint: iface.PointToPoint,
|
||||||
|
Multicast: iface.Multicast,
|
||||||
|
Addrs: iface.Addrs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intfs
|
||||||
|
}
|
||||||
|
m.PineconeMulticast.RegisterNetworkCallback(callback)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
||||||
if enabled {
|
if enabled {
|
||||||
m.PineconeMulticast.Start()
|
m.PineconeMulticast.Start()
|
||||||
|
|
@ -105,7 +154,9 @@ func (m *DendriteMonolith) SetMulticastEnabled(enabled bool) {
|
||||||
|
|
||||||
func (m *DendriteMonolith) SetStaticPeer(uri string) {
|
func (m *DendriteMonolith) SetStaticPeer(uri string) {
|
||||||
m.PineconeManager.RemovePeers()
|
m.PineconeManager.RemovePeers()
|
||||||
m.PineconeManager.AddPeer(strings.TrimSpace(uri))
|
for _, uri := range strings.Split(uri, ",") {
|
||||||
|
m.PineconeManager.AddPeer(strings.TrimSpace(uri))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DendriteMonolith) DisconnectType(peertype int) {
|
func (m *DendriteMonolith) DisconnectType(peertype int) {
|
||||||
|
|
@ -134,32 +185,21 @@ func (m *DendriteMonolith) Conduit(zone string, peertype int) (*Conduit, error)
|
||||||
go func() {
|
go func() {
|
||||||
conduit.portMutex.Lock()
|
conduit.portMutex.Lock()
|
||||||
defer conduit.portMutex.Unlock()
|
defer conduit.portMutex.Unlock()
|
||||||
loop:
|
|
||||||
for i := 1; i <= 10; i++ {
|
logrus.Errorf("Attempting authenticated connect")
|
||||||
logrus.Errorf("Attempting authenticated connect (attempt %d)", i)
|
var err error
|
||||||
var err error
|
if conduit.port, err = m.PineconeRouter.Connect(
|
||||||
conduit.port, err = m.PineconeRouter.Connect(
|
l,
|
||||||
l,
|
pineconeRouter.ConnectionZone(zone),
|
||||||
pineconeRouter.ConnectionZone(zone),
|
pineconeRouter.ConnectionPeerType(peertype),
|
||||||
pineconeRouter.ConnectionPeerType(peertype),
|
); err != nil {
|
||||||
)
|
logrus.Errorf("Authenticated connect failed: %s", err)
|
||||||
switch err {
|
_ = l.Close()
|
||||||
case io.ErrClosedPipe:
|
_ = r.Close()
|
||||||
logrus.Errorf("Authenticated connect failed due to closed pipe (attempt %d)", i)
|
_ = conduit.Close()
|
||||||
return
|
return
|
||||||
case io.EOF:
|
|
||||||
logrus.Errorf("Authenticated connect failed due to EOF (attempt %d)", i)
|
|
||||||
break loop
|
|
||||||
case nil:
|
|
||||||
logrus.Errorf("Authenticated connect succeeded, connected to port %d (attempt %d)", conduit.port, i)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
logrus.WithError(err).Errorf("Authenticated connect failed (attempt %d)", i)
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ = l.Close()
|
logrus.Infof("Authenticated connect succeeded (port %d)", conduit.port)
|
||||||
_ = r.Close()
|
|
||||||
}()
|
}()
|
||||||
return conduit, nil
|
return conduit, nil
|
||||||
}
|
}
|
||||||
|
|
@ -269,19 +309,21 @@ func (m *DendriteMonolith) Start() {
|
||||||
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
cfg.Global.ServerName = gomatrixserverlib.ServerName(hex.EncodeToString(pk))
|
||||||
cfg.Global.PrivateKey = sk
|
cfg.Global.PrivateKey = sk
|
||||||
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
cfg.Global.KeyID = gomatrixserverlib.KeyID(signing.KeyID)
|
||||||
cfg.Global.JetStream.InMemory = true
|
cfg.Global.JetStream.InMemory = false
|
||||||
cfg.Global.JetStream.StoragePath = config.Path(fmt.Sprintf("%s/%s", m.StorageDirectory, prefix))
|
cfg.Global.JetStream.StoragePath = config.Path(filepath.Join(m.CacheDirectory, prefix))
|
||||||
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-account.db", m.StorageDirectory, prefix))
|
cfg.UserAPI.AccountDatabase.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-account.db", filepath.Join(m.StorageDirectory, prefix)))
|
||||||
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/dendrite-p2p-mediaapi.db", m.StorageDirectory))
|
cfg.MediaAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mediaapi.db", filepath.Join(m.StorageDirectory, prefix)))
|
||||||
cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-syncapi.db", m.StorageDirectory, prefix))
|
cfg.SyncAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-syncapi.db", filepath.Join(m.StorageDirectory, prefix)))
|
||||||
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-roomserver.db", m.StorageDirectory, prefix))
|
cfg.RoomServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-roomserver.db", filepath.Join(m.StorageDirectory, prefix)))
|
||||||
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-keyserver.db", m.StorageDirectory, prefix))
|
cfg.KeyServer.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-keyserver.db", filepath.Join(m.StorageDirectory, prefix)))
|
||||||
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s/%s-federationsender.db", m.StorageDirectory, prefix))
|
cfg.FederationAPI.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-federationsender.db", filepath.Join(m.StorageDirectory, prefix)))
|
||||||
cfg.MediaAPI.BasePath = config.Path(fmt.Sprintf("%s/media", m.CacheDirectory))
|
cfg.MediaAPI.BasePath = config.Path(filepath.Join(m.CacheDirectory, "media"))
|
||||||
cfg.MediaAPI.AbsBasePath = config.Path(fmt.Sprintf("%s/media", m.CacheDirectory))
|
cfg.MediaAPI.AbsBasePath = config.Path(filepath.Join(m.CacheDirectory, "media"))
|
||||||
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
cfg.MSCs.MSCs = []string{"msc2836", "msc2946"}
|
||||||
cfg.ClientAPI.RegistrationDisabled = false
|
cfg.ClientAPI.RegistrationDisabled = false
|
||||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||||
|
cfg.SyncAPI.Fulltext.Enabled = true
|
||||||
|
cfg.SyncAPI.Fulltext.IndexPath = config.Path(filepath.Join(m.CacheDirectory, "search"))
|
||||||
if err = cfg.Derive(); err != nil {
|
if err = cfg.Derive(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -395,6 +437,7 @@ func (m *DendriteMonolith) Stop() {
|
||||||
const MaxFrameSize = types.MaxFrameSize
|
const MaxFrameSize = types.MaxFrameSize
|
||||||
|
|
||||||
type Conduit struct {
|
type Conduit struct {
|
||||||
|
closed atomic.Bool
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
port types.SwitchPortID
|
port types.SwitchPortID
|
||||||
portMutex sync.Mutex
|
portMutex sync.Mutex
|
||||||
|
|
@ -407,10 +450,16 @@ func (c *Conduit) Port() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conduit) Read(b []byte) (int, error) {
|
func (c *Conduit) Read(b []byte) (int, error) {
|
||||||
|
if c.closed.Load() {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
return c.conn.Read(b)
|
return c.conn.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conduit) ReadCopy() ([]byte, error) {
|
func (c *Conduit) ReadCopy() ([]byte, error) {
|
||||||
|
if c.closed.Load() {
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
var buf [65535 * 2]byte
|
var buf [65535 * 2]byte
|
||||||
n, err := c.conn.Read(buf[:])
|
n, err := c.conn.Read(buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -420,9 +469,16 @@ func (c *Conduit) ReadCopy() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conduit) Write(b []byte) (int, error) {
|
func (c *Conduit) Write(b []byte) (int, error) {
|
||||||
|
if c.closed.Load() {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conduit) Close() error {
|
func (c *Conduit) Close() error {
|
||||||
|
if c.closed.Load() {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
c.closed.Store(true)
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,13 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
|
||||||
JSON: jsonerror.BadJSON("A username must be supplied."),
|
JSON: jsonerror.BadJSON("A username must be supplied."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localpart, err := userutil.ParseUsernameParam(username, &t.Config.Matrix.ServerName)
|
if len(r.Password) == 0 {
|
||||||
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusUnauthorized,
|
||||||
|
JSON: jsonerror.BadJSON("A password must be supplied."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localpart, _, 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,
|
||||||
|
|
|
||||||
|
|
@ -154,33 +154,31 @@ func SaveReadMarker(
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.FullyRead == "" {
|
if r.FullyRead != "" {
|
||||||
return util.JSONResponse{
|
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
|
||||||
Code: http.StatusBadRequest,
|
if err != nil {
|
||||||
JSON: jsonerror.BadJSON("Missing m.fully_read mandatory field"),
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
dataReq := api.InputAccountDataRequest{
|
||||||
|
UserID: device.UserID,
|
||||||
|
DataType: "m.fully_read",
|
||||||
|
RoomID: roomID,
|
||||||
|
AccountData: data,
|
||||||
|
}
|
||||||
|
dataRes := api.InputAccountDataResponse{}
|
||||||
|
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
|
||||||
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(fullyReadEvent{EventID: r.FullyRead})
|
// Handle the read receipts that may be included in the read marker.
|
||||||
if err != nil {
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
dataReq := api.InputAccountDataRequest{
|
|
||||||
UserID: device.UserID,
|
|
||||||
DataType: "m.fully_read",
|
|
||||||
RoomID: roomID,
|
|
||||||
AccountData: data,
|
|
||||||
}
|
|
||||||
dataRes := api.InputAccountDataResponse{}
|
|
||||||
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
|
|
||||||
return util.ErrorResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the read receipt that may be included in the read marker
|
|
||||||
if r.Read != "" {
|
if r.Read != "" {
|
||||||
return SetReceipt(req, syncProducer, device, roomID, "m.read", r.Read)
|
return SetReceipt(req, userAPI, syncProducer, device, roomID, "m.read", r.Read)
|
||||||
|
}
|
||||||
|
if r.ReadPrivate != "" {
|
||||||
|
return SetReceipt(req, userAPI, syncProducer, device, roomID, "m.read.private", r.ReadPrivate)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,23 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/internal/httputil"
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/internal/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AdminEvacuateRoom(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
func AdminEvacuateRoom(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||||
|
|
@ -63,7 +70,7 @@ func AdminEvacuateUser(req *http.Request, cfg *config.ClientAPI, device *userapi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
if domain != cfg.Matrix.ServerName {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.MissingArgument("User ID must belong to this server."),
|
JSON: jsonerror.MissingArgument("User ID must belong to this server."),
|
||||||
|
|
@ -138,3 +145,89 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *userap
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, natsClient *nats.Conn) util.JSONResponse {
|
||||||
|
_, err := natsClient.RequestMsg(nats.NewMsg(cfg.Matrix.JetStream.Prefixed(jetstream.InputFulltextReindex)), time.Second*10)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("failed to publish nats message")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.ClientKeyAPI) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
userID := vars["userID"]
|
||||||
|
|
||||||
|
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
|
if err != nil {
|
||||||
|
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
if cfg.Matrix.IsLocalServerName(domain) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidParam("Can not mark local device list as stale"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = keyAPI.PerformMarkAsStaleIfNeeded(req.Context(), &api.PerformMarkAsStaleRequest{
|
||||||
|
UserID: userID,
|
||||||
|
Domain: domain,
|
||||||
|
}, &struct{}{})
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: jsonerror.Unknown(fmt.Sprintf("Failed to mark device list as stale: %s", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
|
||||||
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
roomID, ok := vars["roomID"]
|
||||||
|
if !ok {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.MissingArgument("Expecting room ID."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serverName, ok := vars["serverName"]
|
||||||
|
if !ok {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.MissingArgument("Expecting remote server name."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := &roomserverAPI.PerformAdminDownloadStateResponse{}
|
||||||
|
if err := rsAPI.PerformAdminDownloadState(
|
||||||
|
req.Context(),
|
||||||
|
&roomserverAPI.PerformAdminDownloadStateRequest{
|
||||||
|
UserID: device.UserID,
|
||||||
|
RoomID: roomID,
|
||||||
|
ServerName: gomatrixserverlib.ServerName(serverName),
|
||||||
|
},
|
||||||
|
res,
|
||||||
|
); err != nil {
|
||||||
|
return jsonerror.InternalAPIError(req.Context(), err)
|
||||||
|
}
|
||||||
|
if err := res.Error; err != nil {
|
||||||
|
return err.JSONResponse()
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ const recaptchaTemplate = `
|
||||||
<title>Authentication</title>
|
<title>Authentication</title>
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1,
|
<meta name='viewport' content='width=device-width, initial-scale=1,
|
||||||
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
|
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
|
||||||
<script src="https://www.google.com/recaptcha/api.js"
|
<script src="{{.apiJsUrl}}" async defer></script>
|
||||||
async defer></script>
|
|
||||||
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
|
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function captchaDone() {
|
function captchaDone() {
|
||||||
|
|
@ -51,8 +50,8 @@ function captchaDone() {
|
||||||
Please verify that you're not a robot.
|
Please verify that you're not a robot.
|
||||||
</p>
|
</p>
|
||||||
<input type="hidden" name="session" value="{{.session}}" />
|
<input type="hidden" name="session" value="{{.session}}" />
|
||||||
<div class="g-recaptcha"
|
<div class="{{.sitekeyClass}}"
|
||||||
data-sitekey="{{.siteKey}}"
|
data-sitekey="{{.sitekey}}"
|
||||||
data-callback="captchaDone">
|
data-callback="captchaDone">
|
||||||
</div>
|
</div>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
@ -114,9 +113,12 @@ func AuthFallback(
|
||||||
|
|
||||||
serveRecaptcha := func() {
|
serveRecaptcha := func() {
|
||||||
data := map[string]string{
|
data := map[string]string{
|
||||||
"myUrl": req.URL.String(),
|
"myUrl": req.URL.String(),
|
||||||
"session": sessionID,
|
"session": sessionID,
|
||||||
"siteKey": cfg.RecaptchaPublicKey,
|
"apiJsUrl": cfg.RecaptchaApiJsUrl,
|
||||||
|
"sitekey": cfg.RecaptchaPublicKey,
|
||||||
|
"sitekeyClass": cfg.RecaptchaSitekeyClass,
|
||||||
|
"formField": cfg.RecaptchaFormField,
|
||||||
}
|
}
|
||||||
serveTemplate(w, recaptchaTemplate, data)
|
serveTemplate(w, recaptchaTemplate, data)
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +157,7 @@ func AuthFallback(
|
||||||
return &res
|
return &res
|
||||||
}
|
}
|
||||||
|
|
||||||
response := req.Form.Get("g-recaptcha-response")
|
response := req.Form.Get(cfg.RecaptchaFormField)
|
||||||
if err := validateRecaptcha(cfg, response, clientIP); err != nil {
|
if err := validateRecaptcha(cfg, response, clientIP); err != nil {
|
||||||
util.GetLogger(req.Context()).Error(err)
|
util.GetLogger(req.Context()).Error(err)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -169,9 +169,21 @@ func createRoom(
|
||||||
asAPI appserviceAPI.AppServiceInternalAPI,
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
_, userDomain, err := gomatrixserverlib.SplitID('@', device.UserID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
if !cfg.Matrix.IsLocalServerName(userDomain) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden(fmt.Sprintf("User domain %q not configured locally", userDomain)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
// TODO (#267): Check room ID doesn't clash with an existing one, and we
|
||||||
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||||
roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), cfg.Matrix.ServerName)
|
roomID := fmt.Sprintf("!%s:%s", util.RandomString(16), userDomain)
|
||||||
|
|
||||||
logger := util.GetLogger(ctx)
|
logger := util.GetLogger(ctx)
|
||||||
userID := device.UserID
|
userID := device.UserID
|
||||||
|
|
@ -314,7 +326,7 @@ func createRoom(
|
||||||
|
|
||||||
var roomAlias string
|
var roomAlias string
|
||||||
if r.RoomAliasName != "" {
|
if r.RoomAliasName != "" {
|
||||||
roomAlias = fmt.Sprintf("#%s:%s", r.RoomAliasName, cfg.Matrix.ServerName)
|
roomAlias = fmt.Sprintf("#%s:%s", r.RoomAliasName, userDomain)
|
||||||
// check it's free TODO: This races but is better than nothing
|
// check it's free TODO: This races but is better than nothing
|
||||||
hasAliasReq := roomserverAPI.GetRoomIDForAliasRequest{
|
hasAliasReq := roomserverAPI.GetRoomIDForAliasRequest{
|
||||||
Alias: roomAlias,
|
Alias: roomAlias,
|
||||||
|
|
@ -436,7 +448,7 @@ func createRoom(
|
||||||
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
builder.PrevEvents = []gomatrixserverlib.EventReference{builtEvents[i-1].EventReference()}
|
||||||
}
|
}
|
||||||
var ev *gomatrixserverlib.Event
|
var ev *gomatrixserverlib.Event
|
||||||
ev, err = buildEvent(&builder, &authEvents, cfg, evTime, roomVersion)
|
ev, err = buildEvent(&builder, userDomain, &authEvents, cfg, evTime, roomVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
|
|
@ -461,7 +473,7 @@ func createRoom(
|
||||||
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
||||||
Kind: roomserverAPI.KindNew,
|
Kind: roomserverAPI.KindNew,
|
||||||
Event: event,
|
Event: event,
|
||||||
Origin: cfg.Matrix.ServerName,
|
Origin: userDomain,
|
||||||
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -548,7 +560,7 @@ func createRoom(
|
||||||
Event: event,
|
Event: event,
|
||||||
InviteRoomState: inviteStrippedState,
|
InviteRoomState: inviteStrippedState,
|
||||||
RoomVersion: event.RoomVersion,
|
RoomVersion: event.RoomVersion,
|
||||||
SendAsServer: string(cfg.Matrix.ServerName),
|
SendAsServer: string(userDomain),
|
||||||
}, &inviteRes); err != nil {
|
}, &inviteRes); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -591,6 +603,7 @@ func createRoom(
|
||||||
// buildEvent fills out auth_events for the builder then builds the event
|
// buildEvent fills out auth_events for the builder then builds the event
|
||||||
func buildEvent(
|
func buildEvent(
|
||||||
builder *gomatrixserverlib.EventBuilder,
|
builder *gomatrixserverlib.EventBuilder,
|
||||||
|
serverName gomatrixserverlib.ServerName,
|
||||||
provider gomatrixserverlib.AuthEventProvider,
|
provider gomatrixserverlib.AuthEventProvider,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
evTime time.Time,
|
evTime time.Time,
|
||||||
|
|
@ -606,7 +619,7 @@ func buildEvent(
|
||||||
}
|
}
|
||||||
builder.AuthEvents = refs
|
builder.AuthEvents = refs
|
||||||
event, err := builder.Build(
|
event, err := builder.Build(
|
||||||
evTime, cfg.Matrix.ServerName, cfg.Matrix.KeyID,
|
evTime, serverName, cfg.Matrix.KeyID,
|
||||||
cfg.Matrix.PrivateKey, roomVersion,
|
cfg.Matrix.PrivateKey, roomVersion,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
federationAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
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/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type roomDirectoryResponse struct {
|
type roomDirectoryResponse struct {
|
||||||
|
|
@ -75,7 +76,7 @@ func DirectoryRoom(
|
||||||
if res.RoomID == "" {
|
if res.RoomID == "" {
|
||||||
// If we don't know it locally, do a federation query.
|
// If we don't know it locally, do a federation query.
|
||||||
// But don't send the query to ourselves.
|
// But don't send the query to ourselves.
|
||||||
if domain != cfg.Matrix.ServerName {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
fedRes, fedErr := federation.LookupRoomAlias(req.Context(), domain, roomAlias)
|
fedRes, fedErr := federation.LookupRoomAlias(req.Context(), domain, roomAlias)
|
||||||
if fedErr != nil {
|
if fedErr != nil {
|
||||||
// TODO: Return 502 if the remote server errored.
|
// TODO: Return 502 if the remote server errored.
|
||||||
|
|
@ -127,7 +128,7 @@ func SetLocalAlias(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if domain != cfg.Matrix.ServerName {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusForbidden,
|
Code: http.StatusForbidden,
|
||||||
JSON: jsonerror.Forbidden("Alias must be on local homeserver"),
|
JSON: jsonerror.Forbidden("Alias must be on local homeserver"),
|
||||||
|
|
@ -318,3 +319,43 @@ func SetVisibility(
|
||||||
JSON: struct{}{},
|
JSON: struct{}{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetVisibilityAS(
|
||||||
|
req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, dev *userapi.Device,
|
||||||
|
networkID, roomID string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
if dev.AccountType != userapi.AccountTypeAppService {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("Only appservice may use this endpoint"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var v roomVisibility
|
||||||
|
|
||||||
|
// If the method is delete, we simply mark the visibility as private
|
||||||
|
if req.Method == http.MethodDelete {
|
||||||
|
v.Visibility = "private"
|
||||||
|
} else {
|
||||||
|
if reqErr := httputil.UnmarshalJSONRequest(req, &v); reqErr != nil {
|
||||||
|
return *reqErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var publishRes roomserverAPI.PerformPublishResponse
|
||||||
|
if err := rsAPI.PerformPublish(req.Context(), &roomserverAPI.PerformPublishRequest{
|
||||||
|
RoomID: roomID,
|
||||||
|
Visibility: v.Visibility,
|
||||||
|
NetworkID: networkID,
|
||||||
|
AppserviceID: dev.AppserviceID,
|
||||||
|
}, &publishRes); err != nil {
|
||||||
|
return jsonerror.InternalAPIError(req.Context(), err)
|
||||||
|
}
|
||||||
|
if publishRes.Error != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(publishRes.Error).Error("PerformPublish failed")
|
||||||
|
return publishRes.Error.JSONResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,17 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PublicRoomReq struct {
|
type PublicRoomReq struct {
|
||||||
Since string `json:"since,omitempty"`
|
Since string `json:"since,omitempty"`
|
||||||
Limit int16 `json:"limit,omitempty"`
|
Limit int64 `json:"limit,omitempty"`
|
||||||
Filter filter `json:"filter,omitempty"`
|
Filter filter `json:"filter,omitempty"`
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty"`
|
||||||
|
IncludeAllNetworks bool `json:"include_all_networks,omitempty"`
|
||||||
|
NetworkID string `json:"third_party_instance_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type filter struct {
|
type filter struct {
|
||||||
SearchTerms string `json:"generic_search_term,omitempty"`
|
SearchTerms string `json:"generic_search_term,omitempty"`
|
||||||
|
RoomTypes []string `json:"room_types,omitempty"` // TODO: Implement filter on this
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostPublicRooms implements GET and POST /publicRooms
|
// GetPostPublicRooms implements GET and POST /publicRooms
|
||||||
|
|
@ -61,9 +64,15 @@ func GetPostPublicRooms(
|
||||||
return *fillErr
|
return *fillErr
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName := gomatrixserverlib.ServerName(request.Server)
|
if request.IncludeAllNetworks && request.NetworkID != "" {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidParam("include_all_networks and third_party_instance_id can not be used together"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if serverName != "" && serverName != cfg.Matrix.ServerName {
|
serverName := gomatrixserverlib.ServerName(request.Server)
|
||||||
|
if serverName != "" && !cfg.Matrix.IsLocalServerName(serverName) {
|
||||||
res, err := federation.GetPublicRoomsFiltered(
|
res, err := federation.GetPublicRoomsFiltered(
|
||||||
req.Context(), serverName,
|
req.Context(), serverName,
|
||||||
int(request.Limit), request.Since,
|
int(request.Limit), request.Since,
|
||||||
|
|
@ -98,7 +107,7 @@ func publicRooms(
|
||||||
response := gomatrixserverlib.RespPublicRooms{
|
response := gomatrixserverlib.RespPublicRooms{
|
||||||
Chunk: []gomatrixserverlib.PublicRoom{},
|
Chunk: []gomatrixserverlib.PublicRoom{},
|
||||||
}
|
}
|
||||||
var limit int16
|
var limit int64
|
||||||
var offset int64
|
var offset int64
|
||||||
limit = request.Limit
|
limit = request.Limit
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
|
|
@ -115,7 +124,7 @@ func publicRooms(
|
||||||
|
|
||||||
var rooms []gomatrixserverlib.PublicRoom
|
var rooms []gomatrixserverlib.PublicRoom
|
||||||
if request.Since == "" {
|
if request.Since == "" {
|
||||||
rooms = refreshPublicRoomCache(ctx, rsAPI, extRoomsProvider)
|
rooms = refreshPublicRoomCache(ctx, rsAPI, extRoomsProvider, request)
|
||||||
} else {
|
} else {
|
||||||
rooms = getPublicRoomsFromCache()
|
rooms = getPublicRoomsFromCache()
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +186,7 @@ func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSO
|
||||||
JSON: jsonerror.BadJSON("limit param is not a number"),
|
JSON: jsonerror.BadJSON("limit param is not a number"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.Limit = int16(limit)
|
request.Limit = int64(limit)
|
||||||
request.Since = httpReq.FormValue("since")
|
request.Since = httpReq.FormValue("since")
|
||||||
request.Server = httpReq.FormValue("server")
|
request.Server = httpReq.FormValue("server")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -205,7 +214,7 @@ func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSO
|
||||||
// limit=3&since=6 => G (prev='3', next='')
|
// limit=3&since=6 => G (prev='3', next='')
|
||||||
//
|
//
|
||||||
// A value of '-1' for prev/next indicates no position.
|
// A value of '-1' for prev/next indicates no position.
|
||||||
func sliceInto(slice []gomatrixserverlib.PublicRoom, since int64, limit int16) (subset []gomatrixserverlib.PublicRoom, prev, next int) {
|
func sliceInto(slice []gomatrixserverlib.PublicRoom, since int64, limit int64) (subset []gomatrixserverlib.PublicRoom, prev, next int) {
|
||||||
prev = -1
|
prev = -1
|
||||||
next = -1
|
next = -1
|
||||||
|
|
||||||
|
|
@ -231,6 +240,7 @@ func sliceInto(slice []gomatrixserverlib.PublicRoom, since int64, limit int16) (
|
||||||
|
|
||||||
func refreshPublicRoomCache(
|
func refreshPublicRoomCache(
|
||||||
ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, extRoomsProvider api.ExtraPublicRoomsProvider,
|
ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, extRoomsProvider api.ExtraPublicRoomsProvider,
|
||||||
|
request PublicRoomReq,
|
||||||
) []gomatrixserverlib.PublicRoom {
|
) []gomatrixserverlib.PublicRoom {
|
||||||
cacheMu.Lock()
|
cacheMu.Lock()
|
||||||
defer cacheMu.Unlock()
|
defer cacheMu.Unlock()
|
||||||
|
|
@ -239,8 +249,17 @@ func refreshPublicRoomCache(
|
||||||
extraRooms = extRoomsProvider.Rooms()
|
extraRooms = extRoomsProvider.Rooms()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is only here to make Sytest happy, for now.
|
||||||
|
ns := strings.Split(request.NetworkID, "|")
|
||||||
|
if len(ns) == 2 {
|
||||||
|
request.NetworkID = ns[1]
|
||||||
|
}
|
||||||
|
|
||||||
var queryRes roomserverAPI.QueryPublishedRoomsResponse
|
var queryRes roomserverAPI.QueryPublishedRoomsResponse
|
||||||
err := rsAPI.QueryPublishedRooms(ctx, &roomserverAPI.QueryPublishedRoomsRequest{}, &queryRes)
|
err := rsAPI.QueryPublishedRooms(ctx, &roomserverAPI.QueryPublishedRoomsRequest{
|
||||||
|
NetworkID: request.NetworkID,
|
||||||
|
IncludeAllNetworks: request.IncludeAllNetworks,
|
||||||
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("QueryPublishedRooms failed")
|
util.GetLogger(ctx).WithError(err).Error("QueryPublishedRooms failed")
|
||||||
return publicRoomsCache
|
return publicRoomsCache
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func TestSliceInto(t *testing.T) {
|
||||||
slice := []gomatrixserverlib.PublicRoom{
|
slice := []gomatrixserverlib.PublicRoom{
|
||||||
pubRoom("a"), pubRoom("b"), pubRoom("c"), pubRoom("d"), pubRoom("e"), pubRoom("f"), pubRoom("g"),
|
pubRoom("a"), pubRoom("b"), pubRoom("c"), pubRoom("d"), pubRoom("e"), pubRoom("f"), pubRoom("g"),
|
||||||
}
|
}
|
||||||
limit := int16(3)
|
limit := int64(3)
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
since int64
|
since int64
|
||||||
wantPrev int
|
wantPrev int
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
// Copyright 2019 Alex Chen
|
|
||||||
//
|
|
||||||
// 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/jsonerror"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type getEventRequest struct {
|
|
||||||
req *http.Request
|
|
||||||
device *userapi.Device
|
|
||||||
roomID string
|
|
||||||
eventID string
|
|
||||||
cfg *config.ClientAPI
|
|
||||||
requestedEvent *gomatrixserverlib.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEvent implements GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}
|
|
||||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-rooms-roomid-event-eventid
|
|
||||||
func GetEvent(
|
|
||||||
req *http.Request,
|
|
||||||
device *userapi.Device,
|
|
||||||
roomID string,
|
|
||||||
eventID string,
|
|
||||||
cfg *config.ClientAPI,
|
|
||||||
rsAPI api.ClientRoomserverAPI,
|
|
||||||
) util.JSONResponse {
|
|
||||||
eventsReq := api.QueryEventsByIDRequest{
|
|
||||||
EventIDs: []string{eventID},
|
|
||||||
}
|
|
||||||
var eventsResp api.QueryEventsByIDResponse
|
|
||||||
err := rsAPI.QueryEventsByID(req.Context(), &eventsReq, &eventsResp)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryEventsByID failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(eventsResp.Events) == 0 {
|
|
||||||
// Event not found locally
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedEvent := eventsResp.Events[0].Event
|
|
||||||
|
|
||||||
r := getEventRequest{
|
|
||||||
req: req,
|
|
||||||
device: device,
|
|
||||||
roomID: roomID,
|
|
||||||
eventID: eventID,
|
|
||||||
cfg: cfg,
|
|
||||||
requestedEvent: requestedEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
stateReq := api.QueryStateAfterEventsRequest{
|
|
||||||
RoomID: r.requestedEvent.RoomID(),
|
|
||||||
PrevEventIDs: r.requestedEvent.PrevEventIDs(),
|
|
||||||
StateToFetch: []gomatrixserverlib.StateKeyTuple{{
|
|
||||||
EventType: gomatrixserverlib.MRoomMember,
|
|
||||||
StateKey: device.UserID,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
var stateResp api.QueryStateAfterEventsResponse
|
|
||||||
if err := rsAPI.QueryStateAfterEvents(req.Context(), &stateReq, &stateResp); err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("queryAPI.QueryStateAfterEvents failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stateResp.RoomExists {
|
|
||||||
util.GetLogger(req.Context()).Errorf("Expected to find room for event %s but failed", r.requestedEvent.EventID())
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stateResp.PrevEventsExist {
|
|
||||||
// Missing some events locally; stateResp.StateEvents unavailable.
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var appService *config.ApplicationService
|
|
||||||
if device.AppserviceID != "" {
|
|
||||||
for _, as := range cfg.Derived.ApplicationServices {
|
|
||||||
if as.ID == device.AppserviceID {
|
|
||||||
appService = &as
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, stateEvent := range stateResp.StateEvents {
|
|
||||||
if appService != nil {
|
|
||||||
if !appService.IsInterestedInUserID(*stateEvent.StateKey()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if !stateEvent.StateKeyEquals(device.UserID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
membership, err := stateEvent.Membership()
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("stateEvent.Membership failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
if membership == gomatrixserverlib.Join {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: gomatrixserverlib.ToClientEvent(r.requestedEvent, gomatrixserverlib.FormatAll),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
JSON: jsonerror.NotFound("The event was not found or you do not have permission to read this event"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
clientapi/routing/joined_rooms.go
Normal file
52
clientapi/routing/joined_rooms.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2022 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/util"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type getJoinedRoomsResponse struct {
|
||||||
|
JoinedRooms []string `json:"joined_rooms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJoinedRooms(
|
||||||
|
req *http.Request,
|
||||||
|
device *userapi.Device,
|
||||||
|
rsAPI api.ClientRoomserverAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var res api.QueryRoomsForUserResponse
|
||||||
|
err := rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
|
||||||
|
UserID: device.UserID,
|
||||||
|
WantMembership: "join",
|
||||||
|
}, &res)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
if res.RoomIDs == nil {
|
||||||
|
res.RoomIDs = []string{}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: getJoinedRoomsResponse{res.RoomIDs},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,11 +19,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/keyserver/api"
|
"github.com/matrix-org/dendrite/keyserver/api"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type uploadKeysRequest struct {
|
type uploadKeysRequest struct {
|
||||||
|
|
@ -77,7 +78,6 @@ func UploadKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *userapi.Devi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyCount := make(map[string]int)
|
keyCount := make(map[string]int)
|
||||||
// we only return key counts when the client uploads OTKs
|
|
||||||
if len(uploadRes.OneTimeKeyCounts) > 0 {
|
if len(uploadRes.OneTimeKeyCounts) > 0 {
|
||||||
keyCount = uploadRes.OneTimeKeyCounts[0].KeyCount
|
keyCount = uploadRes.OneTimeKeyCounts[0].KeyCount
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +99,11 @@ func (r *queryKeysRequest) GetTimeout() time.Duration {
|
||||||
if r.Timeout == 0 {
|
if r.Timeout == 0 {
|
||||||
return 10 * time.Second
|
return 10 * time.Second
|
||||||
}
|
}
|
||||||
return time.Duration(r.Timeout) * time.Millisecond
|
timeout := time.Duration(r.Timeout) * time.Millisecond
|
||||||
|
if timeout > time.Second*20 {
|
||||||
|
timeout = time.Second * 20
|
||||||
|
}
|
||||||
|
return timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *userapi.Device) util.JSONResponse {
|
func QueryKeys(req *http.Request, keyAPI api.ClientKeyAPI, device *userapi.Device) util.JSONResponse {
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ func Login(
|
||||||
return *authErr
|
return *authErr
|
||||||
}
|
}
|
||||||
// make a device/access token
|
// make a device/access token
|
||||||
authErr2 := completeAuth(req.Context(), cfg.Matrix.ServerName, userAPI, login, req.RemoteAddr, req.UserAgent())
|
authErr2 := completeAuth(req.Context(), cfg.Matrix, userAPI, login, req.RemoteAddr, req.UserAgent())
|
||||||
cleanup(req.Context(), &authErr2)
|
cleanup(req.Context(), &authErr2)
|
||||||
return authErr2
|
return authErr2
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +136,7 @@ func Login(
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeAuth(
|
func completeAuth(
|
||||||
ctx context.Context, serverName gomatrixserverlib.ServerName, userAPI userapi.ClientUserAPI, login *auth.Login,
|
ctx context.Context, cfg *config.Global, userAPI userapi.ClientUserAPI, login *auth.Login,
|
||||||
ipAddr, userAgent string,
|
ipAddr, userAgent string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
token, err := auth.GenerateAccessToken()
|
token, err := auth.GenerateAccessToken()
|
||||||
|
|
@ -145,7 +145,7 @@ func completeAuth(
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
localpart, err := userutil.ParseUsernameParam(login.Username(), &serverName)
|
localpart, serverName, err := userutil.ParseUsernameParam(login.Username(), cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("auth.ParseUsernameParam failed")
|
util.GetLogger(ctx).WithError(err).Error("auth.ParseUsernameParam failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
|
|
|
||||||
|
|
@ -105,12 +105,13 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverName := device.UserDomain()
|
||||||
if err = roomserverAPI.SendEvents(
|
if err = roomserverAPI.SendEvents(
|
||||||
ctx, rsAPI,
|
ctx, rsAPI,
|
||||||
roomserverAPI.KindNew,
|
roomserverAPI.KindNew,
|
||||||
[]*gomatrixserverlib.HeaderedEvent{event.Event.Headered(roomVer)},
|
[]*gomatrixserverlib.HeaderedEvent{event.Event.Headered(roomVer)},
|
||||||
cfg.Matrix.ServerName,
|
serverName,
|
||||||
cfg.Matrix.ServerName,
|
serverName,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -271,7 +272,7 @@ func sendInvite(
|
||||||
Event: event,
|
Event: event,
|
||||||
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
|
||||||
RoomVersion: event.RoomVersion,
|
RoomVersion: event.RoomVersion,
|
||||||
SendAsServer: string(cfg.Matrix.ServerName),
|
SendAsServer: string(device.UserDomain()),
|
||||||
}, &inviteRes); err != nil {
|
}, &inviteRes); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -341,7 +342,7 @@ func loadProfile(
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile *authtypes.Profile
|
var profile *authtypes.Profile
|
||||||
if serverName == cfg.Matrix.ServerName {
|
if cfg.Matrix.IsLocalServerName(serverName) {
|
||||||
profile, err = appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
|
profile, err = appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
|
||||||
} else {
|
} else {
|
||||||
profile = &authtypes.Profile{}
|
profile = &authtypes.Profile{}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ func CreateOpenIDToken(
|
||||||
JSON: openIDTokenResponse{
|
JSON: openIDTokenResponse{
|
||||||
AccessToken: response.Token.Token,
|
AccessToken: response.Token.Token,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
MatrixServerName: string(cfg.Matrix.ServerName),
|
MatrixServerName: string(device.UserDomain()),
|
||||||
ExpiresIn: response.Token.ExpiresAtMS / 1000, // convert ms to s
|
ExpiresIn: response.Token.ExpiresAtMS / 1000, // convert ms to s
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
|
@ -27,7 +29,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -112,12 +113,19 @@ func SetAvatarURL(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("userID does not belong to a locally configured domain"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
evTime, err := httputil.ParseTSParam(req)
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -126,63 +134,26 @@ func SetAvatarURL(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &userapi.QueryProfileResponse{}
|
|
||||||
err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
|
|
||||||
UserID: userID,
|
|
||||||
}, res)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
oldProfile := &authtypes.Profile{
|
|
||||||
Localpart: localpart,
|
|
||||||
DisplayName: res.DisplayName,
|
|
||||||
AvatarURL: res.AvatarURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
setRes := &userapi.PerformSetAvatarURLResponse{}
|
setRes := &userapi.PerformSetAvatarURLResponse{}
|
||||||
if err = profileAPI.SetAvatarURL(req.Context(), &userapi.PerformSetAvatarURLRequest{
|
if err = profileAPI.SetAvatarURL(req.Context(), &userapi.PerformSetAvatarURLRequest{
|
||||||
Localpart: localpart,
|
Localpart: localpart,
|
||||||
AvatarURL: r.AvatarURL,
|
ServerName: domain,
|
||||||
|
AvatarURL: r.AvatarURL,
|
||||||
}, setRes); err != nil {
|
}, setRes); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
|
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
// No need to build new membership events, since nothing changed
|
||||||
var roomsRes api.QueryRoomsForUserResponse
|
if !setRes.Changed {
|
||||||
err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
|
|
||||||
UserID: device.UserID,
|
|
||||||
WantMembership: "join",
|
|
||||||
}, &roomsRes)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
|
|
||||||
newProfile := authtypes.Profile{
|
|
||||||
Localpart: localpart,
|
|
||||||
DisplayName: oldProfile.DisplayName,
|
|
||||||
AvatarURL: r.AvatarURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
events, err := buildMembershipEvents(
|
|
||||||
req.Context(), roomsRes.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
|
|
||||||
)
|
|
||||||
switch e := err.(type) {
|
|
||||||
case nil:
|
|
||||||
case gomatrixserverlib.BadJSONError:
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusOK,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: struct{}{},
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, true); err != nil {
|
response, err := updateProfile(req.Context(), rsAPI, device, setRes.Profile, userID, cfg, evTime)
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
|
if err != nil {
|
||||||
return jsonerror.InternalServerError()
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -241,12 +212,19 @@ func SetDisplayName(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
localpart, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("userID does not belong to a locally configured domain"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
evTime, err := httputil.ParseTSParam(req)
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -255,47 +233,58 @@ func SetDisplayName(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pRes := &userapi.QueryProfileResponse{}
|
profileRes := &userapi.PerformUpdateDisplayNameResponse{}
|
||||||
err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
|
|
||||||
UserID: userID,
|
|
||||||
}, pRes)
|
|
||||||
if err != nil {
|
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
|
|
||||||
return jsonerror.InternalServerError()
|
|
||||||
}
|
|
||||||
oldProfile := &authtypes.Profile{
|
|
||||||
Localpart: localpart,
|
|
||||||
DisplayName: pRes.DisplayName,
|
|
||||||
AvatarURL: pRes.AvatarURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = profileAPI.SetDisplayName(req.Context(), &userapi.PerformUpdateDisplayNameRequest{
|
err = profileAPI.SetDisplayName(req.Context(), &userapi.PerformUpdateDisplayNameRequest{
|
||||||
Localpart: localpart,
|
Localpart: localpart,
|
||||||
|
ServerName: domain,
|
||||||
DisplayName: r.DisplayName,
|
DisplayName: r.DisplayName,
|
||||||
}, &struct{}{})
|
}, profileRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
|
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
// No need to build new membership events, since nothing changed
|
||||||
|
if !profileRes.Changed {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := updateProfile(req.Context(), rsAPI, device, profileRes.Profile, userID, cfg, evTime)
|
||||||
|
if err != nil {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateProfile(
|
||||||
|
ctx context.Context, rsAPI api.ClientRoomserverAPI, device *userapi.Device,
|
||||||
|
profile *authtypes.Profile,
|
||||||
|
userID string, cfg *config.ClientAPI, evTime time.Time,
|
||||||
|
) (util.JSONResponse, error) {
|
||||||
var res api.QueryRoomsForUserResponse
|
var res api.QueryRoomsForUserResponse
|
||||||
err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
|
err := rsAPI.QueryRoomsForUser(ctx, &api.QueryRoomsForUserRequest{
|
||||||
UserID: device.UserID,
|
UserID: device.UserID,
|
||||||
WantMembership: "join",
|
WantMembership: "join",
|
||||||
}, &res)
|
}, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
|
util.GetLogger(ctx).WithError(err).Error("QueryRoomsForUser failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
newProfile := authtypes.Profile{
|
_, domain, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
Localpart: localpart,
|
if err != nil {
|
||||||
DisplayName: r.DisplayName,
|
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.SplitID failed")
|
||||||
AvatarURL: oldProfile.AvatarURL,
|
return jsonerror.InternalServerError(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := buildMembershipEvents(
|
events, err := buildMembershipEvents(
|
||||||
req.Context(), res.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
|
ctx, res.RoomIDs, *profile, userID, cfg, evTime, rsAPI,
|
||||||
)
|
)
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
|
|
@ -303,21 +292,17 @@ func SetDisplayName(
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON(e.Error()),
|
JSON: jsonerror.BadJSON(e.Error()),
|
||||||
}
|
}, e
|
||||||
default:
|
default:
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("buildMembershipEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("buildMembershipEvents failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError(), e
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.SendEvents(req.Context(), rsAPI, api.KindNew, events, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, true); err != nil {
|
if err := api.SendEvents(ctx, rsAPI, api.KindNew, events, domain, domain, nil, true); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Error("SendEvents failed")
|
util.GetLogger(ctx).WithError(err).Error("SendEvents failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError(), err
|
||||||
}
|
|
||||||
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
JSON: struct{}{},
|
|
||||||
}
|
}
|
||||||
|
return util.JSONResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getProfile gets the full profile of a user by querying the database or a
|
// getProfile gets the full profile of a user by querying the database or a
|
||||||
|
|
@ -335,7 +320,7 @@ func getProfile(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if domain != cfg.Matrix.ServerName {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
profile, fedErr := federation.LookupProfile(ctx, domain, userID, "")
|
profile, fedErr := federation.LookupProfile(ctx, domain, userID, "")
|
||||||
if fedErr != nil {
|
if fedErr != nil {
|
||||||
if x, ok := fedErr.(gomatrix.HTTPError); ok {
|
if x, ok := fedErr.(gomatrix.HTTPError); ok {
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,22 @@
|
||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
|
"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, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
|
func SetReceipt(req *http.Request, userAPI api.ClientUserAPI, syncProducer *producers.SyncAPIProducer, device *userapi.Device, roomID, receiptType, eventID string) util.JSONResponse {
|
||||||
timestamp := gomatrixserverlib.AsTimestamp(time.Now())
|
timestamp := gomatrixserverlib.AsTimestamp(time.Now())
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"roomID": roomID,
|
"roomID": roomID,
|
||||||
|
|
@ -37,13 +40,32 @@ func SetReceipt(req *http.Request, syncProducer *producers.SyncAPIProducer, devi
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
}).Debug("Setting receipt")
|
}).Debug("Setting receipt")
|
||||||
|
|
||||||
// currently only m.read is accepted
|
switch receiptType {
|
||||||
if receiptType != "m.read" {
|
case "m.read", "m.read.private":
|
||||||
return util.MessageResponse(400, fmt.Sprintf("receipt type must be m.read not '%s'", receiptType))
|
if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil {
|
||||||
}
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := syncProducer.SendReceipt(req.Context(), device.UserID, roomID, eventID, receiptType, timestamp); err != nil {
|
case "m.fully_read":
|
||||||
return util.ErrorResponse(err)
|
data, err := json.Marshal(fullyReadEvent{EventID: eventID})
|
||||||
|
if err != nil {
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
|
||||||
|
dataReq := api.InputAccountDataRequest{
|
||||||
|
UserID: device.UserID,
|
||||||
|
DataType: "m.fully_read",
|
||||||
|
RoomID: roomID,
|
||||||
|
AccountData: data,
|
||||||
|
}
|
||||||
|
dataRes := api.InputAccountDataResponse{}
|
||||||
|
if err := userAPI.InputAccountData(req.Context(), &dataReq, &dataRes); err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("userAPI.InputAccountData failed")
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return util.MessageResponse(400, fmt.Sprintf("Receipt type '%s' not known", receiptType))
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
|
|
@ -26,8 +29,6 @@ import (
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
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/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type redactionContent struct {
|
type redactionContent struct {
|
||||||
|
|
@ -51,7 +52,7 @@ func SendRedaction(
|
||||||
|
|
||||||
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); ok {
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
||||||
return *res
|
return *res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +131,8 @@ func SendRedaction(
|
||||||
JSON: jsonerror.NotFound("Room does not exist"),
|
JSON: jsonerror.NotFound("Room does not exist"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = roomserverAPI.SendEvents(context.Background(), rsAPI, roomserverAPI.KindNew, []*gomatrixserverlib.HeaderedEvent{e}, cfg.Matrix.ServerName, cfg.Matrix.ServerName, nil, false); err != nil {
|
domain := device.UserDomain()
|
||||||
|
if err = roomserverAPI.SendEvents(context.Background(), rsAPI, roomserverAPI.KindNew, []*gomatrixserverlib.HeaderedEvent{e}, domain, domain, nil, false); err != nil {
|
||||||
util.GetLogger(req.Context()).WithError(err).Errorf("failed to SendEvents")
|
util.GetLogger(req.Context()).WithError(err).Errorf("failed to SendEvents")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +146,7 @@ func SendRedaction(
|
||||||
|
|
||||||
// Add response to transactionsCache
|
// Add response to transactionsCache
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
txnCache.AddTransaction(device.AccessToken, *txnID, &res)
|
txnCache.AddTransaction(device.AccessToken, *txnID, req.URL, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -336,6 +337,7 @@ func validateRecaptcha(
|
||||||
response string,
|
response string,
|
||||||
clientip string,
|
clientip string,
|
||||||
) *util.JSONResponse {
|
) *util.JSONResponse {
|
||||||
|
ip, _, _ := net.SplitHostPort(clientip)
|
||||||
if !cfg.RecaptchaEnabled {
|
if !cfg.RecaptchaEnabled {
|
||||||
return &util.JSONResponse{
|
return &util.JSONResponse{
|
||||||
Code: http.StatusConflict,
|
Code: http.StatusConflict,
|
||||||
|
|
@ -355,7 +357,7 @@ func validateRecaptcha(
|
||||||
url.Values{
|
url.Values{
|
||||||
"secret": {cfg.RecaptchaPrivateKey},
|
"secret": {cfg.RecaptchaPrivateKey},
|
||||||
"response": {response},
|
"response": {response},
|
||||||
"remoteip": {clientip},
|
"remoteip": {ip},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -412,7 +414,7 @@ func UserIDIsWithinApplicationServiceNamespace(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if domain != cfg.Matrix.ServerName {
|
if !cfg.Matrix.IsLocalServerName(domain) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/api"
|
"github.com/matrix-org/dendrite/clientapi/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth"
|
"github.com/matrix-org/dendrite/clientapi/auth"
|
||||||
|
|
@ -35,11 +41,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
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/util"
|
|
||||||
"github.com/nats-io/nats.go"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -81,6 +82,7 @@ func Setup(
|
||||||
|
|
||||||
unstableFeatures := map[string]bool{
|
unstableFeatures := map[string]bool{
|
||||||
"org.matrix.e2e_cross_signing": true,
|
"org.matrix.e2e_cross_signing": true,
|
||||||
|
"org.matrix.msc2285.stable": true,
|
||||||
}
|
}
|
||||||
for _, msc := range cfg.MSCs.MSCs {
|
for _, msc := range cfg.MSCs.MSCs {
|
||||||
unstableFeatures["org.matrix."+msc] = true
|
unstableFeatures["org.matrix."+msc] = true
|
||||||
|
|
@ -173,10 +175,28 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}",
|
||||||
|
httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return AdminDownloadState(req, cfg, device, rsAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
dendriteAdminRouter.Handle("/admin/fulltext/reindex",
|
||||||
|
httputil.MakeAdminAPI("admin_fultext_reindex", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return AdminReindex(req, cfg, device, natsClient)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
dendriteAdminRouter.Handle("/admin/refreshDevices/{userID}",
|
||||||
|
httputil.MakeAdminAPI("admin_refresh_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return AdminMarkAsStale(req, cfg, keyAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
// server notifications
|
// server notifications
|
||||||
if cfg.Matrix.ServerNotices.Enabled {
|
if cfg.Matrix.ServerNotices.Enabled {
|
||||||
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(ctx, userAPI, cfg)
|
serverNotificationSender, err := getSenderDevice(ctx, rsAPI, userAPI, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
|
logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
|
||||||
}
|
}
|
||||||
|
|
@ -366,15 +386,6 @@ func Setup(
|
||||||
nil, cfg, rsAPI, transactionsCache)
|
nil, cfg, rsAPI, transactionsCache)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
v3mux.Handle("/rooms/{roomID}/event/{eventID}",
|
|
||||||
httputil.MakeAuthAPI("rooms_get_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 GetEvent(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI)
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
v3mux.Handle("/rooms/{roomID}/state", httputil.MakeAuthAPI("room_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -487,7 +498,7 @@ func Setup(
|
||||||
return GetVisibility(req, rsAPI, vars["roomID"])
|
return GetVisibility(req, rsAPI, vars["roomID"])
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
// TODO: Add AS support
|
|
||||||
v3mux.Handle("/directory/list/room/{roomID}",
|
v3mux.Handle("/directory/list/room/{roomID}",
|
||||||
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -497,6 +508,27 @@ func Setup(
|
||||||
return SetVisibility(req, rsAPI, device, vars["roomID"])
|
return SetVisibility(req, rsAPI, device, vars["roomID"])
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
|
||||||
|
httputil.MakeAuthAPI("directory_list", 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 SetVisibilityAS(req, rsAPI, device, vars["networkID"], vars["roomID"])
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
// Undocumented endpoint
|
||||||
|
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
|
||||||
|
httputil.MakeAuthAPI("directory_list", 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 SetVisibilityAS(req, rsAPI, device, vars["networkID"], vars["roomID"])
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodDelete, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/publicRooms",
|
v3mux.Handle("/publicRooms",
|
||||||
httputil.MakeExternalAPI("public_rooms", func(req *http.Request) util.JSONResponse {
|
httputil.MakeExternalAPI("public_rooms", func(req *http.Request) util.JSONResponse {
|
||||||
return GetPostPublicRooms(req, rsAPI, extRoomsProvider, federation, cfg)
|
return GetPostPublicRooms(req, rsAPI, extRoomsProvider, federation, cfg)
|
||||||
|
|
@ -868,12 +900,50 @@ func Setup(
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/thirdparty/protocols",
|
v3mux.Handle("/thirdparty/protocols",
|
||||||
httputil.MakeExternalAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
|
httputil.MakeAuthAPI("thirdparty_protocols", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
// TODO: Return the third party protcols
|
return Protocols(req, asAPI, device, "")
|
||||||
return util.JSONResponse{
|
}),
|
||||||
Code: http.StatusOK,
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
JSON: struct{}{},
|
|
||||||
|
v3mux.Handle("/thirdparty/protocol/{protocolID}",
|
||||||
|
httputil.MakeAuthAPI("thirdparty_protocols", 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 Protocols(req, asAPI, device, vars["protocolID"])
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
v3mux.Handle("/thirdparty/user/{protocolID}",
|
||||||
|
httputil.MakeAuthAPI("thirdparty_user", 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 User(req, asAPI, device, vars["protocolID"], req.URL.Query())
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
v3mux.Handle("/thirdparty/user",
|
||||||
|
httputil.MakeAuthAPI("thirdparty_user", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return User(req, asAPI, device, "", req.URL.Query())
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
v3mux.Handle("/thirdparty/location/{protocolID}",
|
||||||
|
httputil.MakeAuthAPI("thirdparty_location", 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 Location(req, asAPI, device, vars["protocolID"], req.URL.Query())
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
v3mux.Handle("/thirdparty/location",
|
||||||
|
httputil.MakeAuthAPI("thirdparty_location", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
return Location(req, asAPI, device, "", req.URL.Query())
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
|
@ -976,26 +1046,6 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/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 GetMemberships(req, device, vars["roomID"], false, cfg, rsAPI)
|
|
||||||
}),
|
|
||||||
).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 GetMemberships(req, device, vars["roomID"], true, cfg, rsAPI)
|
|
||||||
}),
|
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
|
||||||
|
|
||||||
v3mux.Handle("/rooms/{roomID}/read_markers",
|
v3mux.Handle("/rooms/{roomID}/read_markers",
|
||||||
httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("rooms_read_markers", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
|
@ -1370,7 +1420,7 @@ func Setup(
|
||||||
return util.ErrorResponse(err)
|
return util.ErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return SetReceipt(req, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"])
|
return SetReceipt(req, userAPI, syncProducer, device, vars["roomId"], vars["receiptType"], vars["eventId"])
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
v3mux.Handle("/presence/{userId}/status",
|
v3mux.Handle("/presence/{userId}/status",
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func SendEvent(
|
||||||
|
|
||||||
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); ok {
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
||||||
return *res
|
return *res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +94,7 @@ func SendEvent(
|
||||||
// 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
|
||||||
|
domain := device.UserDomain()
|
||||||
mutex, _ := userRoomSendMutexes.LoadOrStore(roomID+userID, &sync.Mutex{})
|
mutex, _ := userRoomSendMutexes.LoadOrStore(roomID+userID, &sync.Mutex{})
|
||||||
mutex.(*sync.Mutex).Lock()
|
mutex.(*sync.Mutex).Lock()
|
||||||
defer mutex.(*sync.Mutex).Unlock()
|
defer mutex.(*sync.Mutex).Unlock()
|
||||||
|
|
@ -185,8 +186,8 @@ func SendEvent(
|
||||||
[]*gomatrixserverlib.HeaderedEvent{
|
[]*gomatrixserverlib.HeaderedEvent{
|
||||||
e.Headered(verRes.RoomVersion),
|
e.Headered(verRes.RoomVersion),
|
||||||
},
|
},
|
||||||
cfg.Matrix.ServerName,
|
domain,
|
||||||
cfg.Matrix.ServerName,
|
domain,
|
||||||
txnAndSessionID,
|
txnAndSessionID,
|
||||||
false,
|
false,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -206,7 +207,7 @@ func SendEvent(
|
||||||
}
|
}
|
||||||
// Add response to transactionsCache
|
// Add response to transactionsCache
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
txnCache.AddTransaction(device.AccessToken, *txnID, &res)
|
txnCache.AddTransaction(device.AccessToken, *txnID, req.URL, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take a note of how long it took to generate the event vs submit
|
// Take a note of how long it took to generate the event vs submit
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/internal/transactions"
|
"github.com/matrix-org/dendrite/internal/transactions"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
// SendToDevice handles PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}
|
||||||
|
|
@ -33,7 +34,7 @@ func SendToDevice(
|
||||||
eventType string, txnID *string,
|
eventType string, txnID *string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID); ok {
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
||||||
return *res
|
return *res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +64,7 @@ func SendToDevice(
|
||||||
}
|
}
|
||||||
|
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
txnCache.AddTransaction(device.AccessToken, *txnID, &res)
|
txnCache.AddTransaction(device.AccessToken, *txnID, req.URL, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/version"
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/gomatrixserverlib/tokens"
|
"github.com/matrix-org/gomatrixserverlib/tokens"
|
||||||
|
|
@ -29,6 +28,8 @@ import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
|
|
||||||
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
|
@ -73,7 +74,7 @@ func SendServerNotice(
|
||||||
|
|
||||||
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); ok {
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
||||||
return *res
|
return *res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,7 +252,7 @@ func SendServerNotice(
|
||||||
}
|
}
|
||||||
// Add response to transactionsCache
|
// Add response to transactionsCache
|
||||||
if txnID != nil {
|
if txnID != nil {
|
||||||
txnCache.AddTransaction(device.AccessToken, *txnID, &res)
|
txnCache.AddTransaction(device.AccessToken, *txnID, req.URL, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take a note of how long it took to generate the event vs submit
|
// Take a note of how long it took to generate the event vs submit
|
||||||
|
|
@ -276,6 +277,7 @@ func (r sendServerNoticeRequest) valid() (ok bool) {
|
||||||
// It returns an userapi.Device, which is used for building the event
|
// It returns an userapi.Device, which is used for building the event
|
||||||
func getSenderDevice(
|
func getSenderDevice(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
rsAPI api.ClientRoomserverAPI,
|
||||||
userAPI userapi.ClientUserAPI,
|
userAPI userapi.ClientUserAPI,
|
||||||
cfg *config.ClientAPI,
|
cfg *config.ClientAPI,
|
||||||
) (*userapi.Device, error) {
|
) (*userapi.Device, error) {
|
||||||
|
|
@ -290,16 +292,32 @@ func getSenderDevice(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the avatarurl for the user
|
// Set the avatarurl for the user
|
||||||
res := &userapi.PerformSetAvatarURLResponse{}
|
avatarRes := &userapi.PerformSetAvatarURLResponse{}
|
||||||
if err = userAPI.SetAvatarURL(ctx, &userapi.PerformSetAvatarURLRequest{
|
if err = userAPI.SetAvatarURL(ctx, &userapi.PerformSetAvatarURLRequest{
|
||||||
Localpart: cfg.Matrix.ServerNotices.LocalPart,
|
Localpart: cfg.Matrix.ServerNotices.LocalPart,
|
||||||
AvatarURL: cfg.Matrix.ServerNotices.AvatarURL,
|
AvatarURL: cfg.Matrix.ServerNotices.AvatarURL,
|
||||||
}, res); err != nil {
|
}, avatarRes); err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("userAPI.SetAvatarURL failed")
|
util.GetLogger(ctx).WithError(err).Error("userAPI.SetAvatarURL failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profile := avatarRes.Profile
|
||||||
|
|
||||||
|
// Set the displayname for the user
|
||||||
|
displayNameRes := &userapi.PerformUpdateDisplayNameResponse{}
|
||||||
|
if err = userAPI.SetDisplayName(ctx, &userapi.PerformUpdateDisplayNameRequest{
|
||||||
|
Localpart: cfg.Matrix.ServerNotices.LocalPart,
|
||||||
|
DisplayName: cfg.Matrix.ServerNotices.DisplayName,
|
||||||
|
}, displayNameRes); err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("userAPI.SetDisplayName failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayNameRes.Changed {
|
||||||
|
profile.DisplayName = cfg.Matrix.ServerNotices.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we got existing devices
|
// Check if we got existing devices
|
||||||
deviceRes := &userapi.QueryDevicesResponse{}
|
deviceRes := &userapi.QueryDevicesResponse{}
|
||||||
err = userAPI.QueryDevices(ctx, &userapi.QueryDevicesRequest{
|
err = userAPI.QueryDevices(ctx, &userapi.QueryDevicesRequest{
|
||||||
|
|
@ -309,7 +327,15 @@ func getSenderDevice(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've got an existing account, return the first device of it
|
||||||
if len(deviceRes.Devices) > 0 {
|
if len(deviceRes.Devices) > 0 {
|
||||||
|
// If there were changes to the profile, create a new membership event
|
||||||
|
if displayNameRes.Changed || avatarRes.Changed {
|
||||||
|
_, err = updateProfile(ctx, rsAPI, &deviceRes.Devices[0], profile, accRes.Account.UserID, cfg, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return &deviceRes.Devices[0], nil
|
return &deviceRes.Devices[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
106
clientapi/routing/thirdparty.go
Normal file
106
clientapi/routing/thirdparty.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2022 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"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protocols implements
|
||||||
|
//
|
||||||
|
// GET /_matrix/client/v3/thirdparty/protocols/{protocol}
|
||||||
|
// GET /_matrix/client/v3/thirdparty/protocols
|
||||||
|
func Protocols(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, device *api.Device, protocol string) util.JSONResponse {
|
||||||
|
resp := &appserviceAPI.ProtocolResponse{}
|
||||||
|
|
||||||
|
if err := asAPI.Protocols(req.Context(), &appserviceAPI.ProtocolRequest{Protocol: protocol}, resp); err != nil {
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
if !resp.Exists {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("The protocol is unknown."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if protocol != "" {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: resp.Protocols[protocol],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: resp.Protocols,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User implements
|
||||||
|
//
|
||||||
|
// GET /_matrix/client/v3/thirdparty/user
|
||||||
|
// GET /_matrix/client/v3/thirdparty/user/{protocol}
|
||||||
|
func User(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, device *api.Device, protocol string, params url.Values) util.JSONResponse {
|
||||||
|
resp := &appserviceAPI.UserResponse{}
|
||||||
|
|
||||||
|
params.Del("access_token")
|
||||||
|
if err := asAPI.User(req.Context(), &appserviceAPI.UserRequest{
|
||||||
|
Protocol: protocol,
|
||||||
|
Params: params.Encode(),
|
||||||
|
}, resp); err != nil {
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
if !resp.Exists {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("The Matrix User ID was not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: resp.Users,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location implements
|
||||||
|
//
|
||||||
|
// GET /_matrix/client/v3/thirdparty/location
|
||||||
|
// GET /_matrix/client/v3/thirdparty/location/{protocol}
|
||||||
|
func Location(req *http.Request, asAPI appserviceAPI.AppServiceInternalAPI, device *api.Device, protocol string, params url.Values) util.JSONResponse {
|
||||||
|
resp := &appserviceAPI.LocationResponse{}
|
||||||
|
|
||||||
|
params.Del("access_token")
|
||||||
|
if err := asAPI.Locations(req.Context(), &appserviceAPI.LocationRequest{
|
||||||
|
Protocol: protocol,
|
||||||
|
Params: params.Encode(),
|
||||||
|
}, resp); err != nil {
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
}
|
||||||
|
if !resp.Exists {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("No portal rooms were found."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: resp.Locations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -215,7 +215,7 @@ func queryIDServerStoreInvite(
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile *authtypes.Profile
|
var profile *authtypes.Profile
|
||||||
if serverName == cfg.Matrix.ServerName {
|
if cfg.Matrix.IsLocalServerName(serverName) {
|
||||||
res := &userapi.QueryProfileResponse{}
|
res := &userapi.QueryProfileResponse{}
|
||||||
err = userAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: device.UserID}, res)
|
err = userAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: device.UserID}, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,23 +25,23 @@ import (
|
||||||
// usernameParam can either be a user ID or just the localpart/username.
|
// usernameParam can either be a user ID or just the localpart/username.
|
||||||
// If serverName is passed, it is verified against the domain obtained from usernameParam (if present)
|
// If serverName is passed, it is verified against the domain obtained from usernameParam (if present)
|
||||||
// Returns error in case of invalid usernameParam.
|
// Returns error in case of invalid usernameParam.
|
||||||
func ParseUsernameParam(usernameParam string, expectedServerName *gomatrixserverlib.ServerName) (string, error) {
|
func ParseUsernameParam(usernameParam string, cfg *config.Global) (string, gomatrixserverlib.ServerName, error) {
|
||||||
localpart := usernameParam
|
localpart := usernameParam
|
||||||
|
|
||||||
if strings.HasPrefix(usernameParam, "@") {
|
if strings.HasPrefix(usernameParam, "@") {
|
||||||
lp, domain, err := gomatrixserverlib.SplitID('@', usernameParam)
|
lp, domain, err := gomatrixserverlib.SplitID('@', usernameParam)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("invalid username")
|
return "", "", errors.New("invalid username")
|
||||||
}
|
}
|
||||||
|
|
||||||
if expectedServerName != nil && domain != *expectedServerName {
|
if !cfg.IsLocalServerName(domain) {
|
||||||
return "", errors.New("user ID does not belong to this server")
|
return "", "", errors.New("user ID does not belong to this server")
|
||||||
}
|
}
|
||||||
|
|
||||||
localpart = lp
|
return lp, domain, nil
|
||||||
}
|
}
|
||||||
return localpart, nil
|
return localpart, cfg.ServerName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeUserID generates user ID from localpart & server name
|
// MakeUserID generates user ID from localpart & server name
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ package userutil
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,7 +29,11 @@ var (
|
||||||
|
|
||||||
// TestGoodUserID checks that correct localpart is returned for a valid user ID.
|
// TestGoodUserID checks that correct localpart is returned for a valid user ID.
|
||||||
func TestGoodUserID(t *testing.T) {
|
func TestGoodUserID(t *testing.T) {
|
||||||
lp, err := ParseUsernameParam(goodUserID, &serverName)
|
cfg := &config.Global{
|
||||||
|
ServerName: serverName,
|
||||||
|
}
|
||||||
|
|
||||||
|
lp, _, err := ParseUsernameParam(goodUserID, cfg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("User ID Parsing failed for ", goodUserID, " with error: ", err.Error())
|
t.Error("User ID Parsing failed for ", goodUserID, " with error: ", err.Error())
|
||||||
|
|
@ -41,7 +46,11 @@ func TestGoodUserID(t *testing.T) {
|
||||||
|
|
||||||
// TestWithLocalpartOnly checks that localpart is returned when usernameParam contains only localpart.
|
// TestWithLocalpartOnly checks that localpart is returned when usernameParam contains only localpart.
|
||||||
func TestWithLocalpartOnly(t *testing.T) {
|
func TestWithLocalpartOnly(t *testing.T) {
|
||||||
lp, err := ParseUsernameParam(localpart, &serverName)
|
cfg := &config.Global{
|
||||||
|
ServerName: serverName,
|
||||||
|
}
|
||||||
|
|
||||||
|
lp, _, err := ParseUsernameParam(localpart, cfg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("User ID Parsing failed for ", localpart, " with error: ", err.Error())
|
t.Error("User ID Parsing failed for ", localpart, " with error: ", err.Error())
|
||||||
|
|
@ -54,7 +63,11 @@ func TestWithLocalpartOnly(t *testing.T) {
|
||||||
|
|
||||||
// TestIncorrectDomain checks for error when there's server name mismatch.
|
// TestIncorrectDomain checks for error when there's server name mismatch.
|
||||||
func TestIncorrectDomain(t *testing.T) {
|
func TestIncorrectDomain(t *testing.T) {
|
||||||
_, err := ParseUsernameParam(goodUserID, &invalidServerName)
|
cfg := &config.Global{
|
||||||
|
ServerName: invalidServerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := ParseUsernameParam(goodUserID, cfg)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Invalid Domain should return an error")
|
t.Error("Invalid Domain should return an error")
|
||||||
|
|
@ -63,7 +76,11 @@ func TestIncorrectDomain(t *testing.T) {
|
||||||
|
|
||||||
// TestBadUserID checks that ParseUsernameParam fails for invalid user ID
|
// TestBadUserID checks that ParseUsernameParam fails for invalid user ID
|
||||||
func TestBadUserID(t *testing.T) {
|
func TestBadUserID(t *testing.T) {
|
||||||
_, err := ParseUsernameParam(badUserID, &serverName)
|
cfg := &config.Global{
|
||||||
|
ServerName: serverName,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := ParseUsernameParam(badUserID, cfg)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Illegal User ID should return an error")
|
t.Error("Illegal User ID should return an error")
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ var (
|
||||||
pwdStdin = flag.Bool("passwordstdin", false, "Reads the password from stdin")
|
pwdStdin = flag.Bool("passwordstdin", false, "Reads the password from stdin")
|
||||||
isAdmin = flag.Bool("admin", false, "Create an admin account")
|
isAdmin = flag.Bool("admin", false, "Create an admin account")
|
||||||
resetPassword = flag.Bool("reset-password", false, "Deprecated")
|
resetPassword = flag.Bool("reset-password", false, "Deprecated")
|
||||||
serverURL = flag.String("url", "https://localhost:8448", "The URL to connect to.")
|
serverURL = flag.String("url", "http://localhost:8008", "The URL to connect to.")
|
||||||
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`)
|
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`)
|
||||||
timeout = flag.Duration("timeout", time.Second*30, "Timeout for the http client when connecting to the server")
|
timeout = flag.Duration("timeout", time.Second*30, "Timeout for the http client when connecting to the server")
|
||||||
)
|
)
|
||||||
|
|
@ -179,7 +179,10 @@ func sharedSecretRegister(sharedSecret, serverURL, localpart, password string, a
|
||||||
body, _ = io.ReadAll(regResp.Body)
|
body, _ = io.ReadAll(regResp.Body)
|
||||||
return "", fmt.Errorf(gjson.GetBytes(body, "error").Str)
|
return "", fmt.Errorf(gjson.GetBytes(body, "error").Str)
|
||||||
}
|
}
|
||||||
r, _ := io.ReadAll(regResp.Body)
|
r, err := io.ReadAll(regResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read response body (HTTP %d): %w", regResp.StatusCode, err)
|
||||||
|
}
|
||||||
|
|
||||||
return gjson.GetBytes(r, "access_token").Str, nil
|
return gjson.GetBytes(r, "access_token").Str, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
cmd/dendrite-demo-pinecone/README.md
Normal file
26
cmd/dendrite-demo-pinecone/README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Pinecone Demo
|
||||||
|
|
||||||
|
This is the Dendrite Pinecone demo! It's easy to get started.
|
||||||
|
|
||||||
|
To run the homeserver, start at the root of the Dendrite repository and run:
|
||||||
|
|
||||||
|
```
|
||||||
|
go run ./cmd/dendrite-demo-pinecone
|
||||||
|
```
|
||||||
|
|
||||||
|
To connect to the static Pinecone peer used by the mobile demos run:
|
||||||
|
|
||||||
|
```
|
||||||
|
go run ./cmd/dendrite-demo-pinecone -peer wss://pinecone.matrix.org/public
|
||||||
|
```
|
||||||
|
|
||||||
|
The following command line arguments are accepted:
|
||||||
|
|
||||||
|
* `-peer tcp://a.b.c.d:e` to specify a static Pinecone peer to connect to - you will need to supply this if you do not have another Pinecone node on your network
|
||||||
|
* `-port 12345` to specify a port to listen on for client connections
|
||||||
|
|
||||||
|
Then point your favourite Matrix client to the homeserver URL`http://localhost:8008` (or whichever `-port` you specified), create an account and log in.
|
||||||
|
|
||||||
|
If your peering connection is operational then you should see a `Connected TCP:` line in the log output. If not then try a different peer.
|
||||||
|
|
||||||
|
Once logged in, you should be able to open the room directory or join a room by its ID.
|
||||||
|
|
@ -54,8 +54,6 @@ import (
|
||||||
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
pineconeSessions "github.com/matrix-org/pinecone/sessions"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -89,6 +87,7 @@ func main() {
|
||||||
if configFlagSet {
|
if configFlagSet {
|
||||||
cfg = setup.ParseFlags(true)
|
cfg = setup.ParseFlags(true)
|
||||||
sk = cfg.Global.PrivateKey
|
sk = cfg.Global.PrivateKey
|
||||||
|
pk = sk.Public().(ed25519.PublicKey)
|
||||||
} else {
|
} else {
|
||||||
keyfile := filepath.Join(*instanceDir, *instanceName) + ".pem"
|
keyfile := filepath.Join(*instanceDir, *instanceName) + ".pem"
|
||||||
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
if _, err := os.Stat(keyfile); os.IsNotExist(err) {
|
||||||
|
|
@ -142,6 +141,9 @@ func main() {
|
||||||
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", filepath.Join(*instanceDir, *instanceName)))
|
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", filepath.Join(*instanceDir, *instanceName)))
|
||||||
cfg.ClientAPI.RegistrationDisabled = false
|
cfg.ClientAPI.RegistrationDisabled = false
|
||||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||||
|
cfg.MediaAPI.BasePath = config.Path(*instanceDir)
|
||||||
|
cfg.SyncAPI.Fulltext.Enabled = true
|
||||||
|
cfg.SyncAPI.Fulltext.IndexPath = config.Path(*instanceDir)
|
||||||
if err := cfg.Derive(); err != nil {
|
if err := cfg.Derive(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/test"
|
"github.com/matrix-org/dendrite/test"
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -134,6 +132,9 @@ func main() {
|
||||||
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", filepath.Join(*instanceDir, *instanceName)))
|
cfg.MSCs.Database.ConnectionString = config.DataSource(fmt.Sprintf("file:%s-mscs.db", filepath.Join(*instanceDir, *instanceName)))
|
||||||
cfg.ClientAPI.RegistrationDisabled = false
|
cfg.ClientAPI.RegistrationDisabled = false
|
||||||
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
cfg.ClientAPI.OpenRegistrationWithoutVerificationEnabled = true
|
||||||
|
cfg.MediaAPI.BasePath = config.Path(*instanceDir)
|
||||||
|
cfg.SyncAPI.Fulltext.Enabled = true
|
||||||
|
cfg.SyncAPI.Fulltext.IndexPath = config.Path(*instanceDir)
|
||||||
if err := cfg.Derive(); err != nil {
|
if err := cfg.Derive(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -27,9 +28,9 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
ironwoodtypes "github.com/Arceliar/ironwood/types"
|
ironwoodtypes "github.com/Arceliar/ironwood/types"
|
||||||
yggdrasilconfig "github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
yggdrasilcore "github.com/yggdrasil-network/yggdrasil-go/src/core"
|
yggdrasilcore "github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
yggdrasildefaults "github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
yggdrasilmulticast "github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
yggdrasilmulticast "github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
|
|
||||||
gologme "github.com/gologme/log"
|
gologme "github.com/gologme/log"
|
||||||
|
|
@ -37,7 +38,6 @@ import (
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
core *yggdrasilcore.Core
|
core *yggdrasilcore.Core
|
||||||
config *yggdrasilconfig.NodeConfig
|
|
||||||
multicast *yggdrasilmulticast.Multicast
|
multicast *yggdrasilmulticast.Multicast
|
||||||
log *gologme.Logger
|
log *gologme.Logger
|
||||||
utpSocket *utp.Socket
|
utpSocket *utp.Socket
|
||||||
|
|
@ -57,43 +57,52 @@ func (n *Node) DialerContext(ctx context.Context, _, address string) (net.Conn,
|
||||||
|
|
||||||
func Setup(sk ed25519.PrivateKey, instanceName, storageDirectory, peerURI, listenURI string) (*Node, error) {
|
func Setup(sk ed25519.PrivateKey, instanceName, storageDirectory, peerURI, listenURI string) (*Node, error) {
|
||||||
n := &Node{
|
n := &Node{
|
||||||
core: &yggdrasilcore.Core{},
|
log: gologme.New(logrus.StandardLogger().Writer(), "", 0),
|
||||||
config: yggdrasildefaults.GenerateConfig(),
|
incoming: make(chan net.Conn),
|
||||||
multicast: &yggdrasilmulticast.Multicast{},
|
|
||||||
log: gologme.New(logrus.StandardLogger().Writer(), "", 0),
|
|
||||||
incoming: make(chan net.Conn),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options := []yggdrasilcore.SetupOption{
|
|
||||||
yggdrasilcore.AdminListenAddress("none"),
|
|
||||||
}
|
|
||||||
if listenURI != "" {
|
|
||||||
options = append(options, yggdrasilcore.ListenAddress(listenURI))
|
|
||||||
}
|
|
||||||
if peerURI != "" {
|
|
||||||
for _, uri := range strings.Split(peerURI, ",") {
|
|
||||||
options = append(options, yggdrasilcore.Peer{
|
|
||||||
URI: uri,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if n.core, err = yggdrasilcore.New(sk, options...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
n.log.EnableLevel("error")
|
n.log.EnableLevel("error")
|
||||||
n.log.EnableLevel("warn")
|
n.log.EnableLevel("warn")
|
||||||
n.log.EnableLevel("info")
|
n.log.EnableLevel("info")
|
||||||
n.core.SetLogger(n.log)
|
|
||||||
if n.utpSocket, err = utp.NewSocketFromPacketConnNoClose(n.core); err != nil {
|
{
|
||||||
panic(err)
|
var err error
|
||||||
|
options := []yggdrasilcore.SetupOption{}
|
||||||
|
if listenURI != "" {
|
||||||
|
options = append(options, yggdrasilcore.ListenAddress(listenURI))
|
||||||
|
}
|
||||||
|
if peerURI != "" {
|
||||||
|
for _, uri := range strings.Split(peerURI, ",") {
|
||||||
|
options = append(options, yggdrasilcore.Peer{
|
||||||
|
URI: uri,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.core, err = core.New(sk[:], n.log, options...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
n.core.SetLogger(n.log)
|
||||||
|
|
||||||
|
if n.utpSocket, err = utp.NewSocketFromPacketConnNoClose(n.core); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = n.multicast.Init(n.core, n.config, n.log, nil); err != nil {
|
|
||||||
panic(err)
|
// Setup the multicast module.
|
||||||
}
|
{
|
||||||
if err = n.multicast.Start(); err != nil {
|
var err error
|
||||||
panic(err)
|
options := []multicast.SetupOption{
|
||||||
|
multicast.MulticastInterface{
|
||||||
|
Regex: regexp.MustCompile(".*"),
|
||||||
|
Beacon: true,
|
||||||
|
Listen: true,
|
||||||
|
Port: 0,
|
||||||
|
Priority: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if n.multicast, err = multicast.New(n.core, n.log, options...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n.log.Printf("Public key: %x", n.core.PublicKey())
|
n.log.Printf("Public key: %x", n.core.PublicKey())
|
||||||
|
|
@ -114,14 +123,7 @@ func (n *Node) DerivedServerName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) PrivateKey() ed25519.PrivateKey {
|
func (n *Node) PrivateKey() ed25519.PrivateKey {
|
||||||
sk := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
|
return n.core.PrivateKey()
|
||||||
sb, err := hex.DecodeString(n.config.PrivateKey)
|
|
||||||
if err == nil {
|
|
||||||
copy(sk, sb[:])
|
|
||||||
} else {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return sk
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) PublicKey() ed25519.PublicKey {
|
func (n *Node) PublicKey() ed25519.PublicKey {
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/userapi"
|
"github.com/matrix-org/dendrite/userapi"
|
||||||
uapi "github.com/matrix-org/dendrite/userapi/api"
|
uapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup/base"
|
"github.com/matrix-org/dendrite/setup/base"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type entrypoint func(base *base.BaseDendrite, cfg *config.Dendrite)
|
type entrypoint func(base *base.BaseDendrite, cfg *config.Dendrite)
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,8 @@ func runImage(dockerClient *client.Client, volumeName, version, imageID string)
|
||||||
// hit /versions to check it is up
|
// hit /versions to check it is up
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for i := 0; i < 500; i++ {
|
for i := 0; i < 500; i++ {
|
||||||
res, err := http.Get(versionsURL)
|
var res *http.Response
|
||||||
|
res, err = http.Get(versionsURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = fmt.Errorf("GET %s => error: %s", versionsURL, err)
|
lastErr = fmt.Errorf("GET %s => error: %s", versionsURL, err)
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
@ -381,18 +382,22 @@ func runImage(dockerClient *client.Client, volumeName, version, imageID string)
|
||||||
lastErr = nil
|
lastErr = nil
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if lastErr != nil {
|
logs, err := dockerClient.ContainerLogs(context.Background(), containerID, types.ContainerLogsOptions{
|
||||||
logs, err := dockerClient.ContainerLogs(context.Background(), containerID, types.ContainerLogsOptions{
|
ShowStdout: true,
|
||||||
ShowStdout: true,
|
ShowStderr: true,
|
||||||
ShowStderr: true,
|
Follow: true,
|
||||||
})
|
})
|
||||||
// ignore errors when cannot get logs, it's just for debugging anyways
|
// ignore errors when cannot get logs, it's just for debugging anyways
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logbody, err := io.ReadAll(logs)
|
go func() {
|
||||||
if err == nil {
|
for {
|
||||||
log.Printf("Container logs:\n\n%s\n\n", string(logbody))
|
if body, err := io.ReadAll(logs); err == nil && len(body) > 0 {
|
||||||
|
log.Printf("%s: %s", version, string(body))
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
}
|
}
|
||||||
return baseURL, containerID, lastErr
|
return baseURL, containerID, lastErr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -124,6 +125,12 @@ func buildConfig(fs *flag.FlagSet, args []string) (*config.Dendrite, error) {
|
||||||
EnableInbound: true,
|
EnableInbound: true,
|
||||||
EnableOutbound: true,
|
EnableOutbound: true,
|
||||||
}
|
}
|
||||||
|
cfg.SyncAPI.Fulltext = config.Fulltext{
|
||||||
|
Enabled: true,
|
||||||
|
IndexPath: config.Path(filepath.Join(*dirPath, "searchindex")),
|
||||||
|
InMemory: true,
|
||||||
|
Language: "en",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,17 @@ global:
|
||||||
private_key: matrix_key.pem
|
private_key: matrix_key.pem
|
||||||
|
|
||||||
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
||||||
# to old signing private keys that were formerly in use on this domain. These
|
# to old signing keys that were formerly in use on this domain name. These
|
||||||
# keys will not be used for federation request or event signing, but will be
|
# keys will not be used for federation request or event signing, but will be
|
||||||
# provided to any other homeserver that asks when trying to verify old events.
|
# provided to any other homeserver that asks when trying to verify old events.
|
||||||
old_private_keys:
|
old_private_keys:
|
||||||
|
# If the old private key file is available:
|
||||||
# - private_key: old_matrix_key.pem
|
# - private_key: old_matrix_key.pem
|
||||||
# expired_at: 1601024554498
|
# expired_at: 1601024554498
|
||||||
|
# If only the public key (in base64 format) and key ID are known:
|
||||||
|
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
|
||||||
|
# key_id: ed25519:mykeyid
|
||||||
|
# expired_at: 1601024554498
|
||||||
|
|
||||||
# How long a remote server can cache our server signing key before requesting it
|
# How long a remote server can cache our server signing key before requesting it
|
||||||
# again. Increasing this number will reduce the number of requests made by other
|
# again. Increasing this number will reduce the number of requests made by other
|
||||||
|
|
@ -37,7 +42,7 @@ global:
|
||||||
# you must configure the "database" block for each component instead.
|
# you must configure the "database" block for each component instead.
|
||||||
database:
|
database:
|
||||||
connection_string: postgresql://username:password@hostname/dendrite?sslmode=disable
|
connection_string: postgresql://username:password@hostname/dendrite?sslmode=disable
|
||||||
max_open_conns: 100
|
max_open_conns: 90
|
||||||
max_idle_conns: 5
|
max_idle_conns: 5
|
||||||
conn_max_lifetime: -1
|
conn_max_lifetime: -1
|
||||||
|
|
||||||
|
|
@ -174,7 +179,13 @@ client_api:
|
||||||
recaptcha_public_key: ""
|
recaptcha_public_key: ""
|
||||||
recaptcha_private_key: ""
|
recaptcha_private_key: ""
|
||||||
recaptcha_bypass_secret: ""
|
recaptcha_bypass_secret: ""
|
||||||
recaptcha_siteverify_api: ""
|
|
||||||
|
# To use hcaptcha.com instead of ReCAPTCHA, set the following parameters, otherwise just keep them empty.
|
||||||
|
# recaptcha_siteverify_api: "https://hcaptcha.com/siteverify"
|
||||||
|
# recaptcha_api_js_url: "https://js.hcaptcha.com/1/api.js"
|
||||||
|
# recaptcha_form_field: "h-captcha-response"
|
||||||
|
# recaptcha_sitekey_class: "h-captcha"
|
||||||
|
|
||||||
|
|
||||||
# TURN server information that this homeserver should send to clients.
|
# TURN server information that this homeserver should send to clients.
|
||||||
turn:
|
turn:
|
||||||
|
|
@ -275,10 +286,19 @@ sync_api:
|
||||||
# address of the client. This is likely required if Dendrite is running behind
|
# address of the client. This is likely required if Dendrite is running behind
|
||||||
# a reverse proxy server.
|
# a reverse proxy server.
|
||||||
# real_ip_header: X-Real-IP
|
# real_ip_header: X-Real-IP
|
||||||
fulltext:
|
|
||||||
|
# Configuration for the full-text search engine.
|
||||||
|
search:
|
||||||
|
# Whether or not search is enabled.
|
||||||
enabled: false
|
enabled: false
|
||||||
index_path: "./fulltextindex"
|
|
||||||
language: "en" # more possible languages can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
|
# The path where the search index will be created in.
|
||||||
|
index_path: "./searchindex"
|
||||||
|
|
||||||
|
# The language most likely to be used on the server - used when indexing, to
|
||||||
|
# ensure the returned results match expectations. A full list of possible languages
|
||||||
|
# can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
|
||||||
|
language: "en"
|
||||||
|
|
||||||
# Configuration for the User API.
|
# Configuration for the User API.
|
||||||
user_api:
|
user_api:
|
||||||
|
|
@ -296,6 +316,14 @@ user_api:
|
||||||
# The default lifetime is 3600000ms (60 minutes).
|
# The default lifetime is 3600000ms (60 minutes).
|
||||||
# openid_token_lifetime_ms: 3600000
|
# openid_token_lifetime_ms: 3600000
|
||||||
|
|
||||||
|
# Users who register on this homeserver will automatically be joined to the rooms listed under "auto_join_rooms" option.
|
||||||
|
# By default, any room aliases included in this list will be created as a publicly joinable room
|
||||||
|
# when the first user registers for the homeserver. If the room already exists,
|
||||||
|
# make certain it is a publicly joinable room, i.e. the join rule of the room must be set to 'public'.
|
||||||
|
# As Spaces are just rooms under the hood, Space aliases may also be used.
|
||||||
|
auto_join_rooms:
|
||||||
|
# - "#main:matrix.org"
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,17 @@ global:
|
||||||
private_key: matrix_key.pem
|
private_key: matrix_key.pem
|
||||||
|
|
||||||
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
|
||||||
# to old signing private keys that were formerly in use on this domain. These
|
# to old signing keys that were formerly in use on this domain name. These
|
||||||
# keys will not be used for federation request or event signing, but will be
|
# keys will not be used for federation request or event signing, but will be
|
||||||
# provided to any other homeserver that asks when trying to verify old events.
|
# provided to any other homeserver that asks when trying to verify old events.
|
||||||
old_private_keys:
|
old_private_keys:
|
||||||
|
# If the old private key file is available:
|
||||||
# - private_key: old_matrix_key.pem
|
# - private_key: old_matrix_key.pem
|
||||||
# expired_at: 1601024554498
|
# expired_at: 1601024554498
|
||||||
|
# If only the public key (in base64 format) and key ID are known:
|
||||||
|
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
|
||||||
|
# key_id: ed25519:mykeyid
|
||||||
|
# expired_at: 1601024554498
|
||||||
|
|
||||||
# How long a remote server can cache our server signing key before requesting it
|
# How long a remote server can cache our server signing key before requesting it
|
||||||
# again. Increasing this number will reduce the number of requests made by other
|
# again. Increasing this number will reduce the number of requests made by other
|
||||||
|
|
@ -170,7 +175,13 @@ client_api:
|
||||||
recaptcha_public_key: ""
|
recaptcha_public_key: ""
|
||||||
recaptcha_private_key: ""
|
recaptcha_private_key: ""
|
||||||
recaptcha_bypass_secret: ""
|
recaptcha_bypass_secret: ""
|
||||||
recaptcha_siteverify_api: ""
|
|
||||||
|
# To use hcaptcha.com instead of ReCAPTCHA, set the following parameters, otherwise just keep them empty.
|
||||||
|
# recaptcha_siteverify_api: "https://hcaptcha.com/siteverify"
|
||||||
|
# recaptcha_api_js_url: "https://js.hcaptcha.com/1/api.js"
|
||||||
|
# recaptcha_form_field: "h-captcha-response"
|
||||||
|
# recaptcha_sitekey_class: "h-captcha"
|
||||||
|
|
||||||
|
|
||||||
# TURN server information that this homeserver should send to clients.
|
# TURN server information that this homeserver should send to clients.
|
||||||
turn:
|
turn:
|
||||||
|
|
@ -326,10 +337,19 @@ sync_api:
|
||||||
max_open_conns: 10
|
max_open_conns: 10
|
||||||
max_idle_conns: 2
|
max_idle_conns: 2
|
||||||
conn_max_lifetime: -1
|
conn_max_lifetime: -1
|
||||||
fulltext:
|
|
||||||
|
# Configuration for the full-text search engine.
|
||||||
|
search:
|
||||||
|
# Whether or not search is enabled.
|
||||||
enabled: false
|
enabled: false
|
||||||
index_path: "./fulltextindex"
|
|
||||||
language: "en" # more possible languages can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
|
# The path where the search index will be created in.
|
||||||
|
index_path: "./searchindex"
|
||||||
|
|
||||||
|
# The language most likely to be used on the server - used when indexing, to
|
||||||
|
# ensure the returned results match expectations. A full list of possible languages
|
||||||
|
# can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
|
||||||
|
language: "en"
|
||||||
|
|
||||||
# This option controls which HTTP header to inspect to find the real remote IP
|
# This option controls which HTTP header to inspect to find the real remote IP
|
||||||
# address of the client. This is likely required if Dendrite is running behind
|
# address of the client. This is likely required if Dendrite is running behind
|
||||||
|
|
@ -361,6 +381,14 @@ user_api:
|
||||||
# The default lifetime is 3600000ms (60 minutes).
|
# The default lifetime is 3600000ms (60 minutes).
|
||||||
# openid_token_lifetime_ms: 3600000
|
# openid_token_lifetime_ms: 3600000
|
||||||
|
|
||||||
|
# Users who register on this homeserver will automatically be joined to the rooms listed under "auto_join_rooms" option.
|
||||||
|
# By default, any room aliases included in this list will be created as a publicly joinable room
|
||||||
|
# when the first user registers for the homeserver. If the room already exists,
|
||||||
|
# make certain it is a publicly joinable room, i.e. the join rule of the room must be set to 'public'.
|
||||||
|
# As Spaces are just rooms under the hood, Space aliases may also be used.
|
||||||
|
auto_join_rooms:
|
||||||
|
# - "#main:matrix.org"
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
||||||
|
|
@ -231,9 +231,9 @@ GEM
|
||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.15.0)
|
minitest (5.15.0)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
nokogiri (1.13.6-arm64-darwin)
|
nokogiri (1.13.9-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.6-x86_64-linux)
|
nokogiri (1.13.9-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
octokit (4.22.0)
|
octokit (4.22.0)
|
||||||
faraday (>= 0.9)
|
faraday (>= 0.9)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ User accounts can be created on a Dendrite instance in a number of ways.
|
||||||
The `create-account` tool is built in the `bin` folder when building Dendrite with
|
The `create-account` tool is built in the `bin` folder when building Dendrite with
|
||||||
the `build.sh` script.
|
the `build.sh` script.
|
||||||
|
|
||||||
It uses the `dendrite.yaml` configuration file to connect to a running Dendrite instance and requires
|
It uses the `dendrite.yaml` configuration file to connect to a running Dendrite instance and requires
|
||||||
shared secret registration to be enabled as explained below.
|
shared secret registration to be enabled as explained below.
|
||||||
|
|
||||||
An example of using `create-account` to create a **normal account**:
|
An example of using `create-account` to create a **normal account**:
|
||||||
|
|
@ -31,11 +31,11 @@ To create a new **admin account**, add the `-admin` flag:
|
||||||
./bin/create-account -config /path/to/dendrite.yaml -username USERNAME -admin
|
./bin/create-account -config /path/to/dendrite.yaml -username USERNAME -admin
|
||||||
```
|
```
|
||||||
|
|
||||||
By default `create-account` uses `https://localhost:8448` to connect to Dendrite, this can be overwritten using
|
By default `create-account` uses `http://localhost:8008` to connect to Dendrite, this can be overwritten using
|
||||||
the `-url` flag:
|
the `-url` flag:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./bin/create-account -config /path/to/dendrite.yaml -username USERNAME -url http://localhost:8008
|
./bin/create-account -config /path/to/dendrite.yaml -username USERNAME -url https://localhost:8448
|
||||||
```
|
```
|
||||||
|
|
||||||
An example of using `create-account` when running in **Docker**, having found the `CONTAINERNAME` from `docker ps`:
|
An example of using `create-account` when running in **Docker**, having found the `CONTAINERNAME` from `docker ps`:
|
||||||
|
|
@ -43,6 +43,7 @@ An example of using `create-account` when running in **Docker**, having found th
|
||||||
```bash
|
```bash
|
||||||
docker exec -it CONTAINERNAME /usr/bin/create-account -config /path/to/dendrite.yaml -username USERNAME
|
docker exec -it CONTAINERNAME /usr/bin/create-account -config /path/to/dendrite.yaml -username USERNAME
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it CONTAINERNAME /usr/bin/create-account -config /path/to/dendrite.yaml -username USERNAME -admin
|
docker exec -it CONTAINERNAME /usr/bin/create-account -config /path/to/dendrite.yaml -username USERNAME -admin
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,16 @@ Request body format:
|
||||||
Reset the password of a local user. The `localpart` is the username only, i.e. if
|
Reset the password of a local user. The `localpart` is the username only, i.e. if
|
||||||
the full user ID is `@alice:domain.com` then the local part is `alice`.
|
the full user ID is `@alice:domain.com` then the local part is `alice`.
|
||||||
|
|
||||||
|
## GET `/_dendrite/admin/fulltext/reindex`
|
||||||
|
|
||||||
|
This endpoint instructs Dendrite to reindex all searchable events (`m.room.message`, `m.room.topic` and `m.room.name`). An empty JSON body will be returned immediately.
|
||||||
|
Indexing is done in the background, the server logs every 1000 events (or below) when they are being indexed. Once reindexing is done, you'll see something along the lines `Indexed 69586 events in 53.68223182s` in your debug logs.
|
||||||
|
|
||||||
|
## POST `/_dendrite/admin/refreshDevices/{userID}`
|
||||||
|
|
||||||
|
This endpoint instructs Dendrite to immediately query `/devices/{userID}` on a federated server. An empty JSON body will be returned on success, updating all locally stored user devices/keys. This can be used to possibly resolve E2EE issues, where the remote user can't decrypt messages.
|
||||||
|
|
||||||
|
|
||||||
## POST `/_synapse/admin/v1/send_server_notice`
|
## POST `/_synapse/admin/v1/send_server_notice`
|
||||||
|
|
||||||
Request body format:
|
Request body format:
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,85 @@
|
||||||
# Sample Caddyfile for using Caddy in front of Dendrite.
|
# Sample Caddyfile for using Caddy in front of Dendrite
|
||||||
#
|
|
||||||
# Customize email address and domain names.
|
|
||||||
# Optional settings commented out.
|
|
||||||
#
|
|
||||||
# BE SURE YOUR DOMAINS ARE POINTED AT YOUR SERVER FIRST.
|
|
||||||
# Documentation: https://caddyserver.com/docs/
|
|
||||||
#
|
|
||||||
# Bonus tip: If your IP address changes, use Caddy's
|
|
||||||
# dynamic DNS plugin to update your DNS records to
|
|
||||||
# point to your new IP automatically:
|
|
||||||
# https://github.com/mholt/caddy-dynamicdns
|
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Customize email address and domain names
|
||||||
|
|
||||||
|
# Optional settings commented out
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
# BE SURE YOUR DOMAINS ARE POINTED AT YOUR SERVER FIRST
|
||||||
|
|
||||||
|
# Documentation: <https://caddyserver.com/docs/>
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
# Bonus tip: If your IP address changes, use Caddy's
|
||||||
|
|
||||||
|
# dynamic DNS plugin to update your DNS records to
|
||||||
|
|
||||||
|
# point to your new IP automatically
|
||||||
|
|
||||||
|
# <https://github.com/mholt/caddy-dynamicdns>
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
# Global options block
|
# Global options block
|
||||||
|
|
||||||
{
|
{
|
||||||
# In case there is a problem with your certificates.
|
# In case there is a problem with your certificates.
|
||||||
# email example@example.com
|
# email example@example.com
|
||||||
|
|
||||||
# Turn off the admin endpoint if you don't need graceful config
|
# Turn off the admin endpoint if you don't need graceful config
|
||||||
# changes and/or are running untrusted code on your machine.
|
# changes and/or are running untrusted code on your machine.
|
||||||
# admin off
|
# admin off
|
||||||
|
|
||||||
# Enable this if your clients don't send ServerName in TLS handshakes.
|
# Enable this if your clients don't send ServerName in TLS handshakes.
|
||||||
# default_sni example.com
|
# default_sni example.com
|
||||||
|
|
||||||
# Enable debug mode for verbose logging.
|
# Enable debug mode for verbose logging.
|
||||||
# debug
|
# debug
|
||||||
|
|
||||||
# Use Let's Encrypt's staging endpoint for testing.
|
# Use Let's Encrypt's staging endpoint for testing.
|
||||||
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
|
||||||
# If you're port-forwarding HTTP/HTTPS ports from 80/443 to something
|
# If you're port-forwarding HTTP/HTTPS ports from 80/443 to something
|
||||||
# else, enable these and put the alternate port numbers here.
|
# else, enable these and put the alternate port numbers here.
|
||||||
# http_port 8080
|
# http_port 8080
|
||||||
# https_port 8443
|
# https_port 8443
|
||||||
}
|
}
|
||||||
|
|
||||||
# The server name of your matrix homeserver. This example shows
|
# The server name of your matrix homeserver. This example shows
|
||||||
# "well-known delegation" from the registered domain to a subdomain,
|
|
||||||
|
# "well-known delegation" from the registered domain to a subdomain
|
||||||
|
|
||||||
# which is only needed if your server_name doesn't match your Matrix
|
# which is only needed if your server_name doesn't match your Matrix
|
||||||
|
|
||||||
# homeserver URL (i.e. you can show users a vanity domain that looks
|
# homeserver URL (i.e. you can show users a vanity domain that looks
|
||||||
|
|
||||||
# nice and is easy to remember but still have your Matrix server on
|
# nice and is easy to remember but still have your Matrix server on
|
||||||
# its own subdomain or hosted service).
|
|
||||||
|
# its own subdomain or hosted service)
|
||||||
|
|
||||||
example.com {
|
example.com {
|
||||||
header /.well-known/matrix/* Content-Type application/json
|
header /.well-known/matrix/*Content-Type application/json
|
||||||
header /.well-known/matrix/* Access-Control-Allow-Origin *
|
header /.well-known/matrix/* Access-Control-Allow-Origin *
|
||||||
respond /.well-known/matrix/server `{"m.server": "matrix.example.com:443"}`
|
respond /.well-known/matrix/server `{"m.server": "matrix.example.com:443"}`
|
||||||
respond /.well-known/matrix/client `{"m.homeserver": {"base_url": "https://matrix.example.com"}}`
|
respond /.well-known/matrix/client `{"m.homeserver": {"base_url": "https://matrix.example.com"}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
# The actual domain name whereby your Matrix server is accessed.
|
# The actual domain name whereby your Matrix server is accessed
|
||||||
|
|
||||||
matrix.example.com {
|
matrix.example.com {
|
||||||
# Change the end of each reverse_proxy line to the correct
|
# Change the end of each reverse_proxy line to the correct
|
||||||
# address for your various services.
|
# address for your various services.
|
||||||
@sync_api {
|
@sync_api {
|
||||||
path_regexp /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/messages)$
|
path_regexp /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|.*?_?members|context/.*?|relations/.*?|event/.*?))$
|
||||||
}
|
}
|
||||||
reverse_proxy @sync_api sync_api:8073
|
reverse_proxy @sync_api sync_api:8073
|
||||||
|
|
||||||
reverse_proxy /_matrix/client* client_api:8071
|
reverse_proxy /_matrix/client* client_api:8071
|
||||||
reverse_proxy /_matrix/federation* federation_api:8071
|
reverse_proxy /_matrix/federation* federation_api:8071
|
||||||
reverse_proxy /_matrix/key* federation_api:8071
|
reverse_proxy /_matrix/key* federation_api:8071
|
||||||
reverse_proxy /_matrix/media* media_api:8071
|
reverse_proxy /_matrix/media* media_api:8071
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,15 @@ VirtualHost {
|
||||||
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
||||||
# /_matrix/client/.*/keys/changes
|
# /_matrix/client/.*/keys/changes
|
||||||
# /_matrix/client/.*/rooms/{roomId}/messages
|
# /_matrix/client/.*/rooms/{roomId}/messages
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/context/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/event/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}/{relType}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}/{relType}/{eventType}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/members
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/joined_members
|
||||||
# to sync_api
|
# to sync_api
|
||||||
ReverseProxy = /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/messages) http://localhost:8073 600
|
ReverseProxy = /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|.*?_?members|context/.*?|relations/.*?|event/.*?))$ http://localhost:8073 600
|
||||||
ReverseProxy = /_matrix/client http://localhost:8071 600
|
ReverseProxy = /_matrix/client http://localhost:8071 600
|
||||||
ReverseProxy = /_matrix/federation http://localhost:8072 600
|
ReverseProxy = /_matrix/federation http://localhost:8072 600
|
||||||
ReverseProxy = /_matrix/key http://localhost:8072 600
|
ReverseProxy = /_matrix/key http://localhost:8072 600
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,41 @@ permalink: /installation/start/optimisation
|
||||||
Now that you have Dendrite running, the following tweaks will improve the reliability
|
Now that you have Dendrite running, the following tweaks will improve the reliability
|
||||||
and performance of your installation.
|
and performance of your installation.
|
||||||
|
|
||||||
|
## PostgreSQL connection limit
|
||||||
|
|
||||||
|
A PostgreSQL database engine is configured to allow only a certain number of connections.
|
||||||
|
This is typically controlled by the `max_connections` and `superuser_reserved_connections`
|
||||||
|
configuration items in `postgresql.conf`. Once these limits are violated, **PostgreSQL will
|
||||||
|
immediately stop accepting new connections** until some of the existing connections are closed.
|
||||||
|
This is a common source of misconfiguration and requires particular care.
|
||||||
|
|
||||||
|
If your PostgreSQL `max_connections` is set to `100` and `superuser_reserved_connections` is
|
||||||
|
set to `3` then you have an effective connection limit of 97 database connections. It is
|
||||||
|
therefore important to ensure that Dendrite doesn't violate that limit, otherwise database
|
||||||
|
queries will unexpectedly fail and this will cause problems both within Dendrite and for users.
|
||||||
|
|
||||||
|
If you are also running other software that uses the same PostgreSQL database engine, then you
|
||||||
|
must also take into account that some connections will be already used by your other software
|
||||||
|
and therefore will not be available to Dendrite. Check the configuration of any other software
|
||||||
|
using the same database engine for their configured connection limits and adjust your calculations
|
||||||
|
accordingly.
|
||||||
|
|
||||||
|
Dendrite has a `max_open_conns` configuration item in each `database` block to control how many
|
||||||
|
connections it will open to the database.
|
||||||
|
|
||||||
|
**If you are using the `global` database pool** then you only need to configure the
|
||||||
|
`max_open_conns` setting once in the `global` section.
|
||||||
|
|
||||||
|
**If you are defining a `database` config per component** then you will need to ensure that
|
||||||
|
the **sum total** of all configured `max_open_conns` to a given database server do not exceed
|
||||||
|
the connection limit. If you configure a total that adds up to more connections than are available
|
||||||
|
then this will cause database queries to fail.
|
||||||
|
|
||||||
|
You may wish to raise the `max_connections` limit on your PostgreSQL server to accommodate
|
||||||
|
additional connections, in which case you should also update the `max_open_conns` in your
|
||||||
|
Dendrite configuration accordingly. However be aware that this is only advisable on particularly
|
||||||
|
powerful servers that can handle the concurrent load of additional queries running at one time.
|
||||||
|
|
||||||
## File descriptor limit
|
## File descriptor limit
|
||||||
|
|
||||||
Most platforms have a limit on how many file descriptors a single process can open. All
|
Most platforms have a limit on how many file descriptors a single process can open. All
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,12 @@ and contain the following JSON document:
|
||||||
For example, this can be done with the following Caddy config:
|
For example, this can be done with the following Caddy config:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
handle /.well-known/matrix/server {
|
||||||
|
header Content-Type application/json
|
||||||
|
header Access-Control-Allow-Origin *
|
||||||
|
respond `"m.server": "matrix.example.com:8448"`
|
||||||
|
}
|
||||||
|
|
||||||
handle /.well-known/matrix/client {
|
handle /.well-known/matrix/client {
|
||||||
header Content-Type application/json
|
header Content-Type application/json
|
||||||
header Access-Control-Allow-Origin *
|
header Access-Control-Allow-Origin *
|
||||||
|
|
|
||||||
|
|
@ -138,16 +138,18 @@ room_server:
|
||||||
conn_max_lifetime: -1
|
conn_max_lifetime: -1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fulltext search
|
## Full-text search
|
||||||
|
|
||||||
Dendrite supports experimental fulltext indexing using [Bleve](https://github.com/blevesearch/bleve), it is configured in the `sync_api` section as follows. Depending on the language most likely to be used on the server, it might make sense to change the `language` used when indexing, to ensure the returned results match the expections. A full list of possible languages can be found [here](https://github.com/blevesearch/bleve/tree/master/analysis/lang).
|
Dendrite supports experimental full-text indexing using [Bleve](https://github.com/blevesearch/bleve). It is configured in the `sync_api` section as follows.
|
||||||
|
|
||||||
|
Depending on the language most likely to be used on the server, it might make sense to change the `language` used when indexing, to ensure the returned results match the expectations. A full list of possible languages can be found [here](https://github.com/blevesearch/bleve/tree/master/analysis/lang).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
sync_api:
|
sync_api:
|
||||||
# ...
|
# ...
|
||||||
fulltext:
|
search:
|
||||||
enabled: false
|
enabled: false
|
||||||
index_path: "./fulltextindex"
|
index_path: "./searchindex"
|
||||||
language: "en"
|
language: "en"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,15 @@ server {
|
||||||
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
# /_matrix/client/.*/user/{userId}/filter/{filterID}
|
||||||
# /_matrix/client/.*/keys/changes
|
# /_matrix/client/.*/keys/changes
|
||||||
# /_matrix/client/.*/rooms/{roomId}/messages
|
# /_matrix/client/.*/rooms/{roomId}/messages
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/context/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/event/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}/{relType}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/relations/{eventID}/{relType}/{eventType}
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/members
|
||||||
|
# /_matrix/client/.*/rooms/{roomId}/joined_members
|
||||||
# to sync_api
|
# to sync_api
|
||||||
location ~ /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/messages)$ {
|
location ~ /_matrix/client/.*?/(sync|user/.*?/filter/?.*|keys/changes|rooms/.*?/(messages|.*?_?members|context/.*?|relations/.*?|event/.*?))$ {
|
||||||
proxy_pass http://sync_api:8073;
|
proxy_pass http://sync_api:8073;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,7 @@ type PerformJoinRequest struct {
|
||||||
// The sorted list of servers to try. Servers will be tried sequentially, after de-duplication.
|
// The sorted list of servers to try. Servers will be tried sequentially, after de-duplication.
|
||||||
ServerNames types.ServerNames `json:"server_names"`
|
ServerNames types.ServerNames `json:"server_names"`
|
||||||
Content map[string]interface{} `json:"content"`
|
Content map[string]interface{} `json:"content"`
|
||||||
|
Unsigned map[string]interface{} `json:"unsigned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformJoinResponse struct {
|
type PerformJoinResponse struct {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
"github.com/matrix-org/dendrite/federationapi/types"
|
"github.com/matrix-org/dendrite/federationapi/types"
|
||||||
|
|
@ -26,21 +31,18 @@ import (
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/nats-io/nats.go"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyChangeConsumer consumes events that originate in key server.
|
// KeyChangeConsumer consumes events that originate in key server.
|
||||||
type KeyChangeConsumer struct {
|
type KeyChangeConsumer struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
jetstream nats.JetStreamContext
|
jetstream nats.JetStreamContext
|
||||||
durable string
|
durable string
|
||||||
db storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
serverName gomatrixserverlib.ServerName
|
isLocalServerName func(gomatrixserverlib.ServerName) bool
|
||||||
rsAPI roomserverAPI.FederationRoomserverAPI
|
rsAPI roomserverAPI.FederationRoomserverAPI
|
||||||
topic string
|
topic string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyChangeConsumer creates a new KeyChangeConsumer. Call Start() to begin consuming from key servers.
|
// NewKeyChangeConsumer creates a new KeyChangeConsumer. Call Start() to begin consuming from key servers.
|
||||||
|
|
@ -53,14 +55,14 @@ func NewKeyChangeConsumer(
|
||||||
rsAPI roomserverAPI.FederationRoomserverAPI,
|
rsAPI roomserverAPI.FederationRoomserverAPI,
|
||||||
) *KeyChangeConsumer {
|
) *KeyChangeConsumer {
|
||||||
return &KeyChangeConsumer{
|
return &KeyChangeConsumer{
|
||||||
ctx: process.Context(),
|
ctx: process.Context(),
|
||||||
jetstream: js,
|
jetstream: js,
|
||||||
durable: cfg.Matrix.JetStream.Prefixed("FederationAPIKeyChangeConsumer"),
|
durable: cfg.Matrix.JetStream.Prefixed("FederationAPIKeyChangeConsumer"),
|
||||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputKeyChangeEvent),
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputKeyChangeEvent),
|
||||||
queues: queues,
|
queues: queues,
|
||||||
db: store,
|
db: store,
|
||||||
serverName: cfg.Matrix.ServerName,
|
isLocalServerName: cfg.Matrix.IsLocalServerName,
|
||||||
rsAPI: rsAPI,
|
rsAPI: rsAPI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,6 +80,7 @@ func (t *KeyChangeConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) boo
|
||||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||||
var m api.DeviceMessage
|
var m api.DeviceMessage
|
||||||
if err := json.Unmarshal(msg.Data, &m); err != nil {
|
if err := json.Unmarshal(msg.Data, &m); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
logrus.WithError(err).Errorf("failed to read device message from key change topic")
|
logrus.WithError(err).Errorf("failed to read device message from key change topic")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -105,10 +108,11 @@ func (t *KeyChangeConsumer) onDeviceKeyMessage(m api.DeviceMessage) bool {
|
||||||
// only send key change events which originated from us
|
// only send key change events which originated from us
|
||||||
_, originServerName, err := gomatrixserverlib.SplitID('@', m.UserID)
|
_, originServerName, err := gomatrixserverlib.SplitID('@', m.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("Failed to extract domain from key change event")
|
logger.WithError(err).Error("Failed to extract domain from key change event")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if originServerName != t.serverName {
|
if !t.isLocalServerName(originServerName) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,6 +122,7 @@ func (t *KeyChangeConsumer) onDeviceKeyMessage(m api.DeviceMessage) bool {
|
||||||
WantMembership: "join",
|
WantMembership: "join",
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +130,7 @@ func (t *KeyChangeConsumer) onDeviceKeyMessage(m api.DeviceMessage) bool {
|
||||||
// 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, queryRes.RoomIDs, true)
|
destinations, err := t.db.GetJoinedHostsForRooms(t.ctx, queryRes.RoomIDs, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +141,7 @@ func (t *KeyChangeConsumer) onDeviceKeyMessage(m api.DeviceMessage) bool {
|
||||||
// Pack the EDU and marshal it
|
// Pack the EDU and marshal it
|
||||||
edu := &gomatrixserverlib.EDU{
|
edu := &gomatrixserverlib.EDU{
|
||||||
Type: gomatrixserverlib.MDeviceListUpdate,
|
Type: gomatrixserverlib.MDeviceListUpdate,
|
||||||
Origin: string(t.serverName),
|
Origin: string(originServerName),
|
||||||
}
|
}
|
||||||
event := gomatrixserverlib.DeviceListUpdateEvent{
|
event := gomatrixserverlib.DeviceListUpdateEvent{
|
||||||
UserID: m.UserID,
|
UserID: m.UserID,
|
||||||
|
|
@ -147,12 +153,13 @@ func (t *KeyChangeConsumer) onDeviceKeyMessage(m api.DeviceMessage) bool {
|
||||||
Keys: m.KeyJSON,
|
Keys: m.KeyJSON,
|
||||||
}
|
}
|
||||||
if edu.Content, err = json.Marshal(event); err != nil {
|
if edu.Content, err = json.Marshal(event); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("failed to marshal EDU JSON")
|
logger.WithError(err).Error("failed to marshal EDU JSON")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Sending device list update message to %q", destinations)
|
logger.Debugf("Sending device list update message to %q", destinations)
|
||||||
err = t.queues.SendEDU(edu, t.serverName, destinations)
|
err = t.queues.SendEDU(edu, originServerName, destinations)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,10 +167,11 @@ func (t *KeyChangeConsumer) onCrossSigningMessage(m api.DeviceMessage) bool {
|
||||||
output := m.CrossSigningKeyUpdate
|
output := m.CrossSigningKeyUpdate
|
||||||
_, host, err := gomatrixserverlib.SplitID('@', output.UserID)
|
_, host, err := gomatrixserverlib.SplitID('@', output.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
logrus.WithError(err).Errorf("fedsender key change consumer: user ID parse failure")
|
logrus.WithError(err).Errorf("fedsender key change consumer: user ID parse failure")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if host != gomatrixserverlib.ServerName(t.serverName) {
|
if !t.isLocalServerName(host) {
|
||||||
// Ignore any messages that didn't originate locally, otherwise we'll
|
// Ignore any messages that didn't originate locally, otherwise we'll
|
||||||
// end up parroting information we received from other servers.
|
// end up parroting information we received from other servers.
|
||||||
return true
|
return true
|
||||||
|
|
@ -176,12 +184,14 @@ func (t *KeyChangeConsumer) onCrossSigningMessage(m api.DeviceMessage) bool {
|
||||||
WantMembership: "join",
|
WantMembership: "join",
|
||||||
}, &queryRes)
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
// 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, queryRes.RoomIDs, true)
|
destinations, err := t.db.GetJoinedHostsForRooms(t.ctx, queryRes.RoomIDs, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -193,15 +203,16 @@ func (t *KeyChangeConsumer) onCrossSigningMessage(m api.DeviceMessage) bool {
|
||||||
// Pack the EDU and marshal it
|
// Pack the EDU and marshal it
|
||||||
edu := &gomatrixserverlib.EDU{
|
edu := &gomatrixserverlib.EDU{
|
||||||
Type: types.MSigningKeyUpdate,
|
Type: types.MSigningKeyUpdate,
|
||||||
Origin: string(t.serverName),
|
Origin: string(host),
|
||||||
}
|
}
|
||||||
if edu.Content, err = json.Marshal(output); err != nil {
|
if edu.Content, err = json.Marshal(output); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
logger.WithError(err).Error("fedsender key change consumer: failed to marshal output, dropping")
|
logger.WithError(err).Error("fedsender key change consumer: failed to marshal output, dropping")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Sending cross-signing update message to %q", destinations)
|
logger.Debugf("Sending cross-signing update message to %q", destinations)
|
||||||
err = t.queues.SendEDU(edu, t.serverName, destinations)
|
err = t.queues.SendEDU(edu, host, destinations)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
fedTypes "github.com/matrix-org/dendrite/federationapi/types"
|
fedTypes "github.com/matrix-org/dendrite/federationapi/types"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
|
|
@ -38,7 +39,8 @@ type OutputPresenceConsumer struct {
|
||||||
durable string
|
durable string
|
||||||
db storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
ServerName gomatrixserverlib.ServerName
|
isLocalServerName func(gomatrixserverlib.ServerName) bool
|
||||||
|
rsAPI roomserverAPI.FederationRoomserverAPI
|
||||||
topic string
|
topic string
|
||||||
outboundPresenceEnabled bool
|
outboundPresenceEnabled bool
|
||||||
}
|
}
|
||||||
|
|
@ -50,16 +52,18 @@ func NewOutputPresenceConsumer(
|
||||||
js nats.JetStreamContext,
|
js nats.JetStreamContext,
|
||||||
queues *queue.OutgoingQueues,
|
queues *queue.OutgoingQueues,
|
||||||
store storage.Database,
|
store storage.Database,
|
||||||
|
rsAPI roomserverAPI.FederationRoomserverAPI,
|
||||||
) *OutputPresenceConsumer {
|
) *OutputPresenceConsumer {
|
||||||
return &OutputPresenceConsumer{
|
return &OutputPresenceConsumer{
|
||||||
ctx: process.Context(),
|
ctx: process.Context(),
|
||||||
jetstream: js,
|
jetstream: js,
|
||||||
queues: queues,
|
queues: queues,
|
||||||
db: store,
|
db: store,
|
||||||
ServerName: cfg.Matrix.ServerName,
|
isLocalServerName: cfg.Matrix.IsLocalServerName,
|
||||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIPresenceConsumer"),
|
durable: cfg.Matrix.JetStream.Durable("FederationAPIPresenceConsumer"),
|
||||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
||||||
outboundPresenceEnabled: cfg.Matrix.Presence.EnableOutbound,
|
outboundPresenceEnabled: cfg.Matrix.Presence.EnableOutbound,
|
||||||
|
rsAPI: rsAPI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +89,17 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg
|
||||||
log.WithError(err).WithField("user_id", userID).Error("failed to extract domain from receipt sender")
|
log.WithError(err).WithField("user_id", userID).Error("failed to extract domain from receipt sender")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if serverName != t.ServerName {
|
if !t.isLocalServerName(serverName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryRes roomserverAPI.QueryRoomsForUserResponse
|
||||||
|
err = t.rsAPI.QueryRoomsForUser(t.ctx, &roomserverAPI.QueryRoomsForUserRequest{
|
||||||
|
UserID: userID,
|
||||||
|
WantMembership: "join",
|
||||||
|
}, &queryRes)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("failed to calculate joined rooms for user")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,11 +110,13 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
joined, err := t.db.GetAllJoinedHosts(ctx)
|
// send this presence to all servers who share rooms with this user.
|
||||||
|
joined, err := t.db.GetJoinedHostsForRooms(t.ctx, queryRes.RoomIDs, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(joined) == 0 {
|
if len(joined) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +143,7 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg
|
||||||
|
|
||||||
edu := &gomatrixserverlib.EDU{
|
edu := &gomatrixserverlib.EDU{
|
||||||
Type: gomatrixserverlib.MPresence,
|
Type: gomatrixserverlib.MPresence,
|
||||||
Origin: string(t.ServerName),
|
Origin: string(serverName),
|
||||||
}
|
}
|
||||||
if edu.Content, err = json.Marshal(content); err != nil {
|
if edu.Content, err = json.Marshal(content); err != nil {
|
||||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||||
|
|
@ -135,7 +151,7 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msgs []*nats.Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracef("sending presence EDU to %d servers", len(joined))
|
log.Tracef("sending presence EDU to %d servers", len(joined))
|
||||||
if err = t.queues.SendEDU(edu, t.ServerName, joined); err != nil {
|
if err = t.queues.SendEDU(edu, serverName, joined); err != nil {
|
||||||
log.WithError(err).Error("failed to send EDU")
|
log.WithError(err).Error("failed to send EDU")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,13 @@ import (
|
||||||
|
|
||||||
// OutputReceiptConsumer consumes events that originate in the clientapi.
|
// OutputReceiptConsumer consumes events that originate in the clientapi.
|
||||||
type OutputReceiptConsumer struct {
|
type OutputReceiptConsumer struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
jetstream nats.JetStreamContext
|
jetstream nats.JetStreamContext
|
||||||
durable string
|
durable string
|
||||||
db storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
ServerName gomatrixserverlib.ServerName
|
isLocalServerName func(gomatrixserverlib.ServerName) bool
|
||||||
topic string
|
topic string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutputReceiptConsumer creates a new OutputReceiptConsumer. Call Start() to begin consuming typing events.
|
// NewOutputReceiptConsumer creates a new OutputReceiptConsumer. Call Start() to begin consuming typing events.
|
||||||
|
|
@ -52,13 +52,13 @@ func NewOutputReceiptConsumer(
|
||||||
store storage.Database,
|
store storage.Database,
|
||||||
) *OutputReceiptConsumer {
|
) *OutputReceiptConsumer {
|
||||||
return &OutputReceiptConsumer{
|
return &OutputReceiptConsumer{
|
||||||
ctx: process.Context(),
|
ctx: process.Context(),
|
||||||
jetstream: js,
|
jetstream: js,
|
||||||
queues: queues,
|
queues: queues,
|
||||||
db: store,
|
db: store,
|
||||||
ServerName: cfg.Matrix.ServerName,
|
isLocalServerName: cfg.Matrix.IsLocalServerName,
|
||||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIReceiptConsumer"),
|
durable: cfg.Matrix.JetStream.Durable("FederationAPIReceiptConsumer"),
|
||||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,13 +81,21 @@ func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msgs []*nats.Msg)
|
||||||
Type: msg.Header.Get("type"),
|
Type: msg.Header.Get("type"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch receipt.Type {
|
||||||
|
case "m.read":
|
||||||
|
// These are allowed to be sent over federation
|
||||||
|
case "m.read.private", "m.fully_read":
|
||||||
|
// These must not be sent over federation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// only send receipt events which originated from us
|
// only send receipt events which originated from us
|
||||||
_, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID)
|
_, receiptServerName, err := gomatrixserverlib.SplitID('@', receipt.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).WithField("user_id", receipt.UserID).Error("failed to extract domain from receipt sender")
|
log.WithError(err).WithField("user_id", receipt.UserID).Error("failed to extract domain from receipt sender")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if receiptServerName != t.ServerName {
|
if !t.isLocalServerName(receiptServerName) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,14 +134,14 @@ func (t *OutputReceiptConsumer) onMessage(ctx context.Context, msgs []*nats.Msg)
|
||||||
|
|
||||||
edu := &gomatrixserverlib.EDU{
|
edu := &gomatrixserverlib.EDU{
|
||||||
Type: gomatrixserverlib.MReceipt,
|
Type: gomatrixserverlib.MReceipt,
|
||||||
Origin: string(t.ServerName),
|
Origin: string(receiptServerName),
|
||||||
}
|
}
|
||||||
if edu.Content, err = json.Marshal(content); err != nil {
|
if edu.Content, err = json.Marshal(content); err != nil {
|
||||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := t.queues.SendEDU(edu, t.ServerName, names); err != nil {
|
if err := t.queues.SendEDU(edu, receiptServerName, names); err != nil {
|
||||||
log.WithError(err).Error("failed to send EDU")
|
log.WithError(err).Error("failed to send EDU")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,13 @@ func (s *OutputRoomEventConsumer) Start() error {
|
||||||
// realises that it cannot update the room state using the deltas.
|
// realises that it cannot update the room state using the deltas.
|
||||||
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
func (s *OutputRoomEventConsumer) onMessage(ctx context.Context, msgs []*nats.Msg) bool {
|
||||||
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
msg := msgs[0] // Guaranteed to exist if onMessage is called
|
||||||
|
receivedType := api.OutputType(msg.Header.Get(jetstream.RoomEventType))
|
||||||
|
|
||||||
|
// Only handle events we care about
|
||||||
|
if receivedType != api.OutputTypeNewRoomEvent && receivedType != api.OutputTypeNewInboundPeek {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Parse out the event JSON
|
// Parse out the event JSON
|
||||||
var output api.OutputEvent
|
var output api.OutputEvent
|
||||||
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
if err := json.Unmarshal(msg.Data, &output); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -18,27 +18,29 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/queue"
|
"github.com/matrix-org/dendrite/federationapi/queue"
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage"
|
"github.com/matrix-org/dendrite/federationapi/storage"
|
||||||
"github.com/matrix-org/dendrite/setup/config"
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
syncTypes "github.com/matrix-org/dendrite/syncapi/types"
|
syncTypes "github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
"github.com/nats-io/nats.go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OutputSendToDeviceConsumer consumes events that originate in the clientapi.
|
// OutputSendToDeviceConsumer consumes events that originate in the clientapi.
|
||||||
type OutputSendToDeviceConsumer struct {
|
type OutputSendToDeviceConsumer struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
jetstream nats.JetStreamContext
|
jetstream nats.JetStreamContext
|
||||||
durable string
|
durable string
|
||||||
db storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
ServerName gomatrixserverlib.ServerName
|
isLocalServerName func(gomatrixserverlib.ServerName) bool
|
||||||
topic string
|
topic string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutputSendToDeviceConsumer creates a new OutputSendToDeviceConsumer. Call Start() to begin consuming send-to-device events.
|
// NewOutputSendToDeviceConsumer creates a new OutputSendToDeviceConsumer. Call Start() to begin consuming send-to-device events.
|
||||||
|
|
@ -50,13 +52,13 @@ func NewOutputSendToDeviceConsumer(
|
||||||
store storage.Database,
|
store storage.Database,
|
||||||
) *OutputSendToDeviceConsumer {
|
) *OutputSendToDeviceConsumer {
|
||||||
return &OutputSendToDeviceConsumer{
|
return &OutputSendToDeviceConsumer{
|
||||||
ctx: process.Context(),
|
ctx: process.Context(),
|
||||||
jetstream: js,
|
jetstream: js,
|
||||||
queues: queues,
|
queues: queues,
|
||||||
db: store,
|
db: store,
|
||||||
ServerName: cfg.Matrix.ServerName,
|
isLocalServerName: cfg.Matrix.IsLocalServerName,
|
||||||
durable: cfg.Matrix.JetStream.Durable("FederationAPIESendToDeviceConsumer"),
|
durable: cfg.Matrix.JetStream.Durable("FederationAPIESendToDeviceConsumer"),
|
||||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,35 +78,37 @@ func (t *OutputSendToDeviceConsumer) onMessage(ctx context.Context, msgs []*nats
|
||||||
sender := msg.Header.Get("sender")
|
sender := msg.Header.Get("sender")
|
||||||
_, originServerName, err := gomatrixserverlib.SplitID('@', sender)
|
_, originServerName, err := gomatrixserverlib.SplitID('@', sender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
log.WithError(err).WithField("user_id", sender).Error("Failed to extract domain from send-to-device sender")
|
log.WithError(err).WithField("user_id", sender).Error("Failed to extract domain from send-to-device sender")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if originServerName != t.ServerName {
|
if !t.isLocalServerName(originServerName) {
|
||||||
log.WithField("other_server", originServerName).Info("Suppressing send-to-device: originated elsewhere")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Extract the send-to-device event from msg.
|
// Extract the send-to-device event from msg.
|
||||||
var ote syncTypes.OutputSendToDeviceEvent
|
var ote syncTypes.OutputSendToDeviceEvent
|
||||||
if err = json.Unmarshal(msg.Data, &ote); err != nil {
|
if err = json.Unmarshal(msg.Data, &ote); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
log.WithError(err).Errorf("output log: message parse failed (expected send-to-device)")
|
log.WithError(err).Errorf("output log: message parse failed (expected send-to-device)")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_, destServerName, err := gomatrixserverlib.SplitID('@', ote.UserID)
|
_, destServerName, err := gomatrixserverlib.SplitID('@', ote.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
log.WithError(err).WithField("user_id", ote.UserID).Error("Failed to extract domain from send-to-device destination")
|
log.WithError(err).WithField("user_id", ote.UserID).Error("Failed to extract domain from send-to-device destination")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// The SyncAPI is already handling sendToDevice for the local server
|
// The SyncAPI is already handling sendToDevice for the local server
|
||||||
if destServerName == t.ServerName {
|
if t.isLocalServerName(destServerName) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack the EDU and marshal it
|
// Pack the EDU and marshal it
|
||||||
edu := &gomatrixserverlib.EDU{
|
edu := &gomatrixserverlib.EDU{
|
||||||
Type: gomatrixserverlib.MDirectToDevice,
|
Type: gomatrixserverlib.MDirectToDevice,
|
||||||
Origin: string(t.ServerName),
|
Origin: string(originServerName),
|
||||||
}
|
}
|
||||||
tdm := gomatrixserverlib.ToDeviceMessage{
|
tdm := gomatrixserverlib.ToDeviceMessage{
|
||||||
Sender: ote.Sender,
|
Sender: ote.Sender,
|
||||||
|
|
@ -117,12 +121,13 @@ func (t *OutputSendToDeviceConsumer) onMessage(ctx context.Context, msgs []*nats
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if edu.Content, err = json.Marshal(tdm); err != nil {
|
if edu.Content, err = json.Marshal(tdm); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Sending send-to-device message into %q destination queue", destServerName)
|
log.Debugf("Sending send-to-device message into %q destination queue", destServerName)
|
||||||
if err := t.queues.SendEDU(edu, t.ServerName, []gomatrixserverlib.ServerName{destServerName}); err != nil {
|
if err := t.queues.SendEDU(edu, originServerName, []gomatrixserverlib.ServerName{destServerName}); err != nil {
|
||||||
log.WithError(err).Error("failed to send EDU")
|
log.WithError(err).Error("failed to send EDU")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ import (
|
||||||
|
|
||||||
// OutputTypingConsumer consumes events that originate in the clientapi.
|
// OutputTypingConsumer consumes events that originate in the clientapi.
|
||||||
type OutputTypingConsumer struct {
|
type OutputTypingConsumer struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
jetstream nats.JetStreamContext
|
jetstream nats.JetStreamContext
|
||||||
durable string
|
durable string
|
||||||
db storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
ServerName gomatrixserverlib.ServerName
|
isLocalServerName func(gomatrixserverlib.ServerName) bool
|
||||||
topic string
|
topic string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutputTypingConsumer creates a new OutputTypingConsumer. Call Start() to begin consuming typing events.
|
// NewOutputTypingConsumer creates a new OutputTypingConsumer. Call Start() to begin consuming typing events.
|
||||||
|
|
@ -49,13 +49,13 @@ func NewOutputTypingConsumer(
|
||||||
store storage.Database,
|
store storage.Database,
|
||||||
) *OutputTypingConsumer {
|
) *OutputTypingConsumer {
|
||||||
return &OutputTypingConsumer{
|
return &OutputTypingConsumer{
|
||||||
ctx: process.Context(),
|
ctx: process.Context(),
|
||||||
jetstream: js,
|
jetstream: js,
|
||||||
queues: queues,
|
queues: queues,
|
||||||
db: store,
|
db: store,
|
||||||
ServerName: cfg.Matrix.ServerName,
|
isLocalServerName: cfg.Matrix.IsLocalServerName,
|
||||||
durable: cfg.Matrix.JetStream.Durable("FederationAPITypingConsumer"),
|
durable: cfg.Matrix.JetStream.Durable("FederationAPITypingConsumer"),
|
||||||
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ func (t *OutputTypingConsumer) onMessage(ctx context.Context, msgs []*nats.Msg)
|
||||||
_ = msg.Ack()
|
_ = msg.Ack()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if typingServerName != t.ServerName {
|
if !t.isLocalServerName(typingServerName) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ func (t *OutputTypingConsumer) onMessage(ctx context.Context, msgs []*nats.Msg)
|
||||||
log.WithError(err).Error("failed to marshal EDU JSON")
|
log.WithError(err).Error("failed to marshal EDU JSON")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if err := t.queues.SendEDU(edu, t.ServerName, names); err != nil {
|
if err := t.queues.SendEDU(edu, typingServerName, names); err != nil {
|
||||||
log.WithError(err).Error("failed to send EDU")
|
log.WithError(err).Error("failed to send EDU")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func AddPublicRoutes(
|
||||||
TopicPresenceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
TopicPresenceEvent: cfg.Matrix.JetStream.Prefixed(jetstream.OutputPresenceEvent),
|
||||||
TopicDeviceListUpdate: cfg.Matrix.JetStream.Prefixed(jetstream.InputDeviceListUpdate),
|
TopicDeviceListUpdate: cfg.Matrix.JetStream.Prefixed(jetstream.InputDeviceListUpdate),
|
||||||
TopicSigningKeyUpdate: cfg.Matrix.JetStream.Prefixed(jetstream.InputSigningKeyUpdate),
|
TopicSigningKeyUpdate: cfg.Matrix.JetStream.Prefixed(jetstream.InputSigningKeyUpdate),
|
||||||
ServerName: cfg.Matrix.ServerName,
|
Config: cfg,
|
||||||
UserAPI: userAPI,
|
UserAPI: userAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ func NewInternalAPI(
|
||||||
) api.FederationInternalAPI {
|
) api.FederationInternalAPI {
|
||||||
cfg := &base.Cfg.FederationAPI
|
cfg := &base.Cfg.FederationAPI
|
||||||
|
|
||||||
federationDB, err := storage.NewDatabase(base, &cfg.Database, base.Caches, base.Cfg.Global.ServerName)
|
federationDB, err := storage.NewDatabase(base, &cfg.Database, base.Caches, base.Cfg.Global.IsLocalServerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Panic("failed to connect to federation sender db")
|
logrus.WithError(err).Panic("failed to connect to federation sender db")
|
||||||
}
|
}
|
||||||
|
|
@ -116,17 +116,14 @@ func NewInternalAPI(
|
||||||
_ = federationDB.RemoveAllServersFromBlacklist()
|
_ = federationDB.RemoveAllServersFromBlacklist()
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &statistics.Statistics{
|
stats := statistics.NewStatistics(federationDB, cfg.FederationMaxRetries+1)
|
||||||
DB: federationDB,
|
|
||||||
FailuresUntilBlacklist: cfg.FederationMaxRetries,
|
|
||||||
}
|
|
||||||
|
|
||||||
js, _ := base.NATS.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
|
js, _ := base.NATS.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
|
||||||
|
|
||||||
queues := queue.NewOutgoingQueues(
|
queues := queue.NewOutgoingQueues(
|
||||||
federationDB, base.ProcessContext,
|
federationDB, base.ProcessContext,
|
||||||
cfg.Matrix.DisableFederation,
|
cfg.Matrix.DisableFederation,
|
||||||
cfg.Matrix.ServerName, federation, rsAPI, stats,
|
cfg.Matrix.ServerName, federation, rsAPI, &stats,
|
||||||
&queue.SigningInfo{
|
&queue.SigningInfo{
|
||||||
KeyID: cfg.Matrix.KeyID,
|
KeyID: cfg.Matrix.KeyID,
|
||||||
PrivateKey: cfg.Matrix.PrivateKey,
|
PrivateKey: cfg.Matrix.PrivateKey,
|
||||||
|
|
@ -167,7 +164,7 @@ func NewInternalAPI(
|
||||||
}
|
}
|
||||||
|
|
||||||
presenceConsumer := consumers.NewOutputPresenceConsumer(
|
presenceConsumer := consumers.NewOutputPresenceConsumer(
|
||||||
base.ProcessContext, cfg, js, queues, federationDB,
|
base.ProcessContext, cfg, js, queues, federationDB, rsAPI,
|
||||||
)
|
)
|
||||||
if err = presenceConsumer.Start(); err != nil {
|
if err = presenceConsumer.Start(); err != nil {
|
||||||
logrus.WithError(err).Panic("failed to start presence consumer")
|
logrus.WithError(err).Panic("failed to start presence consumer")
|
||||||
|
|
@ -183,5 +180,5 @@ func NewInternalAPI(
|
||||||
}
|
}
|
||||||
time.AfterFunc(time.Minute, cleanExpiredEDUs)
|
time.AfterFunc(time.Minute, cleanExpiredEDUs)
|
||||||
|
|
||||||
return internal.NewFederationInternalAPI(federationDB, cfg, rsAPI, federation, stats, caches, queues, keyRing)
|
return internal.NewFederationInternalAPI(federationDB, cfg, rsAPI, federation, &stats, caches, queues, keyRing)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ func TestMain(m *testing.M) {
|
||||||
cfg.Global.JetStream.StoragePath = config.Path(d)
|
cfg.Global.JetStream.StoragePath = config.Path(d)
|
||||||
cfg.Global.KeyID = serverKeyID
|
cfg.Global.KeyID = serverKeyID
|
||||||
cfg.Global.KeyValidityPeriod = s.validity
|
cfg.Global.KeyValidityPeriod = s.validity
|
||||||
|
cfg.FederationAPI.KeyPerspectives = nil
|
||||||
f, err := os.CreateTemp(d, "federation_keys_test*.db")
|
f, err := os.CreateTemp(d, "federation_keys_test*.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1
|
return -1
|
||||||
|
|
@ -207,7 +208,6 @@ func TestRenewalBehaviour(t *testing.T) {
|
||||||
// happy at this point that the key that we already have is from the past
|
// happy at this point that the key that we already have is from the past
|
||||||
// then repeating a key fetch should cause us to try and renew the key.
|
// then repeating a key fetch should cause us to try and renew the key.
|
||||||
// If so, then the new key will end up in our cache.
|
// If so, then the new key will end up in our cache.
|
||||||
|
|
||||||
serverC.renew()
|
serverC.renew()
|
||||||
|
|
||||||
res, err = serverA.api.FetchKeys(
|
res, err = serverA.api.FetchKeys(
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ func TestFederationAPIJoinThenKeyUpdate(t *testing.T) {
|
||||||
func testFederationAPIJoinThenKeyUpdate(t *testing.T, dbType test.DBType) {
|
func testFederationAPIJoinThenKeyUpdate(t *testing.T, dbType test.DBType) {
|
||||||
base, close := testrig.CreateBaseDendrite(t, dbType)
|
base, close := testrig.CreateBaseDendrite(t, dbType)
|
||||||
base.Cfg.FederationAPI.PreferDirectFetch = true
|
base.Cfg.FederationAPI.PreferDirectFetch = true
|
||||||
|
base.Cfg.FederationAPI.KeyPerspectives = nil
|
||||||
defer close()
|
defer close()
|
||||||
jsctx, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream)
|
jsctx, _ := base.NATS.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream)
|
||||||
defer jetstream.DeleteAllStreams(jsctx, &base.Cfg.Global.JetStream)
|
defer jetstream.DeleteAllStreams(jsctx, &base.Cfg.Global.JetStream)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func (a *FederationInternalAPI) ClaimKeys(
|
||||||
) (gomatrixserverlib.RespClaimKeys, error) {
|
) (gomatrixserverlib.RespClaimKeys, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
|
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ires, err := a.doRequestIfNotBackingOffOrBlacklisted(s, func() (interface{}, error) {
|
ires, err := a.doRequestIfNotBlacklisted(s, func() (interface{}, error) {
|
||||||
return a.federation.ClaimKeys(ctx, s, oneTimeKeys)
|
return a.federation.ClaimKeys(ctx, s, oneTimeKeys)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ func (s *FederationInternalAPI) handleLocalKeys(
|
||||||
results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult,
|
results map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult,
|
||||||
) {
|
) {
|
||||||
for req := range requests {
|
for req := range requests {
|
||||||
if req.ServerName != s.cfg.Matrix.ServerName {
|
if !s.cfg.Matrix.IsLocalServerName(req.ServerName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if req.KeyID == s.cfg.Matrix.KeyID {
|
if req.KeyID == s.cfg.Matrix.KeyID {
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/api"
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/consumers"
|
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/version"
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/api"
|
||||||
|
"github.com/matrix-org/dendrite/federationapi/consumers"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PerformLeaveRequest implements api.FederationInternalAPI
|
// PerformLeaveRequest implements api.FederationInternalAPI
|
||||||
|
|
@ -76,7 +77,7 @@ func (r *FederationInternalAPI) PerformJoin(
|
||||||
seenSet := make(map[gomatrixserverlib.ServerName]bool)
|
seenSet := make(map[gomatrixserverlib.ServerName]bool)
|
||||||
var uniqueList []gomatrixserverlib.ServerName
|
var uniqueList []gomatrixserverlib.ServerName
|
||||||
for _, srv := range request.ServerNames {
|
for _, srv := range request.ServerNames {
|
||||||
if seenSet[srv] || srv == r.cfg.Matrix.ServerName {
|
if seenSet[srv] || r.cfg.Matrix.IsLocalServerName(srv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenSet[srv] = true
|
seenSet[srv] = true
|
||||||
|
|
@ -95,6 +96,7 @@ func (r *FederationInternalAPI) PerformJoin(
|
||||||
request.Content,
|
request.Content,
|
||||||
serverName,
|
serverName,
|
||||||
supportedVersions,
|
supportedVersions,
|
||||||
|
request.Unsigned,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
logrus.WithError(err).WithFields(logrus.Fields{
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
"server_name": serverName,
|
"server_name": serverName,
|
||||||
|
|
@ -139,6 +141,7 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
content map[string]interface{},
|
content map[string]interface{},
|
||||||
serverName gomatrixserverlib.ServerName,
|
serverName gomatrixserverlib.ServerName,
|
||||||
supportedVersions []gomatrixserverlib.RoomVersion,
|
supportedVersions []gomatrixserverlib.RoomVersion,
|
||||||
|
unsigned map[string]interface{},
|
||||||
) error {
|
) error {
|
||||||
// Try to perform a make_join using the information supplied in the
|
// Try to perform a make_join using the information supplied in the
|
||||||
// request.
|
// request.
|
||||||
|
|
@ -259,7 +262,7 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("JoinedHostsFromEvents: failed to get joined hosts: %s", err)
|
return fmt.Errorf("JoinedHostsFromEvents: failed to get joined hosts: %s", err)
|
||||||
}
|
}
|
||||||
logrus.WithField("hosts", joinedHosts).WithField("room", roomID).Info("Joined federated room with hosts")
|
logrus.WithField("room", roomID).Infof("Joined federated room with %d hosts", len(joinedHosts))
|
||||||
if _, err = r.db.UpdateRoom(context.Background(), roomID, joinedHosts, nil, true); err != nil {
|
if _, err = r.db.UpdateRoom(context.Background(), roomID, joinedHosts, nil, true); err != nil {
|
||||||
return fmt.Errorf("UpdatedRoom: failed to update room with joined hosts: %s", err)
|
return fmt.Errorf("UpdatedRoom: failed to update room with joined hosts: %s", err)
|
||||||
}
|
}
|
||||||
|
|
@ -267,6 +270,14 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
||||||
// If we successfully performed a send_join above then the other
|
// If we successfully performed a send_join above then the other
|
||||||
// server now thinks we're a part of the room. Send the newly
|
// server now thinks we're a part of the room. Send the newly
|
||||||
// returned state to the roomserver to update our local view.
|
// returned state to the roomserver to update our local view.
|
||||||
|
if unsigned != nil {
|
||||||
|
event, err = event.SetUnsigned(unsigned)
|
||||||
|
if err != nil {
|
||||||
|
// non-fatal, log and continue
|
||||||
|
logrus.WithError(err).Errorf("Failed to set unsigned content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = roomserverAPI.SendEventWithState(
|
if err = roomserverAPI.SendEventWithState(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
r.rsAPI,
|
r.rsAPI,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
"github.com/matrix-org/dendrite/setup/jetstream"
|
"github.com/matrix-org/dendrite/setup/jetstream"
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
userapi "github.com/matrix-org/dendrite/userapi/api"
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
|
@ -39,7 +40,7 @@ type SyncAPIProducer struct {
|
||||||
TopicDeviceListUpdate string
|
TopicDeviceListUpdate string
|
||||||
TopicSigningKeyUpdate string
|
TopicSigningKeyUpdate string
|
||||||
JetStream nats.JetStreamContext
|
JetStream nats.JetStreamContext
|
||||||
ServerName gomatrixserverlib.ServerName
|
Config *config.FederationAPI
|
||||||
UserAPI userapi.UserInternalAPI
|
UserAPI userapi.UserInternalAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +78,7 @@ func (p *SyncAPIProducer) SendToDevice(
|
||||||
// device. If the event isn't targeted locally then we can't expand the
|
// device. If the event isn't targeted locally then we can't expand the
|
||||||
// wildcard as we don't know about the remote devices, so instead we leave it
|
// wildcard as we don't know about the remote devices, so instead we leave it
|
||||||
// as-is, so that the federation sender can send it on with the wildcard intact.
|
// as-is, so that the federation sender can send it on with the wildcard intact.
|
||||||
if domain == p.ServerName && deviceID == "*" {
|
if p.Config.Matrix.IsLocalServerName(domain) && deviceID == "*" {
|
||||||
var res userapi.QueryDevicesResponse
|
var res userapi.QueryDevicesResponse
|
||||||
err = p.UserAPI.QueryDevices(context.TODO(), &userapi.QueryDevicesRequest{
|
err = p.UserAPI.QueryDevices(context.TODO(), &userapi.QueryDevicesRequest{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,22 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
fedapi "github.com/matrix-org/dendrite/federationapi/api"
|
fedapi "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"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"
|
"github.com/matrix-org/dendrite/federationapi/storage/shared"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/setup/process"
|
"github.com/matrix-org/dendrite/setup/process"
|
||||||
"github.com/matrix-org/gomatrix"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxPDUsPerTransaction = 50
|
maxPDUsPerTransaction = 50
|
||||||
maxEDUsPerTransaction = 50
|
maxEDUsPerTransaction = 100
|
||||||
maxPDUsInMemory = 128
|
maxPDUsInMemory = 128
|
||||||
maxEDUsInMemory = 128
|
maxEDUsInMemory = 128
|
||||||
queueIdleTimeout = time.Second * 30
|
queueIdleTimeout = time.Second * 30
|
||||||
|
|
@ -64,7 +65,6 @@ type destinationQueue struct {
|
||||||
pendingPDUs []*queuedPDU // PDUs waiting to be sent
|
pendingPDUs []*queuedPDU // PDUs waiting to be sent
|
||||||
pendingEDUs []*queuedEDU // EDUs waiting to be sent
|
pendingEDUs []*queuedEDU // EDUs waiting to be sent
|
||||||
pendingMutex sync.RWMutex // protects pendingPDUs and pendingEDUs
|
pendingMutex sync.RWMutex // protects pendingPDUs and pendingEDUs
|
||||||
interruptBackoff chan bool // interrupts backoff
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send event adds the event to the pending queue for the destination.
|
// Send event adds the event to the pending queue for the destination.
|
||||||
|
|
@ -75,18 +75,7 @@ func (oq *destinationQueue) sendEvent(event *gomatrixserverlib.HeaderedEvent, re
|
||||||
logrus.Errorf("attempt to send nil PDU with destination %q", oq.destination)
|
logrus.Errorf("attempt to send nil PDU with destination %q", oq.destination)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Create a database entry that associates the given PDU NID with
|
|
||||||
// this destination queue. We'll then be able to retrieve the PDU
|
|
||||||
// later.
|
|
||||||
if err := oq.db.AssociatePDUWithDestination(
|
|
||||||
oq.process.Context(),
|
|
||||||
"", // TODO: remove this, as we don't need to persist the transaction ID
|
|
||||||
oq.destination, // the destination server name
|
|
||||||
receipt, // NIDs from federationapi_queue_json table
|
|
||||||
); err != nil {
|
|
||||||
logrus.WithError(err).Errorf("failed to associate PDU %q with destination %q", event.EventID(), oq.destination)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check if the destination is blacklisted. If it isn't then wake
|
// Check if the destination is blacklisted. If it isn't then wake
|
||||||
// up the queue.
|
// up the queue.
|
||||||
if !oq.statistics.Blacklisted() {
|
if !oq.statistics.Blacklisted() {
|
||||||
|
|
@ -102,11 +91,9 @@ func (oq *destinationQueue) sendEvent(event *gomatrixserverlib.HeaderedEvent, re
|
||||||
oq.overflowed.Store(true)
|
oq.overflowed.Store(true)
|
||||||
}
|
}
|
||||||
oq.pendingMutex.Unlock()
|
oq.pendingMutex.Unlock()
|
||||||
// Wake up the queue if it's asleep.
|
|
||||||
oq.wakeQueueIfNeeded()
|
if !oq.backingOff.Load() {
|
||||||
select {
|
oq.wakeQueueAndNotify()
|
||||||
case oq.notify <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,19 +106,7 @@ func (oq *destinationQueue) sendEDU(event *gomatrixserverlib.EDU, receipt *share
|
||||||
logrus.Errorf("attempt to send nil EDU with destination %q", oq.destination)
|
logrus.Errorf("attempt to send nil EDU with destination %q", oq.destination)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Create a database entry that associates the given PDU NID with
|
|
||||||
// this destination queue. We'll then be able to retrieve the PDU
|
|
||||||
// later.
|
|
||||||
if err := oq.db.AssociateEDUWithDestination(
|
|
||||||
oq.process.Context(),
|
|
||||||
oq.destination, // the destination server name
|
|
||||||
receipt, // NIDs from federationapi_queue_json table
|
|
||||||
event.Type,
|
|
||||||
nil, // this will use the default expireEDUTypes map
|
|
||||||
); err != nil {
|
|
||||||
logrus.WithError(err).Errorf("failed to associate EDU with destination %q", oq.destination)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check if the destination is blacklisted. If it isn't then wake
|
// Check if the destination is blacklisted. If it isn't then wake
|
||||||
// up the queue.
|
// up the queue.
|
||||||
if !oq.statistics.Blacklisted() {
|
if !oq.statistics.Blacklisted() {
|
||||||
|
|
@ -147,24 +122,48 @@ func (oq *destinationQueue) sendEDU(event *gomatrixserverlib.EDU, receipt *share
|
||||||
oq.overflowed.Store(true)
|
oq.overflowed.Store(true)
|
||||||
}
|
}
|
||||||
oq.pendingMutex.Unlock()
|
oq.pendingMutex.Unlock()
|
||||||
// Wake up the queue if it's asleep.
|
|
||||||
oq.wakeQueueIfNeeded()
|
if !oq.backingOff.Load() {
|
||||||
select {
|
oq.wakeQueueAndNotify()
|
||||||
case oq.notify <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleBackoffNotifier is registered as the backoff notification
|
||||||
|
// callback with Statistics. It will wakeup and notify the queue
|
||||||
|
// if the queue is currently backing off.
|
||||||
|
func (oq *destinationQueue) handleBackoffNotifier() {
|
||||||
|
// Only wake up the queue if it is backing off.
|
||||||
|
// Otherwise there is no pending work for the queue to handle
|
||||||
|
// so waking the queue would be a waste of resources.
|
||||||
|
if oq.backingOff.Load() {
|
||||||
|
oq.wakeQueueAndNotify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wakeQueueAndNotify ensures the destination queue is running and notifies it
|
||||||
|
// that there is pending work.
|
||||||
|
func (oq *destinationQueue) wakeQueueAndNotify() {
|
||||||
|
// Wake up the queue if it's asleep.
|
||||||
|
oq.wakeQueueIfNeeded()
|
||||||
|
|
||||||
|
// Notify the queue that there are events ready to send.
|
||||||
|
select {
|
||||||
|
case oq.notify <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// wakeQueueIfNeeded will wake up the destination queue if it is
|
// wakeQueueIfNeeded will wake up the destination queue if it is
|
||||||
// not already running. If it is running but it is backing off
|
// not already running. If it is running but it is backing off
|
||||||
// then we will interrupt the backoff, causing any federation
|
// then we will interrupt the backoff, causing any federation
|
||||||
// requests to retry.
|
// requests to retry.
|
||||||
func (oq *destinationQueue) wakeQueueIfNeeded() {
|
func (oq *destinationQueue) wakeQueueIfNeeded() {
|
||||||
// If we are backing off then interrupt the backoff.
|
// Clear the backingOff flag and update the backoff metrics if it was set.
|
||||||
if oq.backingOff.CompareAndSwap(true, false) {
|
if oq.backingOff.CompareAndSwap(true, false) {
|
||||||
oq.interruptBackoff <- true
|
destinationQueueBackingOff.Dec()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we aren't running then wake up the queue.
|
// If we aren't running then wake up the queue.
|
||||||
if !oq.running.Load() {
|
if !oq.running.Load() {
|
||||||
// Start the queue.
|
// Start the queue.
|
||||||
|
|
@ -196,38 +195,54 @@ func (oq *destinationQueue) getPendingFromDatabase() {
|
||||||
gotEDUs[edu.receipt.String()] = struct{}{}
|
gotEDUs[edu.receipt.String()] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overflowed := false
|
||||||
if pduCapacity := maxPDUsInMemory - len(oq.pendingPDUs); pduCapacity > 0 {
|
if pduCapacity := maxPDUsInMemory - len(oq.pendingPDUs); pduCapacity > 0 {
|
||||||
// We have room in memory for some PDUs - let's request no more than that.
|
// We have room in memory for some PDUs - let's request no more than that.
|
||||||
if pdus, err := oq.db.GetPendingPDUs(ctx, oq.destination, pduCapacity); err == nil {
|
if pdus, err := oq.db.GetPendingPDUs(ctx, oq.destination, maxPDUsInMemory); err == nil {
|
||||||
|
if len(pdus) == maxPDUsInMemory {
|
||||||
|
overflowed = true
|
||||||
|
}
|
||||||
for receipt, pdu := range pdus {
|
for receipt, pdu := range pdus {
|
||||||
if _, ok := gotPDUs[receipt.String()]; ok {
|
if _, ok := gotPDUs[receipt.String()]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oq.pendingPDUs = append(oq.pendingPDUs, &queuedPDU{receipt, pdu})
|
oq.pendingPDUs = append(oq.pendingPDUs, &queuedPDU{receipt, pdu})
|
||||||
retrieved = true
|
retrieved = true
|
||||||
|
if len(oq.pendingPDUs) == maxPDUsInMemory {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logrus.WithError(err).Errorf("Failed to get pending PDUs for %q", oq.destination)
|
logrus.WithError(err).Errorf("Failed to get pending PDUs for %q", oq.destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if eduCapacity := maxEDUsInMemory - len(oq.pendingEDUs); eduCapacity > 0 {
|
if eduCapacity := maxEDUsInMemory - len(oq.pendingEDUs); eduCapacity > 0 {
|
||||||
// We have room in memory for some EDUs - let's request no more than that.
|
// We have room in memory for some EDUs - let's request no more than that.
|
||||||
if edus, err := oq.db.GetPendingEDUs(ctx, oq.destination, eduCapacity); err == nil {
|
if edus, err := oq.db.GetPendingEDUs(ctx, oq.destination, maxEDUsInMemory); err == nil {
|
||||||
|
if len(edus) == maxEDUsInMemory {
|
||||||
|
overflowed = true
|
||||||
|
}
|
||||||
for receipt, edu := range edus {
|
for receipt, edu := range edus {
|
||||||
if _, ok := gotEDUs[receipt.String()]; ok {
|
if _, ok := gotEDUs[receipt.String()]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oq.pendingEDUs = append(oq.pendingEDUs, &queuedEDU{receipt, edu})
|
oq.pendingEDUs = append(oq.pendingEDUs, &queuedEDU{receipt, edu})
|
||||||
retrieved = true
|
retrieved = true
|
||||||
|
if len(oq.pendingEDUs) == maxEDUsInMemory {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logrus.WithError(err).Errorf("Failed to get pending EDUs for %q", oq.destination)
|
logrus.WithError(err).Errorf("Failed to get pending EDUs for %q", oq.destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've retrieved all of the events from the database with room to spare
|
// If we've retrieved all of the events from the database with room to spare
|
||||||
// in memory then we'll no longer consider this queue to be overflowed.
|
// in memory then we'll no longer consider this queue to be overflowed.
|
||||||
if len(oq.pendingPDUs) < maxPDUsInMemory && len(oq.pendingEDUs) < maxEDUsInMemory {
|
if !overflowed {
|
||||||
oq.overflowed.Store(false)
|
oq.overflowed.Store(false)
|
||||||
|
} else {
|
||||||
}
|
}
|
||||||
// If we've retrieved some events then notify the destination queue goroutine.
|
// If we've retrieved some events then notify the destination queue goroutine.
|
||||||
if retrieved {
|
if retrieved {
|
||||||
|
|
@ -238,6 +253,24 @@ func (oq *destinationQueue) getPendingFromDatabase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkNotificationsOnClose checks for any remaining notifications
|
||||||
|
// and starts a new backgroundSend goroutine if any exist.
|
||||||
|
func (oq *destinationQueue) checkNotificationsOnClose() {
|
||||||
|
// NOTE : If we are stopping the queue due to blacklist then it
|
||||||
|
// doesn't matter if we have been notified of new work since
|
||||||
|
// this queue instance will be deleted anyway.
|
||||||
|
if !oq.statistics.Blacklisted() {
|
||||||
|
select {
|
||||||
|
case <-oq.notify:
|
||||||
|
// We received a new notification in between the
|
||||||
|
// idle timeout firing and stopping the goroutine.
|
||||||
|
// Immediately restart the queue.
|
||||||
|
oq.wakeQueueAndNotify()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// backgroundSend is the worker goroutine for sending events.
|
// backgroundSend is the worker goroutine for sending events.
|
||||||
func (oq *destinationQueue) backgroundSend() {
|
func (oq *destinationQueue) backgroundSend() {
|
||||||
// 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
|
||||||
|
|
@ -245,10 +278,17 @@ func (oq *destinationQueue) backgroundSend() {
|
||||||
if !oq.running.CompareAndSwap(false, true) {
|
if !oq.running.CompareAndSwap(false, true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register queue cleanup functions.
|
||||||
|
// NOTE : The ordering here is very intentional.
|
||||||
|
defer oq.checkNotificationsOnClose()
|
||||||
|
defer oq.running.Store(false)
|
||||||
|
|
||||||
destinationQueueRunning.Inc()
|
destinationQueueRunning.Inc()
|
||||||
defer destinationQueueRunning.Dec()
|
defer destinationQueueRunning.Dec()
|
||||||
defer oq.queues.clearQueue(oq)
|
|
||||||
defer oq.running.Store(false)
|
idleTimeout := time.NewTimer(queueIdleTimeout)
|
||||||
|
defer idleTimeout.Stop()
|
||||||
|
|
||||||
// Mark the queue as overflowed, so we will consult the database
|
// Mark the queue as overflowed, so we will consult the database
|
||||||
// to see if there's anything new to send.
|
// to see if there's anything new to send.
|
||||||
|
|
@ -261,59 +301,33 @@ func (oq *destinationQueue) backgroundSend() {
|
||||||
oq.getPendingFromDatabase()
|
oq.getPendingFromDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the queue idle timeout.
|
||||||
|
if !idleTimeout.Stop() {
|
||||||
|
select {
|
||||||
|
case <-idleTimeout.C:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idleTimeout.Reset(queueIdleTimeout)
|
||||||
|
|
||||||
// If we have nothing to do then wait either for incoming events, or
|
// If we have nothing to do then wait either for incoming events, or
|
||||||
// until we hit an idle timeout.
|
// until we hit an idle timeout.
|
||||||
select {
|
select {
|
||||||
case <-oq.notify:
|
case <-oq.notify:
|
||||||
// There's work to do, either because getPendingFromDatabase
|
// There's work to do, either because getPendingFromDatabase
|
||||||
// told us there is, or because a new event has come in via
|
// told us there is, a new event has come in via sendEvent/sendEDU,
|
||||||
// sendEvent/sendEDU.
|
// or we are backing off and it is time to retry.
|
||||||
case <-time.After(queueIdleTimeout):
|
case <-idleTimeout.C:
|
||||||
// The worker is idle so stop the goroutine. It'll get
|
// The worker is idle so stop the goroutine. It'll get
|
||||||
// restarted automatically the next time we have an event to
|
// restarted automatically the next time we have an event to
|
||||||
// send.
|
// send.
|
||||||
return
|
return
|
||||||
case <-oq.process.Context().Done():
|
case <-oq.process.Context().Done():
|
||||||
// The parent process is shutting down, so stop.
|
// The parent process is shutting down, so stop.
|
||||||
|
oq.statistics.ClearBackoff()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are backing off this server then wait for the
|
|
||||||
// backoff duration to complete first, or until explicitly
|
|
||||||
// told to retry.
|
|
||||||
until, blacklisted := oq.statistics.BackoffInfo()
|
|
||||||
if blacklisted {
|
|
||||||
// It's been suggested that we should give up because the backoff
|
|
||||||
// has exceeded a maximum allowable value. Clean up the in-memory
|
|
||||||
// buffers at this point. The PDU clean-up is already on a defer.
|
|
||||||
logrus.Warnf("Blacklisting %q due to exceeding backoff threshold", oq.destination)
|
|
||||||
oq.pendingMutex.Lock()
|
|
||||||
for i := range oq.pendingPDUs {
|
|
||||||
oq.pendingPDUs[i] = nil
|
|
||||||
}
|
|
||||||
for i := range oq.pendingEDUs {
|
|
||||||
oq.pendingEDUs[i] = nil
|
|
||||||
}
|
|
||||||
oq.pendingPDUs = nil
|
|
||||||
oq.pendingEDUs = nil
|
|
||||||
oq.pendingMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if until != nil && until.After(time.Now()) {
|
|
||||||
// We haven't backed off yet, so wait for the suggested amount of
|
|
||||||
// time.
|
|
||||||
duration := time.Until(*until)
|
|
||||||
logrus.Debugf("Backing off %q for %s", oq.destination, duration)
|
|
||||||
oq.backingOff.Store(true)
|
|
||||||
destinationQueueBackingOff.Inc()
|
|
||||||
select {
|
|
||||||
case <-time.After(duration):
|
|
||||||
case <-oq.interruptBackoff:
|
|
||||||
}
|
|
||||||
destinationQueueBackingOff.Dec()
|
|
||||||
oq.backingOff.Store(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Work out which PDUs/EDUs to include in the next transaction.
|
// Work out which PDUs/EDUs to include in the next transaction.
|
||||||
oq.pendingMutex.RLock()
|
oq.pendingMutex.RLock()
|
||||||
pduCount := len(oq.pendingPDUs)
|
pduCount := len(oq.pendingPDUs)
|
||||||
|
|
@ -328,99 +342,52 @@ func (oq *destinationQueue) backgroundSend() {
|
||||||
toSendEDUs := oq.pendingEDUs[:eduCount]
|
toSendEDUs := oq.pendingEDUs[:eduCount]
|
||||||
oq.pendingMutex.RUnlock()
|
oq.pendingMutex.RUnlock()
|
||||||
|
|
||||||
|
// If we didn't get anything from the database and there are no
|
||||||
|
// pending EDUs then there's nothing to do - stop here.
|
||||||
|
if pduCount == 0 && eduCount == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If we have pending PDUs or EDUs then construct a transaction.
|
// If we have pending PDUs or EDUs then construct a transaction.
|
||||||
// Try sending the next transaction and see what happens.
|
// Try sending the next transaction and see what happens.
|
||||||
transaction, pc, ec, terr := oq.nextTransaction(toSendPDUs, toSendEDUs)
|
terr := oq.nextTransaction(toSendPDUs, toSendEDUs)
|
||||||
if terr != nil {
|
if terr != nil {
|
||||||
// We failed to send the transaction. Mark it as a failure.
|
// We failed to send the transaction. Mark it as a failure.
|
||||||
oq.statistics.Failure()
|
_, blacklisted := oq.statistics.Failure()
|
||||||
|
if !blacklisted {
|
||||||
} else if transaction {
|
// Register the backoff state and exit the goroutine.
|
||||||
// If we successfully sent the transaction then clear out
|
// It'll get restarted automatically when the backoff
|
||||||
// the pending events and EDUs, and wipe our transaction ID.
|
// completes.
|
||||||
oq.statistics.Success()
|
oq.backingOff.Store(true)
|
||||||
oq.pendingMutex.Lock()
|
destinationQueueBackingOff.Inc()
|
||||||
for i := range oq.pendingPDUs[:pc] {
|
return
|
||||||
oq.pendingPDUs[i] = nil
|
} else {
|
||||||
|
// Immediately trigger the blacklist logic.
|
||||||
|
oq.blacklistDestination()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for i := range oq.pendingEDUs[:ec] {
|
} else {
|
||||||
oq.pendingEDUs[i] = nil
|
oq.handleTransactionSuccess(pduCount, eduCount)
|
||||||
}
|
|
||||||
oq.pendingPDUs = oq.pendingPDUs[pc:]
|
|
||||||
oq.pendingEDUs = oq.pendingEDUs[ec:]
|
|
||||||
oq.pendingMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextTransaction creates a new transaction from the pending event
|
// nextTransaction creates a new transaction from the pending event
|
||||||
// queue and sends it. Returns true if a transaction was sent or
|
// queue and sends it.
|
||||||
// false otherwise.
|
// Returns an error if the transaction wasn't sent.
|
||||||
func (oq *destinationQueue) nextTransaction(
|
func (oq *destinationQueue) nextTransaction(
|
||||||
pdus []*queuedPDU,
|
pdus []*queuedPDU,
|
||||||
edus []*queuedEDU,
|
edus []*queuedEDU,
|
||||||
) (bool, int, int, error) {
|
) error {
|
||||||
// If there's no projected transaction ID then generate one. If
|
|
||||||
// the transaction succeeds then we'll set it back to "" so that
|
|
||||||
// we generate a new one next time. If it fails, we'll preserve
|
|
||||||
// it so that we retry with the same transaction ID.
|
|
||||||
oq.transactionIDMutex.Lock()
|
|
||||||
if oq.transactionID == "" {
|
|
||||||
now := gomatrixserverlib.AsTimestamp(time.Now())
|
|
||||||
oq.transactionID = gomatrixserverlib.TransactionID(fmt.Sprintf("%d-%d", now, oq.statistics.SuccessCount()))
|
|
||||||
}
|
|
||||||
oq.transactionIDMutex.Unlock()
|
|
||||||
|
|
||||||
// Create the transaction.
|
// Create the transaction.
|
||||||
t := gomatrixserverlib.Transaction{
|
t, pduReceipts, eduReceipts := oq.createTransaction(pdus, edus)
|
||||||
PDUs: []json.RawMessage{},
|
|
||||||
EDUs: []gomatrixserverlib.EDU{},
|
|
||||||
}
|
|
||||||
t.Origin = oq.origin
|
|
||||||
t.Destination = oq.destination
|
|
||||||
t.OriginServerTS = gomatrixserverlib.AsTimestamp(time.Now())
|
|
||||||
t.TransactionID = oq.transactionID
|
|
||||||
|
|
||||||
// If we didn't get anything from the database and there are no
|
|
||||||
// pending EDUs then there's nothing to do - stop here.
|
|
||||||
if len(pdus) == 0 && len(edus) == 0 {
|
|
||||||
return false, 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pduReceipts []*shared.Receipt
|
|
||||||
var eduReceipts []*shared.Receipt
|
|
||||||
|
|
||||||
// Go through PDUs that we retrieved from the database, if any,
|
|
||||||
// and add them into the transaction.
|
|
||||||
for _, pdu := range pdus {
|
|
||||||
if pdu == nil || pdu.pdu == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Append the JSON of the event, since this is a json.RawMessage type in the
|
|
||||||
// gomatrixserverlib.Transaction struct
|
|
||||||
t.PDUs = append(t.PDUs, pdu.pdu.JSON())
|
|
||||||
pduReceipts = append(pduReceipts, pdu.receipt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the same for pending EDUS in the queue.
|
|
||||||
for _, edu := range edus {
|
|
||||||
if edu == nil || edu.edu == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.EDUs = append(t.EDUs, *edu.edu)
|
|
||||||
eduReceipts = append(eduReceipts, edu.receipt)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithField("server_name", oq.destination).Debugf("Sending transaction %q containing %d PDUs, %d EDUs", t.TransactionID, len(t.PDUs), len(t.EDUs))
|
logrus.WithField("server_name", oq.destination).Debugf("Sending transaction %q containing %d PDUs, %d EDUs", t.TransactionID, len(t.PDUs), len(t.EDUs))
|
||||||
|
|
||||||
// Try to send the transaction to the destination server.
|
// Try to send the transaction to the destination server.
|
||||||
// TODO: we should check for 500-ish fails vs 400-ish here,
|
|
||||||
// since we shouldn't queue things indefinitely in response
|
|
||||||
// to a 400-ish error
|
|
||||||
ctx, cancel := context.WithTimeout(oq.process.Context(), time.Minute*5)
|
ctx, cancel := context.WithTimeout(oq.process.Context(), time.Minute*5)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
_, err := oq.client.SendTransaction(ctx, t)
|
_, err := oq.client.SendTransaction(ctx, t)
|
||||||
switch err.(type) {
|
switch errResponse := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
// Clean up the transaction in the database.
|
// Clean up the transaction in the database.
|
||||||
if pduReceipts != nil {
|
if pduReceipts != nil {
|
||||||
|
|
@ -439,16 +406,129 @@ func (oq *destinationQueue) nextTransaction(
|
||||||
oq.transactionIDMutex.Lock()
|
oq.transactionIDMutex.Lock()
|
||||||
oq.transactionID = ""
|
oq.transactionID = ""
|
||||||
oq.transactionIDMutex.Unlock()
|
oq.transactionIDMutex.Unlock()
|
||||||
return true, len(t.PDUs), len(t.EDUs), nil
|
return nil
|
||||||
case gomatrix.HTTPError:
|
case gomatrix.HTTPError:
|
||||||
// Report that we failed to send the transaction and we
|
// Report that we failed to send the transaction and we
|
||||||
// will retry again, subject to backoff.
|
// will retry again, subject to backoff.
|
||||||
return false, 0, 0, err
|
|
||||||
|
// TODO: we should check for 500-ish fails vs 400-ish here,
|
||||||
|
// since we shouldn't queue things indefinitely in response
|
||||||
|
// to a 400-ish error
|
||||||
|
code := errResponse.Code
|
||||||
|
logrus.Debug("Transaction failed with HTTP", code)
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"destination": oq.destination,
|
"destination": oq.destination,
|
||||||
logrus.ErrorKey: err,
|
logrus.ErrorKey: err,
|
||||||
}).Debugf("Failed to send transaction %q", t.TransactionID)
|
}).Debugf("Failed to send transaction %q", t.TransactionID)
|
||||||
return false, 0, 0, err
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTransaction generates a gomatrixserverlib.Transaction from the provided pdus and edus.
|
||||||
|
// It also returns the associated event receipts so they can be cleaned from the database in
|
||||||
|
// the case of a successful transaction.
|
||||||
|
func (oq *destinationQueue) createTransaction(
|
||||||
|
pdus []*queuedPDU,
|
||||||
|
edus []*queuedEDU,
|
||||||
|
) (gomatrixserverlib.Transaction, []*shared.Receipt, []*shared.Receipt) {
|
||||||
|
// If there's no projected transaction ID then generate one. If
|
||||||
|
// the transaction succeeds then we'll set it back to "" so that
|
||||||
|
// we generate a new one next time. If it fails, we'll preserve
|
||||||
|
// it so that we retry with the same transaction ID.
|
||||||
|
oq.transactionIDMutex.Lock()
|
||||||
|
if oq.transactionID == "" {
|
||||||
|
now := gomatrixserverlib.AsTimestamp(time.Now())
|
||||||
|
oq.transactionID = gomatrixserverlib.TransactionID(fmt.Sprintf("%d-%d", now, oq.statistics.SuccessCount()))
|
||||||
|
}
|
||||||
|
oq.transactionIDMutex.Unlock()
|
||||||
|
|
||||||
|
t := gomatrixserverlib.Transaction{
|
||||||
|
PDUs: []json.RawMessage{},
|
||||||
|
EDUs: []gomatrixserverlib.EDU{},
|
||||||
|
}
|
||||||
|
t.Origin = oq.origin
|
||||||
|
t.Destination = oq.destination
|
||||||
|
t.OriginServerTS = gomatrixserverlib.AsTimestamp(time.Now())
|
||||||
|
t.TransactionID = oq.transactionID
|
||||||
|
|
||||||
|
var pduReceipts []*shared.Receipt
|
||||||
|
var eduReceipts []*shared.Receipt
|
||||||
|
|
||||||
|
// Go through PDUs that we retrieved from the database, if any,
|
||||||
|
// and add them into the transaction.
|
||||||
|
for _, pdu := range pdus {
|
||||||
|
// These should never be nil.
|
||||||
|
if pdu == nil || pdu.pdu == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Append the JSON of the event, since this is a json.RawMessage type in the
|
||||||
|
// gomatrixserverlib.Transaction struct
|
||||||
|
t.PDUs = append(t.PDUs, pdu.pdu.JSON())
|
||||||
|
pduReceipts = append(pduReceipts, pdu.receipt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same for pending EDUS in the queue.
|
||||||
|
for _, edu := range edus {
|
||||||
|
// These should never be nil.
|
||||||
|
if edu == nil || edu.edu == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.EDUs = append(t.EDUs, *edu.edu)
|
||||||
|
eduReceipts = append(eduReceipts, edu.receipt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, pduReceipts, eduReceipts
|
||||||
|
}
|
||||||
|
|
||||||
|
// blacklistDestination removes all pending PDUs and EDUs that have been cached
|
||||||
|
// and deletes this queue.
|
||||||
|
func (oq *destinationQueue) blacklistDestination() {
|
||||||
|
// It's been suggested that we should give up because the backoff
|
||||||
|
// has exceeded a maximum allowable value. Clean up the in-memory
|
||||||
|
// buffers at this point. The PDU clean-up is already on a defer.
|
||||||
|
logrus.Warnf("Blacklisting %q due to exceeding backoff threshold", oq.destination)
|
||||||
|
|
||||||
|
oq.pendingMutex.Lock()
|
||||||
|
for i := range oq.pendingPDUs {
|
||||||
|
oq.pendingPDUs[i] = nil
|
||||||
|
}
|
||||||
|
for i := range oq.pendingEDUs {
|
||||||
|
oq.pendingEDUs[i] = nil
|
||||||
|
}
|
||||||
|
oq.pendingPDUs = nil
|
||||||
|
oq.pendingEDUs = nil
|
||||||
|
oq.pendingMutex.Unlock()
|
||||||
|
|
||||||
|
// Delete this queue as no more messages will be sent to this
|
||||||
|
// destination until it is no longer blacklisted.
|
||||||
|
oq.statistics.AssignBackoffNotifier(nil)
|
||||||
|
oq.queues.clearQueue(oq)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTransactionSuccess updates the cached event queues as well as the success and
|
||||||
|
// backoff information for this server.
|
||||||
|
func (oq *destinationQueue) handleTransactionSuccess(pduCount int, eduCount int) {
|
||||||
|
// If we successfully sent the transaction then clear out
|
||||||
|
// the pending events and EDUs, and wipe our transaction ID.
|
||||||
|
oq.statistics.Success()
|
||||||
|
oq.pendingMutex.Lock()
|
||||||
|
defer oq.pendingMutex.Unlock()
|
||||||
|
|
||||||
|
for i := range oq.pendingPDUs[:pduCount] {
|
||||||
|
oq.pendingPDUs[i] = nil
|
||||||
|
}
|
||||||
|
for i := range oq.pendingEDUs[:eduCount] {
|
||||||
|
oq.pendingEDUs[i] = nil
|
||||||
|
}
|
||||||
|
oq.pendingPDUs = oq.pendingPDUs[pduCount:]
|
||||||
|
oq.pendingEDUs = oq.pendingEDUs[eduCount:]
|
||||||
|
|
||||||
|
if len(oq.pendingPDUs) > 0 || len(oq.pendingEDUs) > 0 {
|
||||||
|
select {
|
||||||
|
case oq.notify <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
|
|
@ -161,23 +163,25 @@ func (oqs *OutgoingQueues) getQueue(destination gomatrixserverlib.ServerName) *d
|
||||||
if !ok || oq == nil {
|
if !ok || oq == nil {
|
||||||
destinationQueueTotal.Inc()
|
destinationQueueTotal.Inc()
|
||||||
oq = &destinationQueue{
|
oq = &destinationQueue{
|
||||||
queues: oqs,
|
queues: oqs,
|
||||||
db: oqs.db,
|
db: oqs.db,
|
||||||
process: oqs.process,
|
process: oqs.process,
|
||||||
rsAPI: oqs.rsAPI,
|
rsAPI: oqs.rsAPI,
|
||||||
origin: oqs.origin,
|
origin: oqs.origin,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
client: oqs.client,
|
client: oqs.client,
|
||||||
statistics: oqs.statistics.ForServer(destination),
|
statistics: oqs.statistics.ForServer(destination),
|
||||||
notify: make(chan struct{}, 1),
|
notify: make(chan struct{}, 1),
|
||||||
interruptBackoff: make(chan bool),
|
signing: oqs.signing,
|
||||||
signing: oqs.signing,
|
|
||||||
}
|
}
|
||||||
|
oq.statistics.AssignBackoffNotifier(oq.handleBackoffNotifier)
|
||||||
oqs.queues[destination] = oq
|
oqs.queues[destination] = oq
|
||||||
}
|
}
|
||||||
return oq
|
return oq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clearQueue removes the queue for the provided destination from the
|
||||||
|
// set of destination queues.
|
||||||
func (oqs *OutgoingQueues) clearQueue(oq *destinationQueue) {
|
func (oqs *OutgoingQueues) clearQueue(oq *destinationQueue) {
|
||||||
oqs.queuesMutex.Lock()
|
oqs.queuesMutex.Lock()
|
||||||
defer oqs.queuesMutex.Unlock()
|
defer oqs.queuesMutex.Unlock()
|
||||||
|
|
@ -243,12 +247,35 @@ func (oqs *OutgoingQueues) SendEvent(
|
||||||
return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
|
return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destQueues := make([]*destinationQueue, 0, len(destmap))
|
||||||
for destination := range destmap {
|
for destination := range destmap {
|
||||||
if queue := oqs.getQueue(destination); queue != nil {
|
if queue := oqs.getQueue(destination); queue != nil {
|
||||||
queue.sendEvent(ev, nid)
|
destQueues = append(destQueues, queue)
|
||||||
|
} else {
|
||||||
|
delete(destmap, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a database entry that associates the given PDU NID with
|
||||||
|
// this destinations queue. We'll then be able to retrieve the PDU
|
||||||
|
// later.
|
||||||
|
if err := oqs.db.AssociatePDUWithDestinations(
|
||||||
|
oqs.process.Context(),
|
||||||
|
destmap,
|
||||||
|
nid, // NIDs from federationapi_queue_json table
|
||||||
|
); err != nil {
|
||||||
|
logrus.WithError(err).Errorf("failed to associate PDUs %q with destinations", nid)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE : PDUs should be associated with destinations before sending
|
||||||
|
// them, otherwise this is technically a race.
|
||||||
|
// If the send completes before they are associated then they won't
|
||||||
|
// get properly cleaned up in the database.
|
||||||
|
for _, queue := range destQueues {
|
||||||
|
queue.sendEvent(ev, nid)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,20 +334,47 @@ func (oqs *OutgoingQueues) SendEDU(
|
||||||
|
|
||||||
ephemeralJSON, err := json.Marshal(e)
|
ephemeralJSON, err := json.Marshal(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
return fmt.Errorf("json.Marshal: %w", err)
|
return fmt.Errorf("json.Marshal: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nid, err := oqs.db.StoreJSON(oqs.process.Context(), string(ephemeralJSON))
|
nid, err := oqs.db.StoreJSON(oqs.process.Context(), string(ephemeralJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
|
return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destQueues := make([]*destinationQueue, 0, len(destmap))
|
||||||
for destination := range destmap {
|
for destination := range destmap {
|
||||||
if queue := oqs.getQueue(destination); queue != nil {
|
if queue := oqs.getQueue(destination); queue != nil {
|
||||||
queue.sendEDU(e, nid)
|
destQueues = append(destQueues, queue)
|
||||||
|
} else {
|
||||||
|
delete(destmap, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a database entry that associates the given PDU NID with
|
||||||
|
// these destination queues. We'll then be able to retrieve the PDU
|
||||||
|
// later.
|
||||||
|
if err := oqs.db.AssociateEDUWithDestinations(
|
||||||
|
oqs.process.Context(),
|
||||||
|
destmap, // the destination server names
|
||||||
|
nid, // NIDs from federationapi_queue_json table
|
||||||
|
e.Type,
|
||||||
|
nil, // this will use the default expireEDUTypes map
|
||||||
|
); err != nil {
|
||||||
|
logrus.WithError(err).Errorf("failed to associate EDU with destinations")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE : EDUs should be associated with destinations before sending
|
||||||
|
// them, otherwise this is technically a race.
|
||||||
|
// If the send completes before they are associated then they won't
|
||||||
|
// get properly cleaned up in the database.
|
||||||
|
for _, queue := range destQueues {
|
||||||
|
queue.sendEDU(e, nid)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,7 +383,9 @@ func (oqs *OutgoingQueues) RetryServer(srv gomatrixserverlib.ServerName) {
|
||||||
if oqs.disabled {
|
if oqs.disabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
oqs.statistics.ForServer(srv).RemoveBlacklist()
|
||||||
if queue := oqs.getQueue(srv); queue != nil {
|
if queue := oqs.getQueue(srv); queue != nil {
|
||||||
|
queue.statistics.ClearBackoff()
|
||||||
queue.wakeQueueIfNeeded()
|
queue.wakeQueueIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1060
federationapi/queue/queue_test.go
Normal file
1060
federationapi/queue/queue_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -95,7 +96,10 @@ func fetchEvent(ctx context.Context, rsAPI api.FederationRoomserverAPI, eventID
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(eventsResponse.Events) == 0 {
|
if len(eventsResponse.Events) == 0 {
|
||||||
return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil}
|
return nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("Event not found"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return eventsResponse.Events[0].Event, nil
|
return eventsResponse.Events[0].Event, nil
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func localKeys(cfg *config.FederationAPI, validUntil time.Time) (*gomatrixserver
|
||||||
for _, oldVerifyKey := range cfg.Matrix.OldVerifyKeys {
|
for _, oldVerifyKey := range cfg.Matrix.OldVerifyKeys {
|
||||||
keys.OldVerifyKeys[oldVerifyKey.KeyID] = gomatrixserverlib.OldVerifyKey{
|
keys.OldVerifyKeys[oldVerifyKey.KeyID] = gomatrixserverlib.OldVerifyKey{
|
||||||
VerifyKey: gomatrixserverlib.VerifyKey{
|
VerifyKey: gomatrixserverlib.VerifyKey{
|
||||||
Key: gomatrixserverlib.Base64Bytes(oldVerifyKey.PrivateKey.Public().(ed25519.PublicKey)),
|
Key: oldVerifyKey.PublicKey,
|
||||||
},
|
},
|
||||||
ExpiredTS: oldVerifyKey.ExpiredAt,
|
ExpiredTS: oldVerifyKey.ExpiredAt,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,29 @@ package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
"github.com/matrix-org/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PublicRoomReq struct {
|
type PublicRoomReq struct {
|
||||||
Since string `json:"since,omitempty"`
|
Since string `json:"since,omitempty"`
|
||||||
Limit int16 `json:"limit,omitempty"`
|
Limit int16 `json:"limit,omitempty"`
|
||||||
Filter filter `json:"filter,omitempty"`
|
Filter filter `json:"filter,omitempty"`
|
||||||
|
IncludeAllNetworks bool `json:"include_all_networks,omitempty"`
|
||||||
|
NetworkID string `json:"third_party_instance_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type filter struct {
|
type filter struct {
|
||||||
SearchTerms string `json:"generic_search_term,omitempty"`
|
SearchTerms string `json:"generic_search_term,omitempty"`
|
||||||
|
RoomTypes []string `json:"room_types,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostPublicRooms implements GET and POST /publicRooms
|
// GetPostPublicRooms implements GET and POST /publicRooms
|
||||||
|
|
@ -57,8 +62,14 @@ func publicRooms(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if request.IncludeAllNetworks && request.NetworkID != "" {
|
||||||
|
return nil, fmt.Errorf("include_all_networks and third_party_instance_id can not be used together")
|
||||||
|
}
|
||||||
|
|
||||||
var queryRes roomserverAPI.QueryPublishedRoomsResponse
|
var queryRes roomserverAPI.QueryPublishedRoomsResponse
|
||||||
err = rsAPI.QueryPublishedRooms(ctx, &roomserverAPI.QueryPublishedRoomsRequest{}, &queryRes)
|
err = rsAPI.QueryPublishedRooms(ctx, &roomserverAPI.QueryPublishedRoomsRequest{
|
||||||
|
NetworkID: request.NetworkID,
|
||||||
|
}, &queryRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.GetLogger(ctx).WithError(err).Error("QueryPublishedRooms failed")
|
util.GetLogger(ctx).WithError(err).Error("QueryPublishedRooms failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ func Setup(
|
||||||
|
|
||||||
mu := internal.NewMutexByRoom()
|
mu := internal.NewMutexByRoom()
|
||||||
v1fedmux.Handle("/send/{txnID}", MakeFedAPI(
|
v1fedmux.Handle("/send/{txnID}", MakeFedAPI(
|
||||||
"federation_send", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_send", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return Send(
|
return Send(
|
||||||
httpReq, request, gomatrixserverlib.TransactionID(vars["txnID"]),
|
httpReq, request, gomatrixserverlib.TransactionID(vars["txnID"]),
|
||||||
|
|
@ -134,7 +134,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPut, http.MethodOptions)
|
)).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
v1fedmux.Handle("/invite/{roomID}/{eventID}", MakeFedAPI(
|
v1fedmux.Handle("/invite/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_invite", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_invite", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -150,7 +150,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPut, http.MethodOptions)
|
)).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
v2fedmux.Handle("/invite/{roomID}/{eventID}", MakeFedAPI(
|
v2fedmux.Handle("/invite/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_invite", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_invite", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -172,7 +172,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPost, http.MethodOptions)
|
)).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
v1fedmux.Handle("/exchange_third_party_invite/{roomID}", MakeFedAPI(
|
v1fedmux.Handle("/exchange_third_party_invite/{roomID}", MakeFedAPI(
|
||||||
"exchange_third_party_invite", cfg.Matrix.ServerName, keys, wakeup,
|
"exchange_third_party_invite", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return ExchangeThirdPartyInvite(
|
return ExchangeThirdPartyInvite(
|
||||||
httpReq, request, vars["roomID"], rsAPI, cfg, federation,
|
httpReq, request, vars["roomID"], rsAPI, cfg, federation,
|
||||||
|
|
@ -181,7 +181,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPut, http.MethodOptions)
|
)).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
v1fedmux.Handle("/event/{eventID}", MakeFedAPI(
|
v1fedmux.Handle("/event/{eventID}", MakeFedAPI(
|
||||||
"federation_get_event", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_get_event", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return GetEvent(
|
return GetEvent(
|
||||||
httpReq.Context(), request, rsAPI, vars["eventID"], cfg.Matrix.ServerName,
|
httpReq.Context(), request, rsAPI, vars["eventID"], cfg.Matrix.ServerName,
|
||||||
|
|
@ -190,7 +190,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/state/{roomID}", MakeFedAPI(
|
v1fedmux.Handle("/state/{roomID}", MakeFedAPI(
|
||||||
"federation_get_state", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_get_state", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -205,7 +205,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/state_ids/{roomID}", MakeFedAPI(
|
v1fedmux.Handle("/state_ids/{roomID}", MakeFedAPI(
|
||||||
"federation_get_state_ids", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_get_state_ids", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -220,7 +220,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/event_auth/{roomID}/{eventID}", MakeFedAPI(
|
v1fedmux.Handle("/event_auth/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_get_event_auth", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_get_event_auth", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -235,7 +235,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/query/directory", MakeFedAPI(
|
v1fedmux.Handle("/query/directory", MakeFedAPI(
|
||||||
"federation_query_room_alias", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_query_room_alias", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return RoomAliasToID(
|
return RoomAliasToID(
|
||||||
httpReq, federation, cfg, rsAPI, fsAPI,
|
httpReq, federation, cfg, rsAPI, fsAPI,
|
||||||
|
|
@ -244,7 +244,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/query/profile", MakeFedAPI(
|
v1fedmux.Handle("/query/profile", MakeFedAPI(
|
||||||
"federation_query_profile", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_query_profile", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return GetProfile(
|
return GetProfile(
|
||||||
httpReq, userAPI, cfg,
|
httpReq, userAPI, cfg,
|
||||||
|
|
@ -253,7 +253,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/user/devices/{userID}", MakeFedAPI(
|
v1fedmux.Handle("/user/devices/{userID}", MakeFedAPI(
|
||||||
"federation_user_devices", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_user_devices", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return GetUserDevices(
|
return GetUserDevices(
|
||||||
httpReq, keyAPI, vars["userID"],
|
httpReq, keyAPI, vars["userID"],
|
||||||
|
|
@ -263,7 +263,7 @@ func Setup(
|
||||||
|
|
||||||
if mscCfg.Enabled("msc2444") {
|
if mscCfg.Enabled("msc2444") {
|
||||||
v1fedmux.Handle("/peek/{roomID}/{peekID}", MakeFedAPI(
|
v1fedmux.Handle("/peek/{roomID}/{peekID}", MakeFedAPI(
|
||||||
"federation_peek", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_peek", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -294,7 +294,7 @@ func Setup(
|
||||||
}
|
}
|
||||||
|
|
||||||
v1fedmux.Handle("/make_join/{roomID}/{userID}", MakeFedAPI(
|
v1fedmux.Handle("/make_join/{roomID}/{userID}", MakeFedAPI(
|
||||||
"federation_make_join", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_make_join", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -325,7 +325,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/send_join/{roomID}/{eventID}", MakeFedAPI(
|
v1fedmux.Handle("/send_join/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_send_join", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_send_join", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -357,7 +357,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPut)
|
)).Methods(http.MethodPut)
|
||||||
|
|
||||||
v2fedmux.Handle("/send_join/{roomID}/{eventID}", MakeFedAPI(
|
v2fedmux.Handle("/send_join/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_send_join", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_send_join", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -374,7 +374,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPut)
|
)).Methods(http.MethodPut)
|
||||||
|
|
||||||
v1fedmux.Handle("/make_leave/{roomID}/{eventID}", MakeFedAPI(
|
v1fedmux.Handle("/make_leave/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_make_leave", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_make_leave", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -391,7 +391,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/send_leave/{roomID}/{eventID}", MakeFedAPI(
|
v1fedmux.Handle("/send_leave/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_send_leave", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_send_leave", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -423,7 +423,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPut)
|
)).Methods(http.MethodPut)
|
||||||
|
|
||||||
v2fedmux.Handle("/send_leave/{roomID}/{eventID}", MakeFedAPI(
|
v2fedmux.Handle("/send_leave/{roomID}/{eventID}", MakeFedAPI(
|
||||||
"federation_send_leave", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_send_leave", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -447,7 +447,7 @@ func Setup(
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/get_missing_events/{roomID}", MakeFedAPI(
|
v1fedmux.Handle("/get_missing_events/{roomID}", MakeFedAPI(
|
||||||
"federation_get_missing_events", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_get_missing_events", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -460,7 +460,7 @@ func Setup(
|
||||||
)).Methods(http.MethodPost)
|
)).Methods(http.MethodPost)
|
||||||
|
|
||||||
v1fedmux.Handle("/backfill/{roomID}", MakeFedAPI(
|
v1fedmux.Handle("/backfill/{roomID}", MakeFedAPI(
|
||||||
"federation_backfill", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_backfill", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) {
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
@ -479,14 +479,14 @@ func Setup(
|
||||||
).Methods(http.MethodGet, http.MethodPost)
|
).Methods(http.MethodGet, http.MethodPost)
|
||||||
|
|
||||||
v1fedmux.Handle("/user/keys/claim", MakeFedAPI(
|
v1fedmux.Handle("/user/keys/claim", MakeFedAPI(
|
||||||
"federation_keys_claim", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_keys_claim", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return ClaimOneTimeKeys(httpReq, request, keyAPI, cfg.Matrix.ServerName)
|
return ClaimOneTimeKeys(httpReq, request, keyAPI, cfg.Matrix.ServerName)
|
||||||
},
|
},
|
||||||
)).Methods(http.MethodPost)
|
)).Methods(http.MethodPost)
|
||||||
|
|
||||||
v1fedmux.Handle("/user/keys/query", MakeFedAPI(
|
v1fedmux.Handle("/user/keys/query", MakeFedAPI(
|
||||||
"federation_keys_query", cfg.Matrix.ServerName, keys, wakeup,
|
"federation_keys_query", cfg.Matrix.ServerName, cfg.Matrix.IsLocalServerName, keys, wakeup,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse {
|
||||||
return QueryDeviceKeys(httpReq, request, keyAPI, cfg.Matrix.ServerName)
|
return QueryDeviceKeys(httpReq, request, keyAPI, cfg.Matrix.ServerName)
|
||||||
},
|
},
|
||||||
|
|
@ -525,15 +525,15 @@ func ErrorIfLocalServerNotInRoom(
|
||||||
|
|
||||||
// MakeFedAPI makes an http.Handler that checks matrix federation authentication.
|
// MakeFedAPI makes an http.Handler that checks matrix federation authentication.
|
||||||
func MakeFedAPI(
|
func MakeFedAPI(
|
||||||
metricsName string,
|
metricsName string, serverName gomatrixserverlib.ServerName,
|
||||||
serverName gomatrixserverlib.ServerName,
|
isLocalServerName func(gomatrixserverlib.ServerName) bool,
|
||||||
keyRing gomatrixserverlib.JSONVerifier,
|
keyRing gomatrixserverlib.JSONVerifier,
|
||||||
wakeup *FederationWakeups,
|
wakeup *FederationWakeups,
|
||||||
f func(*http.Request, *gomatrixserverlib.FederationRequest, map[string]string) util.JSONResponse,
|
f func(*http.Request, *gomatrixserverlib.FederationRequest, map[string]string) util.JSONResponse,
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
h := func(req *http.Request) util.JSONResponse {
|
h := func(req *http.Request) util.JSONResponse {
|
||||||
fedReq, errResp := gomatrixserverlib.VerifyHTTPRequest(
|
fedReq, errResp := gomatrixserverlib.VerifyHTTPRequest(
|
||||||
req, time.Now(), serverName, keyRing,
|
req, time.Now(), serverName, isLocalServerName, keyRing,
|
||||||
)
|
)
|
||||||
if fedReq == nil {
|
if fedReq == nil {
|
||||||
return errResp
|
return errResp
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
@ -350,6 +351,7 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
||||||
for deviceID, message := range byUser {
|
for deviceID, message := range byUser {
|
||||||
// TODO: check that the user and the device actually exist here
|
// TODO: check that the user and the device actually exist here
|
||||||
if err := t.producer.SendToDevice(ctx, directPayload.Sender, userID, deviceID, directPayload.Type, message); err != nil {
|
if err := t.producer.SendToDevice(ctx, directPayload.Sender, userID, deviceID, directPayload.Type, message); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{
|
||||||
"sender": directPayload.Sender,
|
"sender": directPayload.Sender,
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
|
|
@ -360,6 +362,7 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
||||||
}
|
}
|
||||||
case gomatrixserverlib.MDeviceListUpdate:
|
case gomatrixserverlib.MDeviceListUpdate:
|
||||||
if err := t.producer.SendDeviceListUpdate(ctx, e.Content, t.Origin); err != nil {
|
if err := t.producer.SendDeviceListUpdate(ctx, e.Content, t.Origin); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
util.GetLogger(ctx).WithError(err).Error("failed to InputDeviceListUpdate")
|
util.GetLogger(ctx).WithError(err).Error("failed to InputDeviceListUpdate")
|
||||||
}
|
}
|
||||||
case gomatrixserverlib.MReceipt:
|
case gomatrixserverlib.MReceipt:
|
||||||
|
|
@ -395,6 +398,7 @@ func (t *txnReq) processEDUs(ctx context.Context) {
|
||||||
}
|
}
|
||||||
case types.MSigningKeyUpdate:
|
case types.MSigningKeyUpdate:
|
||||||
if err := t.producer.SendSigningKeyUpdate(ctx, e.Content, t.Origin); err != nil {
|
if err := t.producer.SendSigningKeyUpdate(ctx, e.Content, t.Origin); err != nil {
|
||||||
|
sentry.CaptureException(err)
|
||||||
logrus.WithError(err).Errorf("Failed to process signing key update")
|
logrus.WithError(err).Errorf("Failed to process signing key update")
|
||||||
}
|
}
|
||||||
case gomatrixserverlib.MPresence:
|
case gomatrixserverlib.MPresence:
|
||||||
|
|
|
||||||
|
|
@ -135,23 +135,24 @@ func getState(
|
||||||
return nil, nil, &resErr
|
return nil, nil, &resErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response.StateKnown {
|
switch {
|
||||||
|
case !response.RoomExists:
|
||||||
|
return nil, nil, &util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("Room not found"),
|
||||||
|
}
|
||||||
|
case !response.StateKnown:
|
||||||
return nil, nil, &util.JSONResponse{
|
return nil, nil, &util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("State not known"),
|
JSON: jsonerror.NotFound("State not known"),
|
||||||
}
|
}
|
||||||
}
|
case response.IsRejected:
|
||||||
if response.IsRejected {
|
|
||||||
return nil, nil, &util.JSONResponse{
|
return nil, nil, &util.JSONResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
JSON: jsonerror.NotFound("Event not found"),
|
JSON: jsonerror.NotFound("Event not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response.RoomExists {
|
|
||||||
return nil, nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.StateEvents, response.AuthChainEvents, nil
|
return response.StateEvents, response.AuthChainEvents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package statistics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -20,12 +21,23 @@ type Statistics struct {
|
||||||
servers map[gomatrixserverlib.ServerName]*ServerStatistics
|
servers map[gomatrixserverlib.ServerName]*ServerStatistics
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
backoffTimers map[gomatrixserverlib.ServerName]*time.Timer
|
||||||
|
backoffMutex sync.RWMutex
|
||||||
|
|
||||||
// How many times should we tolerate consecutive failures before we
|
// How many times should we tolerate consecutive failures before we
|
||||||
// just blacklist the host altogether? The backoff is exponential,
|
// just blacklist the host altogether? The backoff is exponential,
|
||||||
// so the max time here to attempt is 2**failures seconds.
|
// so the max time here to attempt is 2**failures seconds.
|
||||||
FailuresUntilBlacklist uint32
|
FailuresUntilBlacklist uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewStatistics(db storage.Database, failuresUntilBlacklist uint32) Statistics {
|
||||||
|
return Statistics{
|
||||||
|
DB: db,
|
||||||
|
FailuresUntilBlacklist: failuresUntilBlacklist,
|
||||||
|
backoffTimers: make(map[gomatrixserverlib.ServerName]*time.Timer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ForServer returns server statistics for the given server name. If it
|
// ForServer returns server statistics for the given server name. If it
|
||||||
// does not exist, it will create empty statistics and return those.
|
// does not exist, it will create empty statistics and return those.
|
||||||
func (s *Statistics) ForServer(serverName gomatrixserverlib.ServerName) *ServerStatistics {
|
func (s *Statistics) ForServer(serverName gomatrixserverlib.ServerName) *ServerStatistics {
|
||||||
|
|
@ -45,7 +57,6 @@ func (s *Statistics) ForServer(serverName gomatrixserverlib.ServerName) *ServerS
|
||||||
server = &ServerStatistics{
|
server = &ServerStatistics{
|
||||||
statistics: s,
|
statistics: s,
|
||||||
serverName: serverName,
|
serverName: serverName,
|
||||||
interrupt: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
s.servers[serverName] = server
|
s.servers[serverName] = server
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
@ -64,29 +75,43 @@ func (s *Statistics) ForServer(serverName gomatrixserverlib.ServerName) *ServerS
|
||||||
// many times we failed etc. It also manages the backoff time and black-
|
// many times we failed etc. It also manages the backoff time and black-
|
||||||
// listing a remote host if it remains uncooperative.
|
// listing a remote host if it remains uncooperative.
|
||||||
type ServerStatistics struct {
|
type ServerStatistics struct {
|
||||||
statistics *Statistics //
|
statistics *Statistics //
|
||||||
serverName gomatrixserverlib.ServerName //
|
serverName gomatrixserverlib.ServerName //
|
||||||
blacklisted atomic.Bool // is the node blacklisted
|
blacklisted atomic.Bool // is the node blacklisted
|
||||||
backoffStarted atomic.Bool // is the backoff started
|
backoffStarted atomic.Bool // is the backoff started
|
||||||
backoffUntil atomic.Value // time.Time until this backoff interval ends
|
backoffUntil atomic.Value // time.Time until this backoff interval ends
|
||||||
backoffCount atomic.Uint32 // number of times BackoffDuration has been called
|
backoffCount atomic.Uint32 // number of times BackoffDuration has been called
|
||||||
interrupt chan struct{} // interrupts the backoff goroutine
|
successCounter atomic.Uint32 // how many times have we succeeded?
|
||||||
successCounter atomic.Uint32 // how many times have we succeeded?
|
backoffNotifier func() // notifies destination queue when backoff completes
|
||||||
|
notifierMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxJitterMultiplier = 1.4
|
||||||
|
const minJitterMultiplier = 0.8
|
||||||
|
|
||||||
// duration returns how long the next backoff interval should be.
|
// duration returns how long the next backoff interval should be.
|
||||||
func (s *ServerStatistics) duration(count uint32) time.Duration {
|
func (s *ServerStatistics) duration(count uint32) time.Duration {
|
||||||
return time.Second * time.Duration(math.Exp2(float64(count)))
|
// Add some jitter to minimise the chance of having multiple backoffs
|
||||||
|
// ending at the same time.
|
||||||
|
jitter := rand.Float64()*(maxJitterMultiplier-minJitterMultiplier) + minJitterMultiplier
|
||||||
|
duration := time.Millisecond * time.Duration(math.Exp2(float64(count))*jitter*1000)
|
||||||
|
return duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel will interrupt the currently active backoff.
|
// cancel will interrupt the currently active backoff.
|
||||||
func (s *ServerStatistics) cancel() {
|
func (s *ServerStatistics) cancel() {
|
||||||
s.blacklisted.Store(false)
|
s.blacklisted.Store(false)
|
||||||
s.backoffUntil.Store(time.Time{})
|
s.backoffUntil.Store(time.Time{})
|
||||||
select {
|
|
||||||
case s.interrupt <- struct{}{}:
|
s.ClearBackoff()
|
||||||
default:
|
}
|
||||||
}
|
|
||||||
|
// AssignBackoffNotifier configures the channel to send to when
|
||||||
|
// a backoff completes.
|
||||||
|
func (s *ServerStatistics) AssignBackoffNotifier(notifier func()) {
|
||||||
|
s.notifierMutex.Lock()
|
||||||
|
defer s.notifierMutex.Unlock()
|
||||||
|
s.backoffNotifier = notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success updates the server statistics with a new successful
|
// Success updates the server statistics with a new successful
|
||||||
|
|
@ -95,8 +120,8 @@ func (s *ServerStatistics) cancel() {
|
||||||
// we will unblacklist it.
|
// we will unblacklist it.
|
||||||
func (s *ServerStatistics) Success() {
|
func (s *ServerStatistics) Success() {
|
||||||
s.cancel()
|
s.cancel()
|
||||||
s.successCounter.Inc()
|
|
||||||
s.backoffCount.Store(0)
|
s.backoffCount.Store(0)
|
||||||
|
s.successCounter.Inc()
|
||||||
if s.statistics.DB != nil {
|
if s.statistics.DB != nil {
|
||||||
if err := s.statistics.DB.RemoveServerFromBlacklist(s.serverName); err != nil {
|
if err := s.statistics.DB.RemoveServerFromBlacklist(s.serverName); err != nil {
|
||||||
logrus.WithError(err).Errorf("Failed to remove %q from blacklist", s.serverName)
|
logrus.WithError(err).Errorf("Failed to remove %q from blacklist", s.serverName)
|
||||||
|
|
@ -105,13 +130,17 @@ func (s *ServerStatistics) Success() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failure marks a failure and starts backing off if needed.
|
// Failure marks a failure and starts backing off if needed.
|
||||||
// The next call to BackoffIfRequired will do the right thing
|
// It will return the time that the current failure
|
||||||
// after this. It will return the time that the current failure
|
|
||||||
// will result in backoff waiting until, and a bool signalling
|
// will result in backoff waiting until, and a bool signalling
|
||||||
// whether we have blacklisted and therefore to give up.
|
// whether we have blacklisted and therefore to give up.
|
||||||
func (s *ServerStatistics) Failure() (time.Time, bool) {
|
func (s *ServerStatistics) Failure() (time.Time, bool) {
|
||||||
|
// Return immediately if we have blacklisted this node.
|
||||||
|
if s.blacklisted.Load() {
|
||||||
|
return time.Time{}, true
|
||||||
|
}
|
||||||
|
|
||||||
// If we aren't already backing off, this call will start
|
// If we aren't already backing off, this call will start
|
||||||
// a new backoff period. Increase the failure counter and
|
// a new backoff period, increase the failure counter and
|
||||||
// start a goroutine which will wait out the backoff and
|
// start a goroutine which will wait out the backoff and
|
||||||
// unset the backoffStarted flag when done.
|
// unset the backoffStarted flag when done.
|
||||||
if s.backoffStarted.CompareAndSwap(false, true) {
|
if s.backoffStarted.CompareAndSwap(false, true) {
|
||||||
|
|
@ -122,40 +151,48 @@ func (s *ServerStatistics) Failure() (time.Time, bool) {
|
||||||
logrus.WithError(err).Errorf("Failed to add %q to blacklist", s.serverName)
|
logrus.WithError(err).Errorf("Failed to add %q to blacklist", s.serverName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.ClearBackoff()
|
||||||
return time.Time{}, true
|
return time.Time{}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
// We're starting a new back off so work out what the next interval
|
||||||
until, ok := s.backoffUntil.Load().(time.Time)
|
// will be.
|
||||||
if ok && !until.IsZero() {
|
count := s.backoffCount.Load()
|
||||||
select {
|
until := time.Now().Add(s.duration(count))
|
||||||
case <-time.After(time.Until(until)):
|
s.backoffUntil.Store(until)
|
||||||
case <-s.interrupt:
|
|
||||||
}
|
s.statistics.backoffMutex.Lock()
|
||||||
s.backoffStarted.Store(false)
|
defer s.statistics.backoffMutex.Unlock()
|
||||||
}
|
s.statistics.backoffTimers[s.serverName] = time.AfterFunc(time.Until(until), s.backoffFinished)
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have blacklisted this node.
|
return s.backoffUntil.Load().(time.Time), false
|
||||||
if s.blacklisted.Load() {
|
}
|
||||||
return time.Now(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're already backing off and we haven't yet surpassed
|
// ClearBackoff stops the backoff timer for this destination if it is running
|
||||||
// the deadline then return that. Repeated calls to Failure
|
// and removes the timer from the backoffTimers map.
|
||||||
// within a single backoff interval will have no side effects.
|
func (s *ServerStatistics) ClearBackoff() {
|
||||||
if until, ok := s.backoffUntil.Load().(time.Time); ok && !time.Now().After(until) {
|
// If the timer is still running then stop it so it's memory is cleaned up sooner.
|
||||||
return until, false
|
s.statistics.backoffMutex.Lock()
|
||||||
|
defer s.statistics.backoffMutex.Unlock()
|
||||||
|
if timer, ok := s.statistics.backoffTimers[s.serverName]; ok {
|
||||||
|
timer.Stop()
|
||||||
}
|
}
|
||||||
|
delete(s.statistics.backoffTimers, s.serverName)
|
||||||
|
|
||||||
// We're either backing off and have passed the deadline, or
|
s.backoffStarted.Store(false)
|
||||||
// we aren't backing off, so work out what the next interval
|
}
|
||||||
// will be.
|
|
||||||
count := s.backoffCount.Load()
|
// backoffFinished will clear the previous backoff and notify the destination queue.
|
||||||
until := time.Now().Add(s.duration(count))
|
func (s *ServerStatistics) backoffFinished() {
|
||||||
s.backoffUntil.Store(until)
|
s.ClearBackoff()
|
||||||
return until, false
|
|
||||||
|
// Notify the destinationQueue if one is currently running.
|
||||||
|
s.notifierMutex.Lock()
|
||||||
|
defer s.notifierMutex.Unlock()
|
||||||
|
if s.backoffNotifier != nil {
|
||||||
|
s.backoffNotifier()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackoffInfo returns information about the current or previous backoff.
|
// BackoffInfo returns information about the current or previous backoff.
|
||||||
|
|
@ -174,6 +211,12 @@ func (s *ServerStatistics) Blacklisted() bool {
|
||||||
return s.blacklisted.Load()
|
return s.blacklisted.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveBlacklist removes the blacklisted status from the server.
|
||||||
|
func (s *ServerStatistics) RemoveBlacklist() {
|
||||||
|
s.cancel()
|
||||||
|
s.backoffCount.Store(0)
|
||||||
|
}
|
||||||
|
|
||||||
// SuccessCount returns the number of successful requests. This is
|
// SuccessCount returns the number of successful requests. This is
|
||||||
// usually useful in constructing transaction IDs.
|
// usually useful in constructing transaction IDs.
|
||||||
func (s *ServerStatistics) SuccessCount() uint32 {
|
func (s *ServerStatistics) SuccessCount() uint32 {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBackoff(t *testing.T) {
|
func TestBackoff(t *testing.T) {
|
||||||
stats := Statistics{
|
stats := NewStatistics(nil, 7)
|
||||||
FailuresUntilBlacklist: 7,
|
|
||||||
}
|
|
||||||
server := ServerStatistics{
|
server := ServerStatistics{
|
||||||
statistics: &stats,
|
statistics: &stats,
|
||||||
serverName: "test.com",
|
serverName: "test.com",
|
||||||
|
|
@ -36,7 +34,7 @@ func TestBackoff(t *testing.T) {
|
||||||
|
|
||||||
// Get the duration.
|
// Get the duration.
|
||||||
_, blacklist := server.BackoffInfo()
|
_, blacklist := server.BackoffInfo()
|
||||||
duration := time.Until(until).Round(time.Second)
|
duration := time.Until(until)
|
||||||
|
|
||||||
// Unset the backoff, or otherwise our next call will think that
|
// Unset the backoff, or otherwise our next call will think that
|
||||||
// there's a backoff in progress and return the same result.
|
// there's a backoff in progress and return the same result.
|
||||||
|
|
@ -57,8 +55,17 @@ func TestBackoff(t *testing.T) {
|
||||||
|
|
||||||
// Check if the duration is what we expect.
|
// Check if the duration is what we expect.
|
||||||
t.Logf("Backoff %d is for %s", i, duration)
|
t.Logf("Backoff %d is for %s", i, duration)
|
||||||
if wanted := time.Second * time.Duration(math.Exp2(float64(i))); !blacklist && duration != wanted {
|
roundingAllowance := 0.01
|
||||||
t.Fatalf("Backoff %d should have been %s but was %s", i, wanted, duration)
|
minDuration := time.Millisecond * time.Duration(math.Exp2(float64(i))*minJitterMultiplier*1000-roundingAllowance)
|
||||||
|
maxDuration := time.Millisecond * time.Duration(math.Exp2(float64(i))*maxJitterMultiplier*1000+roundingAllowance)
|
||||||
|
var inJitterRange bool
|
||||||
|
if duration >= minDuration && duration <= maxDuration {
|
||||||
|
inJitterRange = true
|
||||||
|
} else {
|
||||||
|
inJitterRange = false
|
||||||
|
}
|
||||||
|
if !blacklist && !inJitterRange {
|
||||||
|
t.Fatalf("Backoff %d should have been between %s and %s but was %s", i, minDuration, maxDuration, duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/federationapi/storage/shared"
|
"github.com/matrix-org/dendrite/federationapi/storage/shared"
|
||||||
"github.com/matrix-org/dendrite/federationapi/types"
|
"github.com/matrix-org/dendrite/federationapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database interface {
|
type Database interface {
|
||||||
|
|
@ -38,8 +39,8 @@ type Database interface {
|
||||||
GetPendingPDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, limit int) (pdus map[*shared.Receipt]*gomatrixserverlib.HeaderedEvent, err error)
|
GetPendingPDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, limit int) (pdus map[*shared.Receipt]*gomatrixserverlib.HeaderedEvent, err error)
|
||||||
GetPendingEDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, limit int) (edus map[*shared.Receipt]*gomatrixserverlib.EDU, err error)
|
GetPendingEDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, limit int) (edus map[*shared.Receipt]*gomatrixserverlib.EDU, err error)
|
||||||
|
|
||||||
AssociatePDUWithDestination(ctx context.Context, transactionID gomatrixserverlib.TransactionID, serverName gomatrixserverlib.ServerName, receipt *shared.Receipt) error
|
AssociatePDUWithDestinations(ctx context.Context, destinations map[gomatrixserverlib.ServerName]struct{}, receipt *shared.Receipt) error
|
||||||
AssociateEDUWithDestination(ctx context.Context, serverName gomatrixserverlib.ServerName, receipt *shared.Receipt, eduType string, expireEDUTypes map[string]time.Duration) error
|
AssociateEDUWithDestinations(ctx context.Context, destinations map[gomatrixserverlib.ServerName]struct{}, receipt *shared.Receipt, eduType string, expireEDUTypes map[string]time.Duration) error
|
||||||
|
|
||||||
CleanPDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, receipts []*shared.Receipt) error
|
CleanPDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, receipts []*shared.Receipt) error
|
||||||
CleanEDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, receipts []*shared.Receipt) error
|
CleanEDUs(ctx context.Context, serverName gomatrixserverlib.ServerName, receipts []*shared.Receipt) error
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ type Database struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase opens a new database
|
// NewDatabase opens a new database
|
||||||
func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions, cache caching.FederationCache, serverName gomatrixserverlib.ServerName) (*Database, error) {
|
func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions, cache caching.FederationCache, isLocalServerName func(gomatrixserverlib.ServerName) bool) (*Database, error) {
|
||||||
var d Database
|
var d Database
|
||||||
var err error
|
var err error
|
||||||
if d.db, d.writer, err = base.DatabaseConnection(dbProperties, sqlutil.NewDummyWriter()); err != nil {
|
if d.db, d.writer, err = base.DatabaseConnection(dbProperties, sqlutil.NewDummyWriter()); err != nil {
|
||||||
|
|
@ -96,7 +96,7 @@ func NewDatabase(base *base.BaseDendrite, dbProperties *config.DatabaseOptions,
|
||||||
}
|
}
|
||||||
d.Database = shared.Database{
|
d.Database = shared.Database{
|
||||||
DB: d.db,
|
DB: d.db,
|
||||||
ServerName: serverName,
|
IsLocalServerName: isLocalServerName,
|
||||||
Cache: cache,
|
Cache: cache,
|
||||||
Writer: d.writer,
|
Writer: d.writer,
|
||||||
FederationJoinedHosts: joinedHosts,
|
FederationJoinedHosts: joinedHosts,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
ServerName gomatrixserverlib.ServerName
|
IsLocalServerName func(gomatrixserverlib.ServerName) bool
|
||||||
Cache caching.FederationCache
|
Cache caching.FederationCache
|
||||||
Writer sqlutil.Writer
|
Writer sqlutil.Writer
|
||||||
FederationQueuePDUs tables.FederationQueuePDUs
|
FederationQueuePDUs tables.FederationQueuePDUs
|
||||||
|
|
@ -52,6 +52,10 @@ type Receipt struct {
|
||||||
nid int64
|
nid int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewReceipt(nid int64) Receipt {
|
||||||
|
return Receipt{nid: nid}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Receipt) String() string {
|
func (r *Receipt) String() string {
|
||||||
return fmt.Sprintf("%d", r.nid)
|
return fmt.Sprintf("%d", r.nid)
|
||||||
}
|
}
|
||||||
|
|
@ -70,27 +74,27 @@ func (d *Database) UpdateRoom(
|
||||||
) (joinedHosts []types.JoinedHost, err error) {
|
) (joinedHosts []types.JoinedHost, err error) {
|
||||||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||||
if purgeRoomFirst {
|
if purgeRoomFirst {
|
||||||
// If the event is a create event then we'll delete all of the existing
|
|
||||||
// data for the room. The only reason that a create event would be replayed
|
|
||||||
// to us in this way is if we're about to receive the entire room state.
|
|
||||||
if err = d.FederationJoinedHosts.DeleteJoinedHostsForRoom(ctx, txn, roomID); err != nil {
|
if err = d.FederationJoinedHosts.DeleteJoinedHostsForRoom(ctx, txn, roomID); err != nil {
|
||||||
return fmt.Errorf("d.FederationJoinedHosts.DeleteJoinedHosts: %w", err)
|
return fmt.Errorf("d.FederationJoinedHosts.DeleteJoinedHosts: %w", err)
|
||||||
}
|
}
|
||||||
}
|
for _, add := range addHosts {
|
||||||
|
if err = d.FederationJoinedHosts.InsertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName); err != nil {
|
||||||
joinedHosts, err = d.FederationJoinedHosts.SelectJoinedHostsWithTx(ctx, txn, roomID)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return err
|
joinedHosts = append(joinedHosts, add)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
for _, add := range addHosts {
|
if joinedHosts, err = d.FederationJoinedHosts.SelectJoinedHostsWithTx(ctx, txn, roomID); err != nil {
|
||||||
err = d.FederationJoinedHosts.InsertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
|
for _, add := range addHosts {
|
||||||
|
if err = d.FederationJoinedHosts.InsertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = d.FederationJoinedHosts.DeleteJoinedHosts(ctx, txn, removeHosts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if err = d.FederationJoinedHosts.DeleteJoinedHosts(ctx, txn, removeHosts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
@ -120,7 +124,7 @@ func (d *Database) GetJoinedHostsForRooms(ctx context.Context, roomIDs []string,
|
||||||
}
|
}
|
||||||
if excludeSelf {
|
if excludeSelf {
|
||||||
for i, server := range servers {
|
for i, server := range servers {
|
||||||
if server == d.ServerName {
|
if d.IsLocalServerName(server) {
|
||||||
servers = append(servers[:i], servers[i+1:]...)
|
servers = append(servers[:i], servers[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ var defaultExpireEDUTypes = map[string]time.Duration{
|
||||||
// AssociateEDUWithDestination creates an association that the
|
// AssociateEDUWithDestination creates an association that the
|
||||||
// destination queues will use to determine which JSON blobs to send
|
// destination queues will use to determine which JSON blobs to send
|
||||||
// to which servers.
|
// to which servers.
|
||||||
func (d *Database) AssociateEDUWithDestination(
|
func (d *Database) AssociateEDUWithDestinations(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
serverName gomatrixserverlib.ServerName,
|
destinations map[gomatrixserverlib.ServerName]struct{},
|
||||||
receipt *Receipt,
|
receipt *Receipt,
|
||||||
eduType string,
|
eduType string,
|
||||||
expireEDUTypes map[string]time.Duration,
|
expireEDUTypes map[string]time.Duration,
|
||||||
|
|
@ -59,17 +59,18 @@ func (d *Database) AssociateEDUWithDestination(
|
||||||
expiresAt = 0
|
expiresAt = 0
|
||||||
}
|
}
|
||||||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||||||
if err := d.FederationQueueEDUs.InsertQueueEDU(
|
var err error
|
||||||
ctx, // context
|
for destination := range destinations {
|
||||||
txn, // SQL transaction
|
err = d.FederationQueueEDUs.InsertQueueEDU(
|
||||||
eduType, // EDU type for coalescing
|
ctx, // context
|
||||||
serverName, // destination server name
|
txn, // SQL transaction
|
||||||
receipt.nid, // NID from the federationapi_queue_json table
|
eduType, // EDU type for coalescing
|
||||||
expiresAt, // The timestamp this EDU will expire
|
destination, // destination server name
|
||||||
); err != nil {
|
receipt.nid, // NID from the federationapi_queue_json table
|
||||||
return fmt.Errorf("InsertQueueEDU: %w", err)
|
expiresAt, // The timestamp this EDU will expire
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue