diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bbbdd82a4..96a361711 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,6 +3,6 @@ * [ ] 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 below using a legally identifiable name](https://matrix-org.github.io/dendrite/development/contributing#sign-off) _or_ I have already signed off privately +* [ ] Pull request includes a [sign off below](https://element-hq.github.io/dendrite/development/contributing#sign-off) _or_ I have already signed off privately Signed-off-by: `Your Name ` diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 000000000..71b20a1ae --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + labels: + - "dependencies" + - "go" + - package-ecosystem: "github-actions" + directory: / + schedule: + interval: weekly \ No newline at end of file diff --git a/.github/workflows/dendrite.yml b/.github/workflows/dendrite.yml index 9c7a1fdc9..1aa6198f7 100644 --- a/.github/workflows/dendrite.yml +++ b/.github/workflows/dendrite.yml @@ -31,13 +31,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" cache: true - name: Install Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: 14 @@ -70,11 +70,11 @@ jobs: - name: Install libolm run: sudo apt-get install libolm-dev libolm3 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 # run go test with different go versions test: @@ -106,7 +106,7 @@ jobs: - name: Install libolm run: sudo apt-get install libolm-dev libolm3 - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" - uses: actions/cache@v4 @@ -143,7 +143,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" - uses: actions/cache@v4 @@ -176,7 +176,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" - uses: actions/cache@v4 @@ -239,7 +239,7 @@ jobs: - name: Install libolm run: sudo apt-get install libolm-dev libolm3 - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" - name: Set up gotestfmt @@ -262,7 +262,7 @@ jobs: POSTGRES_PASSWORD: postgres POSTGRES_DB: dendrite - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: unittests fail_ci_if_error: true @@ -277,7 +277,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" cache: true @@ -294,9 +294,9 @@ jobs: - name: Build upgrade-tests run: go build ./cmd/dendrite-upgrade-tests - name: Test upgrade (PostgreSQL) - run: ./dendrite-upgrade-tests --head . + run: ./dendrite-upgrade-tests -repository=matrix-org/dendrite --head . - name: Test upgrade (SQLite) - run: ./dendrite-upgrade-tests --sqlite --head . + run: ./dendrite-upgrade-tests --sqlite -repository=matrix-org/dendrite --head . # run database upgrade tests, skipping over one version upgrade_test_direct: @@ -307,7 +307,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: "go.mod" cache: true @@ -324,9 +324,9 @@ jobs: - name: Build upgrade-tests run: go build ./cmd/dendrite-upgrade-tests - name: Test upgrade (PostgreSQL) - run: ./dendrite-upgrade-tests -direct -from HEAD-2 --head . + run: ./dendrite-upgrade-tests -direct -from HEAD-2 -repository=matrix-org/dendrite --head . - name: Test upgrade (SQLite) - run: ./dendrite-upgrade-tests -direct -from HEAD-2 --head . + run: ./dendrite-upgrade-tests --sqlite -direct -from HEAD-2 -repository=matrix-org/dendrite --head . # run Sytest in different variations sytest: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c795cd366..5f120626a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,7 +14,7 @@ on: env: DOCKER_NAMESPACE: matrixdotorg DOCKER_HUB_USER: dendritegithub - GHCR_NAMESPACE: matrix-org + GHCR_NAMESPACE: element-hq PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7 jobs: @@ -48,13 +48,27 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + # Build until the "build" stage, this then can be used by other steps. + - name: Build "build" image + if: github.ref_name == 'main' || github.event_name == 'release' + id: docker_build_cache + uses: docker/build-push-action@v6 + with: + target: build + cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache + cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache,mode=max + context: . + platforms: ${{ env.PLATFORMS }} + push: true + tags: | + ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:binaries + - name: Build main monolith image if: github.ref_name == 'main' id: docker_build_monolith - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache - cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache,mode=max context: . platforms: ${{ env.PLATFORMS }} push: true @@ -65,10 +79,8 @@ jobs: - name: Build release monolith image if: github.event_name == 'release' # Only for GitHub releases id: docker_build_monolith_release - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: - cache-from: type=gha - cache-to: type=gha,mode=max context: . platforms: ${{ env.PLATFORMS }} push: true @@ -86,13 +98,14 @@ jobs: output: "trivy-results.sarif" - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: "trivy-results.sarif" demo-pinecone: name: Pinecone demo image runs-on: ubuntu-latest + needs: monolith permissions: contents: read packages: write @@ -122,10 +135,9 @@ jobs: - name: Build main Pinecone demo image if: github.ref_name == 'main' id: docker_build_demo_pinecone - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache context: . file: ./build/docker/Dockerfile.demo-pinecone platforms: ${{ env.PLATFORMS }} @@ -137,23 +149,23 @@ jobs: - name: Build release Pinecone demo image if: github.event_name == 'release' # Only for GitHub releases id: docker_build_demo_pinecone_release - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache context: . file: ./build/docker/Dockerfile.demo-pinecone 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 }} + ${{ env.DOCKER_NAMESPACE }}/dendrite-demo-pinecone:latest + ${{ env.DOCKER_NAMESPACE }}/dendrite-demo-pinecone:${{ env.RELEASE_VERSION }} + ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-pinecone:latest + ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-demo-pinecone:${{ env.RELEASE_VERSION }} demo-yggdrasil: name: Yggdrasil demo image runs-on: ubuntu-latest + needs: monolith permissions: contents: read packages: write @@ -183,10 +195,9 @@ jobs: - name: Build main Yggdrasil demo image if: github.ref_name == 'main' id: docker_build_demo_yggdrasil - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache context: . file: ./build/docker/Dockerfile.demo-yggdrasil platforms: ${{ env.PLATFORMS }} @@ -198,10 +209,9 @@ jobs: - 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 + uses: docker/build-push-action@v6 with: - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:buildcache context: . file: ./build/docker/Dockerfile.demo-yggdrasil platforms: ${{ env.PLATFORMS }} diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 30f55b7c8..24a5cba61 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -30,14 +30,14 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v5 - name: Build with Jekyll uses: actions/jekyll-build-pages@v1 with: source: ./docs destination: ./_site - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 # Deployment job deploy: @@ -49,4 +49,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 10eb7c020..3f0d3eaa1 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -27,12 +27,12 @@ jobs: git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - name: Install Helm - uses: azure/setup-helm@v3 + uses: azure/setup-helm@v4 with: version: v3.10.0 - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.6.0 + uses: helm/chart-releaser-action@v1.7.0 env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" with: diff --git a/.github/workflows/k8s.yml b/.github/workflows/k8s.yml index a49042bf2..e308137eb 100644 --- a/.github/workflows/k8s.yml +++ b/.github/workflows/k8s.yml @@ -20,14 +20,14 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: azure/setup-helm@v3 + - uses: azure/setup-helm@v4 with: version: v3.10.0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.11 check-latest: true - - uses: helm/chart-testing-action@v2.3.1 + - uses: helm/chart-testing-action@v2.7.0 - name: Get changed status id: list-changed run: | @@ -53,16 +53,16 @@ jobs: fetch-depth: 0 ref: ${{ inputs.checkoutCommit }} - name: Install Kubernetes tools - uses: yokawasa/action-setup-kube-tools@v0.8.2 + uses: yokawasa/action-setup-kube-tools@v0.11.2 with: setup-tools: | helmv3 helm: "3.10.3" - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Set up chart-testing - uses: helm/chart-testing-action@v2.3.1 + uses: helm/chart-testing-action@v2.7.0 - name: Create k3d cluster uses: nolar/setup-k3d-k3s@v1 with: diff --git a/.github/workflows/schedules.yaml b/.github/workflows/schedules.yaml index e339c14d3..28db6186b 100644 --- a/.github/workflows/schedules.yaml +++ b/.github/workflows/schedules.yaml @@ -98,7 +98,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: 'stable' cache: true @@ -110,7 +110,7 @@ jobs: grep -Ev 'relayapi|setup/mscs|api_trace' sytest.cov > final.cov go tool covdata func -i="$(find Sytest* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: files: ./final.cov flags: sytest @@ -222,7 +222,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: 'stable' cache: true @@ -234,7 +234,7 @@ jobs: grep -Ev 'relayapi|setup/mscs|api_trace' complement.cov > final.cov go tool covdata func -i="$(find Complement* -name 'covmeta*' -type f -exec dirname {} \; | uniq | paste -s -d ',' -)" - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: files: ./final.cov flags: complement @@ -254,7 +254,7 @@ jobs: - uses: actions/checkout@v4 with: repository: matrix-org/matrix-react-sdk - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: cache: 'yarn' - name: Fetch layered build @@ -272,7 +272,7 @@ jobs: run: | sed -i '/HOMESERVER/c\ HOMESERVER: "dendrite",' cypress.config.ts - name: "Run cypress tests" - uses: cypress-io/github-action@v4.1.1 + uses: cypress-io/github-action@v6.10.0 with: browser: chrome start: npx serve -p 8080 ./element-web/webapp @@ -294,7 +294,7 @@ jobs: - uses: actions/checkout@v4 with: repository: matrix-org/matrix-react-sdk - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: cache: 'yarn' - name: Fetch layered build @@ -312,7 +312,7 @@ jobs: run: | sed -i '/HOMESERVER/c\ HOMESERVER: "dendritePinecone",' cypress.config.ts - name: "Run cypress tests" - uses: cypress-io/github-action@v4.1.1 + uses: cypress-io/github-action@v6.10.0 with: browser: chrome start: npx serve -p 8080 ./element-web/webapp diff --git a/.golangci.yml b/.golangci.yml index 6f3fd3627..2b280e1bd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,7 +28,6 @@ run: # the dependency descriptions in go.mod. #modules-download-mode: (release|readonly|vendor) - # output configuration options output: # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" @@ -41,7 +40,6 @@ output: # print linter name in the end of issue text, default is true print-linter-name: true - # all available settings of specific linters linters-settings: errcheck: @@ -72,22 +70,12 @@ linters-settings: - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - golint: - # minimal confidence for issues, default is 0.8 - min-confidence: 0.8 gofmt: # simplify code: gofmt with `-s` option, true by default simplify: true - goimports: - # put imports beginning with prefix after 3rd-party packages; - # it's a comma-separated list of prefixes - #local-prefixes: github.com/org/project gocyclo: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 25 - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true dupl: # tokens count to trigger issue, 150 by default threshold: 100 @@ -96,30 +84,17 @@ linters-settings: min-len: 3 # minimal occurrences count to trigger, 3 by default min-occurrences: 3 - depguard: - list-type: blacklist - include-go-root: false - packages: - # - github.com/davecgh/go-spew/spew misspell: # Correct spellings using locale preferences for US or UK. # Default is to use a neutral variety of English. # Setting locale to US will correct the British spelling of 'colour' to 'color'. locale: UK - ignore-words: - # - someword lll: # max line length, lines longer will be reported. Default is 120. # '\t' is counted as 1 character by default, and can be changed with the tab-width option line-length: 96 # tab width in spaces. Default to 1. tab-width: 1 - unused: - # treat code as a program (not a library) and report unused exported identifiers; default is false. - # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find funcs usages. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false unparam: # Inspect exported functions, default is false. Set to true if no external program/library imports your code. # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: @@ -167,7 +142,6 @@ linters: - gosimple - govet - ineffassign - - megacheck - misspell # Check code comments, whereas misspell in CI checks *.md files - nakedret - staticcheck @@ -182,22 +156,16 @@ linters: - gochecknoinits - gocritic - gofmt - - golint - gosec # Should turn back on soon - - interfacer - lll - - maligned - prealloc # Should turn back on soon - - scopelint - stylecheck - typecheck # Should turn back on soon - unconvert # Should turn back on soon - goconst # Slightly annoying, as it reports "issues" in SQL statements disable-all: false - presets: fast: false - issues: # which files to skip: they will be analyzed, but issues from them # won't be reported. Default value is empty list, but there is @@ -217,13 +185,6 @@ issues: - bin - docs - # List of regexps of issue texts to exclude, empty list by default. - # But independently from this option we use default exclude patterns, - # it can be disabled by `exclude-use-default: false`. To list all - # excluded by default patterns execute `golangci-lint run --help` - exclude: - # - abcdef - # Excluding configuration per-path, per-linter, per-text and per-source exclude-rules: # Exclude some linters from running on tests files. diff --git a/CHANGES.md b/CHANGES.md index c2c1a73f7..62493f2c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,82 @@ # Changelog +## Dendrite 0.15.2 (2025-08-15) + +### Bug fixes + - Fixed an issue which could cause Dendrite to crash on startup if the room state lacked a create event. + +## Dendrite 0.15.1 (2025-08-13) + +### Bug fixes + - Fixed an issue which could cause Dendrite to become unresponsive for minutes at a time. (contributed by [viviicat](https://github.com/viviicat)) + - Fixed an issue which prevented joining v12 rooms in some circumstances. + - Fixed an issue which prevented sending invites to v12 rooms. + - Fixed an issue which prevented some clients from syncing v12 rooms. + - Fixed an issue where a single badly formed PDU would block entire transactions of PDUs being processed. See https://github.com/element-hq/synapse/issues/7543 for the related issue on Synapse. + +## Dendrite 0.15.0 (2025-08-12) + +### ⚠ Important + +This is a security release, adding support for [room version 12](https://matrix.org/blog/2025/08/security-release/). + +### Features + - Add support for [MSC4163](https://github.com/matrix-org/matrix-spec-proposals/pull/4163). + - Add support for [MSC3967](https://github.com/matrix-org/matrix-spec-proposals/pull/3967). + - Add support for room version 12. + +### Bug fixes + - Refactored NATS JetStream code to gracefully handle more potential errors. (contributed by [neilalexander](https://github.com/neilalexander)) + - Refactored NATS startup and readiness checking. (contributed by [neilalexander](https://github.com/neilalexander)) + - Updated NATS to 2.11.7. (contributed by [neilalexander](https://github.com/neilalexander)) + - Fixed an issue which could cause Dendrite to become unresponsive for minutes at a time. (contributed by [viviicat](https://github.com/viviicat)) + - Order events when backfilling to reduce the amount of unecessary `/state_ids` requests. + - Gracefully handle incorrect sync filter JSON. + - Fixed an issue which prevented device deletion working correctly. (contributed by [robinsdan](https://github.com/robinsdan)) + +## Dendrite 0.14.1 (2025-01-16) + +### ⚠ Important + +This is a security release, [gomatrixserverlib](https://github.com/matrix-org/gomatrixserverlib) was vulnerable to +server-side request forgery, serving content from a private network it can access, under certain conditions. + +Upgrading to this version is **highly** recommended. + +### Security + +- Support for blocking access to certain networks, fixing [CVE-2024-52594](https://www.cve.org/CVERecord?id=CVE-2024-52594) and + [GHSA-4ff6-858j-r822](https://github.com/matrix-org/gomatrixserverlib/security/advisories/GHSA-4ff6-858j-r822) + +### Fixes + +- Speed-up loading server ACLs on startup, this is mostly noticeable on larger instances with many rooms. + +## Dendrite 0.14.0 (2024-12-18) + +This is the first release after forking matrix-org/dendrite, this repository is now licensed under AGPLv3.0. + +Upgrading to this version is **highly** recommended, as it fixes several long-standing bugs which could lead to state resets. +It also improves performance and memory usage. + +### Features + +- The required Go version to build Dendrite is now 1.22 +- Support for listening and connecting to I2P and Onion services was added (contributed by [eyedeekay](https://github.com/eyedeekay)) +- Add via parameter on join room requests as per [MSC4156](https://github.com/matrix-org/matrix-spec-proposals/pull/MSC4156) (contributed by [Johennes](https://github.com/Johennes)) +- Support for fallback keys has been added (contributed by [neilalexander](https://github.com/neilalexander)) +- Dendrite now supports [MSC4225](https://github.com/matrix-org/matrix-spec-proposals/pull/4225) +- Updated dependencies + - Internal NATS Server has been updated from v2.10.20 to v2.10.23 (contributed by [neilalexander](https://github.com/neilalexander)) + - gomatrixserverlib has been updated, which includes several performance improvements + +### Fixes + +- Correctly respond to `OPTIONS` requests on authed media endpoints (contributed by [arenekosreal](https://github.com/arenekosreal)) +- A long-standing bug which could lead to state resets has been fixed (contributed by [neilalexander](https://github.com/neilalexander)) + - Note: While state resets should happen less frequently, they are still part of the Matrix protocol, so they are not entirely fixed. + - Also, rooms which have been utterly broken may take some time to reconcile, it may be worth to leave, purge and rejoin such rooms. + ## Dendrite 0.13.8 (2024-09-13) ### Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 028424406..1b2e0175f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ This is the repository for Dendrite, a second-generation Matrix homeserver writt We ask that everybody who contributes to this project signs off their contributions, as explained below. -We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license their contribution under the same terms as the project's overall 'outbound' license - in our case, this is Apache Software License v2 (see [LICENSE](./LICENSE)). +We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license their contribution under the same terms as the project's overall 'outbound' license - in our case, this is GNU Affero General Public License v3 (see [LICENSE](./LICENSE)). Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution. In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach used by the [Linux Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other projects: the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix: diff --git a/Dockerfile b/Dockerfile index 27e7b39ad..6942fc1f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # # base installs required dependencies and runs go mod download to cache dependencies # -FROM --platform=${BUILDPLATFORM} docker.io/golang:1.22-alpine AS base +FROM --platform=${BUILDPLATFORM} docker.io/golang:1.24-alpine AS base RUN apk --update --no-cache add bash build-base curl git # diff --git a/README.md b/README.md index 8b53a8999..2d6d6c9dd 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ [![Build status](https://github.com/element-hq/dendrite/actions/workflows/dendrite.yml/badge.svg?event=push)](https://github.com/element-hq/dendrite/actions/workflows/dendrite.yml) [![Dendrite](https://img.shields.io/matrix/dendrite:matrix.org.svg?label=%23dendrite%3Amatrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#dendrite:matrix.org) [![Dendrite Dev](https://img.shields.io/matrix/dendrite-dev:matrix.org.svg?label=%23dendrite-dev%3Amatrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#dendrite-dev:matrix.org) -Dendrite is a second-generation Matrix homeserver written in Go. -It intends to provide an **efficient**, **reliable** and **scalable** alternative to [Synapse](https://github.com/matrix-org/synapse): +Dendrite is a second-generation Matrix homeserver written in Go. It is currently in maintenance mode, +meaning only security fixes are being applied, for example [room version 12](https://matrix.org/blog/2025/08/security-release/). + +It intends to provide an **efficient** and **reliable** alternative to [Synapse](https://github.com/matrix-org/synapse): - Efficient: A small memory footprint with better baseline performance than an out-of-the-box Synapse. - Reliable: Implements the Matrix specification as written, using the - [same test suite](https://github.com/matrix-org/sytest) as Synapse as well as - a [brand new Go test suite](https://github.com/matrix-org/complement). -- Scalable: can run on multiple machines and eventually scale to massive homeserver deployments. + [same](https://github.com/matrix-org/sytest) test [suites](https://github.com/matrix-org/complement) as Synapse. Dendrite is **beta** software, which means: @@ -31,9 +31,13 @@ If you have further questions, please take a look at [our FAQ](docs/FAQ.md) or j - **[#dendrite-dev:matrix.org](https://matrix.to/#/#dendrite-dev:matrix.org)** - The place for developers, where all Dendrite development discussion happens - **[#dendrite-alerts:matrix.org](https://matrix.to/#/#dendrite-alerts:matrix.org)** - Release notifications and important info, highly recommended for all Dendrite server admins +Dendrite does not currently support the following MSCs, which impacts the ability to use Element X with Dendrite servers: + - [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186): Simplified Sliding Sync + - [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861): Next-gen auth OIDC + ## Requirements -See the [Planning your Installation](https://matrix-org.github.io/dendrite/installation/planning) page for +See the [Planning your Installation](https://element-hq.github.io/dendrite/installation/planning) page for more information on requirements. To build Dendrite, you will need Go 1.21 or later. @@ -53,7 +57,7 @@ The [Federation Tester](https://federationtester.matrix.org) can be used to veri ## Get started -If you wish to build a fully-federating Dendrite instance, see [the Installation documentation](https://matrix-org.github.io/dendrite/installation). For running in Docker, see [build/docker](build/docker). +If you wish to build a fully-federating Dendrite instance, see [the Installation documentation](https://element-hq.github.io/dendrite/installation). For running in Docker, see [build/docker](build/docker). The following instructions are enough to get Dendrite started as a non-federating test deployment using self-signed certificates and SQLite databases: @@ -83,36 +87,6 @@ $ ./bin/create-account --config dendrite.yaml --username alice Then point your favourite Matrix client at `http://localhost:8008` or `https://localhost:8448`. -## Progress - -We use a script called "Are We Synapse Yet" which checks Sytest compliance rates. Sytest is a black-box homeserver -test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it -updates with CI. As of January 2023, we have 100% server-server parity with Synapse, and the client-server parity is at 93% , though check -CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse -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 -than features that massive deployments may be interested in (OpenID, Guests, Admin APIs, AS API). -This means Dendrite supports amongst others: - -- Core room functionality (creating rooms, invites, auth rules) -- Room versions 1 to 10 supported -- Backfilling locally and via federation -- Accounts, profiles and devices -- Published room lists -- Typing -- Media APIs -- Redaction -- Tagging -- Context -- E2E keys and device lists -- Receipts -- Push -- Guests -- User Directory -- Presence -- Fulltext search - ## Contributing We would be grateful for any help on issues marked as @@ -121,7 +95,20 @@ all have related Sytests which need to pass in order for the issue to be closed. code, you can quickly run Sytest to ensure that the test names are now passing. If you're new to the project, see our -[Contributing page](https://matrix-org.github.io/dendrite/development/contributing) to get up to speed, then +[Contributing page](https://element-hq.github.io/dendrite/development/contributing) to get up to speed, then look for [Good First Issues](https://github.com/element-hq/dendrite/labels/good%20first%20issue). If you're familiar with the project, look for [Help Wanted](https://github.com/element-hq/dendrite/labels/help-wanted) issues. + +## Copyright & License + +Copyright 2017 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2017-2025 New Vector Ltd + +This software is dual-licensed by New Vector Ltd (Element). It can be used either: + +(1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR + +(2) under the terms of a paid-for Element Commercial License agreement between you and Element (the terms of which may vary depending on what you and Element have agreed to). +Unless required by applicable law or agreed to in writing, software distributed under the Licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licenses for the specific language governing permissions and limitations under the Licenses. diff --git a/build/dendritejs-pinecone/jsServer.go b/build/dendritejs-pinecone/jsServer.go index 57e1f610e..f2f60f0ba 100644 --- a/build/dendritejs-pinecone/jsServer.go +++ b/build/dendritejs-pinecone/jsServer.go @@ -26,13 +26,16 @@ type JSServer struct { // OnRequestFromJS is the function that JS will invoke when there is a new request. // The JS function signature is: -// function(reqString: string): Promise<{result: string, error: string}> +// +// function(reqString: string): Promise<{result: string, error: string}> +// // Usage is like: -// const res = await global._go_js_server.fetch(reqString); -// if (res.error) { -// // handle error: this is a 'network' error, not a non-2xx error. -// } -// const rawHttpResponse = res.result; +// +// const res = await global._go_js_server.fetch(reqString); +// if (res.error) { +// // handle error: this is a 'network' error, not a non-2xx error. +// } +// const rawHttpResponse = res.result; func (h *JSServer) OnRequestFromJS(this js.Value, args []js.Value) interface{} { // we HAVE to spawn a new goroutine and return immediately or else Go will deadlock // if this request blocks at all e.g for /sync calls diff --git a/build/dendritejs-pinecone/main.go b/build/dendritejs-pinecone/main.go index 4f6235c54..5b3ba729f 100644 --- a/build/dendritejs-pinecone/main.go +++ b/build/dendritejs-pinecone/main.go @@ -15,21 +15,21 @@ import ( "fmt" "syscall/js" + "github.com/element-hq/dendrite/appservice" + "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/conn" + "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/rooms" + "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" + "github.com/element-hq/dendrite/federationapi" + "github.com/element-hq/dendrite/internal/caching" + "github.com/element-hq/dendrite/internal/httputil" + "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/element-hq/dendrite/roomserver" + "github.com/element-hq/dendrite/setup" + "github.com/element-hq/dendrite/setup/config" + "github.com/element-hq/dendrite/setup/jetstream" + "github.com/element-hq/dendrite/setup/process" + "github.com/element-hq/dendrite/userapi" "github.com/gorilla/mux" - "github.com/matrix-org/dendrite/appservice" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/conn" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/rooms" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" - "github.com/matrix-org/dendrite/federationapi" - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/dendrite/internal/httputil" - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/dendrite/roomserver" - "github.com/matrix-org/dendrite/setup" - "github.com/matrix-org/dendrite/setup/config" - "github.com/matrix-org/dendrite/setup/jetstream" - "github.com/matrix-org/dendrite/setup/process" - "github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/gomatrixserverlib" diff --git a/build/docker/Dockerfile.demo-pinecone b/build/docker/Dockerfile.demo-pinecone index dc6b22d7d..dee77a678 100644 --- a/build/docker/Dockerfile.demo-pinecone +++ b/build/docker/Dockerfile.demo-pinecone @@ -1,29 +1,18 @@ -FROM docker.io/golang:1.22-alpine AS base +#syntax=docker/dockerfile:1.2 -# -# 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 - -WORKDIR /build - -COPY . /build - -RUN mkdir -p bin -RUN go build -trimpath -o bin/ ./cmd/dendrite-demo-pinecone -RUN go build -trimpath -o bin/ ./cmd/create-account -RUN go build -trimpath -o bin/ ./cmd/generate-keys +FROM --platform=${TARGETPLATFORM} ghcr.io/element-hq/dendrite-monolith:binaries AS build FROM alpine:latest RUN apk --update --no-cache add curl LABEL org.opencontainers.image.title="Dendrite (Pinecone demo)" 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.licenses="AGPL-3.0-only OR LicenseRef-Element-Commercial" -COPY --from=base /build/bin/* /usr/bin/ +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-demo-pinecone /usr/bin/dendrite-demo-pinecone VOLUME /etc/dendrite WORKDIR /etc/dendrite diff --git a/build/docker/Dockerfile.demo-yggdrasil b/build/docker/Dockerfile.demo-yggdrasil index 6d6b37ea3..8522e0304 100644 --- a/build/docker/Dockerfile.demo-yggdrasil +++ b/build/docker/Dockerfile.demo-yggdrasil @@ -1,28 +1,17 @@ -FROM docker.io/golang:1.22 AS base +#syntax=docker/dockerfile:1.2 -# -# 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 - -WORKDIR /build - -COPY . /build - -RUN mkdir -p bin -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/generate-keys +FROM --platform=${TARGETPLATFORM} ghcr.io/element-hq/dendrite-monolith:binaries AS build FROM alpine:latest LABEL org.opencontainers.image.title="Dendrite (Yggdrasil demo)" 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.licenses="AGPL-3.0-only OR LicenseRef-Element-Commercial" -COPY --from=base /build/bin/* /usr/bin/ +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-demo-yggdrasil /usr/bin/dendrite-demo-yggdrasil VOLUME /etc/dendrite WORKDIR /etc/dendrite diff --git a/build/docker/README.md b/build/docker/README.md index 8d69b9af1..cc8b8215a 100644 --- a/build/docker/README.md +++ b/build/docker/README.md @@ -12,7 +12,7 @@ The `Dockerfile` is a multistage file which can build Dendrite. From the root of repository, run: ``` -docker build . -t matrixdotorg/dendrite-monolith +docker build -t ghcr.io/element-hq/dendrite-monolith:latest . ``` ## Compose file @@ -36,7 +36,7 @@ To generate keys: ``` docker run --rm --entrypoint="" \ -v $(pwd):/mnt \ - matrixdotorg/dendrite-monolith:latest \ + ghcr.io/element-hq/dendrite-monolith:latest \ /usr/bin/generate-keys \ -private-key /mnt/matrix_key.pem \ -tls-cert /mnt/server.crt \ diff --git a/build/docker/docker-compose.yml b/build/docker/docker-compose.yml index 9397673f8..d05d46bc6 100644 --- a/build/docker/docker-compose.yml +++ b/build/docker/docker-compose.yml @@ -23,7 +23,7 @@ services: monolith: hostname: monolith - image: matrixdotorg/dendrite-monolith:latest + image: ghcr.io/element-hq/dendrite-monolith:latest ports: - 8008:8008 - 8448:8448 diff --git a/build/docker/images-build.sh b/build/docker/images-build.sh index bf227968f..e56422939 100755 --- a/build/docker/images-build.sh +++ b/build/docker/images-build.sh @@ -6,6 +6,9 @@ TAG=${1:-latest} echo "Building tag '${TAG}'" -docker build . --target monolith -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} \ No newline at end of file +docker build -t ghcr.io/element-hq/dendrite-monolith:binaries --target build . + +docker build -t ghcr.io/element-hq/dendrite-monolith:${TAG} . + +docker build -t ghcr.io/element-hq/dendrite-demo-yggdrasil:${TAG} -f build/docker/Dockerfile.demo-yggdrasil . +docker build -t ghcr.io/element-hq/dendrite-demo-pinecone:${TAG} -f build/docker/Dockerfile.demo-pinecone . \ No newline at end of file diff --git a/build/docker/images-pull.sh b/build/docker/images-pull.sh index 7772ca747..c51f92b01 100755 --- a/build/docker/images-pull.sh +++ b/build/docker/images-pull.sh @@ -4,4 +4,4 @@ TAG=${1:-latest} echo "Pulling tag '${TAG}'" -docker pull matrixdotorg/dendrite-monolith:${TAG} +docker pull ghcr.io/element-hq/dendrite-monolith:${TAG} diff --git a/build/docker/images-push.sh b/build/docker/images-push.sh index d166d355a..9819326d8 100755 --- a/build/docker/images-push.sh +++ b/build/docker/images-push.sh @@ -4,4 +4,4 @@ TAG=${1:-latest} echo "Pushing tag '${TAG}'" -docker push matrixdotorg/dendrite-monolith:${TAG} +docker push ghcr.io/element-hq/dendrite-monolith:${TAG} diff --git a/build/gobind-pinecone/monolith.go b/build/gobind-pinecone/monolith.go index 4cde4783e..1b55864ea 100644 --- a/build/gobind-pinecone/monolith.go +++ b/build/gobind-pinecone/monolith.go @@ -16,16 +16,16 @@ import ( "path/filepath" "strings" - "github.com/matrix-org/dendrite/clientapi/userutil" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/conduit" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/monolith" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-pinecone/relay" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" - "github.com/matrix-org/dendrite/federationapi/api" - "github.com/matrix-org/dendrite/internal/httputil" - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/dendrite/setup/process" - userapiAPI "github.com/matrix-org/dendrite/userapi/api" + "github.com/element-hq/dendrite/clientapi/userutil" + "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/conduit" + "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/monolith" + "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/relay" + "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" + "github.com/element-hq/dendrite/federationapi/api" + "github.com/element-hq/dendrite/internal/httputil" + "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/element-hq/dendrite/setup/process" + userapiAPI "github.com/element-hq/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/pinecone/types" diff --git a/build/gobind-yggdrasil/monolith.go b/build/gobind-yggdrasil/monolith.go index 2b227d373..335028c89 100644 --- a/build/gobind-yggdrasil/monolith.go +++ b/build/gobind-yggdrasil/monolith.go @@ -12,26 +12,26 @@ import ( "path/filepath" "time" + "github.com/element-hq/dendrite/appservice" + "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" + "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/yggconn" + "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/yggrooms" + "github.com/element-hq/dendrite/federationapi" + "github.com/element-hq/dendrite/federationapi/api" + "github.com/element-hq/dendrite/internal" + "github.com/element-hq/dendrite/internal/caching" + "github.com/element-hq/dendrite/internal/httputil" + "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/element-hq/dendrite/roomserver" + "github.com/element-hq/dendrite/setup" + basepkg "github.com/element-hq/dendrite/setup/base" + "github.com/element-hq/dendrite/setup/config" + "github.com/element-hq/dendrite/setup/jetstream" + "github.com/element-hq/dendrite/setup/process" + "github.com/element-hq/dendrite/test" + "github.com/element-hq/dendrite/userapi" "github.com/getsentry/sentry-go" "github.com/gorilla/mux" - "github.com/matrix-org/dendrite/appservice" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/signing" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggconn" - "github.com/matrix-org/dendrite/cmd/dendrite-demo-yggdrasil/yggrooms" - "github.com/matrix-org/dendrite/federationapi" - "github.com/matrix-org/dendrite/federationapi/api" - "github.com/matrix-org/dendrite/internal" - "github.com/matrix-org/dendrite/internal/caching" - "github.com/matrix-org/dendrite/internal/httputil" - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/dendrite/roomserver" - "github.com/matrix-org/dendrite/setup" - basepkg "github.com/matrix-org/dendrite/setup/base" - "github.com/matrix-org/dendrite/setup/config" - "github.com/matrix-org/dendrite/setup/jetstream" - "github.com/matrix-org/dendrite/setup/process" - "github.com/matrix-org/dendrite/test" - "github.com/matrix-org/dendrite/userapi" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/sirupsen/logrus" diff --git a/build/scripts/Complement.Dockerfile b/build/scripts/Complement.Dockerfile index 660b84a46..d6880f5b2 100644 --- a/build/scripts/Complement.Dockerfile +++ b/build/scripts/Complement.Dockerfile @@ -1,6 +1,6 @@ #syntax=docker/dockerfile:1.2 -FROM golang:1.22-bookworm as build +FROM golang:1.24-bookworm as build RUN apt-get update && apt-get install -y sqlite3 WORKDIR /build diff --git a/build/scripts/ComplementLocal.Dockerfile b/build/scripts/ComplementLocal.Dockerfile index 8fc847650..044a98096 100644 --- a/build/scripts/ComplementLocal.Dockerfile +++ b/build/scripts/ComplementLocal.Dockerfile @@ -8,7 +8,7 @@ # # Use these mounts to make use of this dockerfile: # COMPLEMENT_HOST_MOUNTS='/your/local/dendrite:/dendrite:ro;/your/go/path:/go:ro' -FROM golang:1.22-bookworm +FROM golang:1.24-bookworm RUN apt-get update && apt-get install -y sqlite3 ENV SERVER_NAME=localhost diff --git a/build/scripts/ComplementPostgres.Dockerfile b/build/scripts/ComplementPostgres.Dockerfile index 0026842d8..b81316d86 100644 --- a/build/scripts/ComplementPostgres.Dockerfile +++ b/build/scripts/ComplementPostgres.Dockerfile @@ -1,6 +1,6 @@ #syntax=docker/dockerfile:1.2 -FROM golang:1.22-bookworm as build +FROM golang:1.24-bookworm as build RUN apt-get update && apt-get install -y postgresql WORKDIR /build diff --git a/clientapi/routing/admin.go b/clientapi/routing/admin.go index 2c8f31608..48e58209c 100644 --- a/clientapi/routing/admin.go +++ b/clientapi/routing/admin.go @@ -10,9 +10,9 @@ import ( "strconv" "time" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/eventutil" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" diff --git a/clientapi/routing/createroom.go b/clientapi/routing/createroom.go index dfc151730..a384e0757 100644 --- a/clientapi/routing/createroom.go +++ b/clientapi/routing/createroom.go @@ -176,12 +176,6 @@ func createRoom( roomVersion = candidateVersion } - logger.WithFields(log.Fields{ - "userID": userID.String(), - "roomID": roomID.String(), - "roomVersion": roomVersion, - }).Info("Creating new room") - profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI) if err != nil { util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed") @@ -197,6 +191,49 @@ func createRoom( keyID := cfg.Matrix.KeyID privateKey := cfg.Matrix.PrivateKey + verImpl := gomatrixserverlib.MustGetRoomVersion(roomVersion) + + var createEventJSON json.RawMessage + if verImpl.DomainlessRoomIDs() { + // make the create event up-front so the roomserver can calculate the room NID to store. + var additionalCreators []string + if createRequest.Preset == spec.PresetTrustedPrivateChat { + additionalCreators = createRequest.Invite + } + createContent, err := roomserverAPI.GenerateCreateContent(ctx, createRequest.RoomVersion, userID.String(), createRequest.CreationContent, additionalCreators) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("GenerateCreateContent failed") + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.BadJSON("invalid create content"), + } + } + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + identity, err := cfg.Matrix.SigningIdentityFor(userID.Domain()) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to get signing identity") + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + createEvent, jsonErr := roomserverAPI.GeneratePDU( + ctx, gomatrixserverlib.MustGetRoomVersion(roomVersion), + gomatrixserverlib.FledglingEvent{ + Type: spec.MRoomCreate, + Content: createContent, + }, + authEvents, 1, "", identity, evTime, userID.String(), "", rsAPI, + ) + if jsonErr != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to make the create event") + return *jsonErr + } + createEventJSON = createEvent.JSON() + r := createEvent.RoomID() + roomID = &r + } + req := roomserverAPI.PerformCreateRoomRequest{ InvitedUsers: createRequest.Invite, RoomName: createRequest.Name, @@ -204,6 +241,7 @@ func createRoom( Topic: createRequest.Topic, StatePreset: createRequest.Preset, CreationContent: createRequest.CreationContent, + CreateEvent: createEventJSON, InitialState: createRequest.InitialState, RoomAliasName: createRequest.RoomAliasName, RoomVersion: roomVersion, @@ -217,6 +255,12 @@ func createRoom( EventTime: evTime, } + logger.WithFields(log.Fields{ + "userID": userID.String(), + "roomID": roomID.String(), + "roomVersion": roomVersion, + }).Info("Creating new room") + roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req) if createRes != nil { return *createRes diff --git a/clientapi/routing/directory.go b/clientapi/routing/directory.go index ef8e3075c..bd4d4580b 100644 --- a/clientapi/routing/directory.go +++ b/clientapi/routing/directory.go @@ -411,10 +411,11 @@ func SetVisibility( JSON: spec.InternalServerError{}, } } + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) // NOTSPEC: Check if the user's power is greater than power required to change m.room.canonical_alias event power, _ := gomatrixserverlib.NewPowerLevelContentFromEvent(queryEventsRes.StateEvents[0].PDU) - if power.UserLevel(*senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) { + if !privileged && power.UserLevel(*senderID) < power.EventLevel(spec.MRoomCanonicalAlias, true) { return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden("userID doesn't have power level to change visibility"), diff --git a/clientapi/routing/joinroom.go b/clientapi/routing/joinroom.go index 30cb3233c..275d356a3 100644 --- a/clientapi/routing/joinroom.go +++ b/clientapi/routing/joinroom.go @@ -36,9 +36,16 @@ func JoinRoomByIDOrAlias( Content: map[string]interface{}{}, } - // Check to see if any ?server_name= query parameters were - // given in the request. - if serverNames, ok := req.URL.Query()["server_name"]; ok { + // Check to see if any ?via= or ?server_name= query parameters + // were given in the request. + if serverNames, ok := req.URL.Query()["via"]; ok { + for _, serverName := range serverNames { + joinReq.ServerNames = append( + joinReq.ServerNames, + spec.ServerName(serverName), + ) + } + } else if serverNames, ok := req.URL.Query()["server_name"]; ok { for _, serverName := range serverNames { joinReq.ServerNames = append( joinReq.ServerNames, diff --git a/clientapi/routing/key_crosssigning.go b/clientapi/routing/key_crosssigning.go index e6f093b5e..26e0014b5 100644 --- a/clientapi/routing/key_crosssigning.go +++ b/clientapi/routing/key_crosssigning.go @@ -7,7 +7,12 @@ package routing import ( + "context" "net/http" + "time" + + "github.com/matrix-org/gomatrixserverlib/fclient" + "github.com/sirupsen/logrus" "github.com/element-hq/dendrite/clientapi/auth" "github.com/element-hq/dendrite/clientapi/auth/authtypes" @@ -23,10 +28,15 @@ type crossSigningRequest struct { Auth newPasswordAuth `json:"auth"` } +type UploadKeysAPI interface { + QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse) + api.UploadDeviceKeysAPI +} + func UploadCrossSigningDeviceKeys( - req *http.Request, userInteractiveAuth *auth.UserInteractive, - keyserverAPI api.ClientKeyAPI, device *api.Device, - accountAPI api.ClientUserAPI, cfg *config.ClientAPI, + req *http.Request, + keyserverAPI UploadKeysAPI, device *api.Device, + accountAPI auth.GetAccountByPassword, cfg *config.ClientAPI, ) util.JSONResponse { uploadReq := &crossSigningRequest{} uploadRes := &api.PerformUploadDeviceKeysResponse{} @@ -35,32 +45,59 @@ func UploadCrossSigningDeviceKeys( if resErr != nil { return *resErr } - sessionID := uploadReq.Auth.Session - if sessionID == "" { - sessionID = util.RandomString(sessionIDLength) - } - if uploadReq.Auth.Type != authtypes.LoginTypePassword { + + // Query existing keys to determine if UIA is required + keyResp := api.QueryKeysResponse{} + keyserverAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{ + UserID: device.UserID, + UserToDevices: map[string][]string{device.UserID: {device.ID}}, + Timeout: time.Second * 10, + }, &keyResp) + + if keyResp.Error != nil { + logrus.WithError(keyResp.Error).Error("Failed to query keys") return util.JSONResponse{ - Code: http.StatusUnauthorized, - JSON: newUserInteractiveResponse( - sessionID, - []authtypes.Flow{ - { - Stages: []authtypes.LoginType{authtypes.LoginTypePassword}, - }, - }, - nil, - ), + Code: http.StatusBadRequest, + JSON: spec.Unknown(keyResp.Error.Error()), } } - typePassword := auth.LoginTypePassword{ - GetAccountByPassword: accountAPI.QueryAccountByPassword, - Config: cfg, + + existingMasterKey, hasMasterKey := keyResp.MasterKeys[device.UserID] + requireUIA := false + if hasMasterKey { + // If we have a master key, check if any of the existing keys differ. If they do, + // we need to re-authenticate the user. + requireUIA = keysDiffer(existingMasterKey, keyResp, uploadReq, device.UserID) } - if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil { - return *authErr + + if requireUIA { + sessionID := uploadReq.Auth.Session + if sessionID == "" { + sessionID = util.RandomString(sessionIDLength) + } + if uploadReq.Auth.Type != authtypes.LoginTypePassword { + return util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: newUserInteractiveResponse( + sessionID, + []authtypes.Flow{ + { + Stages: []authtypes.LoginType{authtypes.LoginTypePassword}, + }, + }, + nil, + ), + } + } + typePassword := auth.LoginTypePassword{ + GetAccountByPassword: accountAPI, + Config: cfg, + } + if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil { + return *authErr + } + sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword) } - sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword) uploadReq.UserID = device.UserID keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes) @@ -96,6 +133,21 @@ func UploadCrossSigningDeviceKeys( } } +func keysDiffer(existingMasterKey fclient.CrossSigningKey, keyResp api.QueryKeysResponse, uploadReq *crossSigningRequest, userID string) bool { + masterKeyEqual := existingMasterKey.Equal(&uploadReq.MasterKey) + if !masterKeyEqual { + return true + } + existingSelfSigningKey := keyResp.SelfSigningKeys[userID] + selfSigningEqual := existingSelfSigningKey.Equal(&uploadReq.SelfSigningKey) + if !selfSigningEqual { + return true + } + existingUserSigningKey := keyResp.UserSigningKeys[userID] + userSigningEqual := existingUserSigningKey.Equal(&uploadReq.UserSigningKey) + return !userSigningEqual +} + func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.ClientKeyAPI, device *api.Device) util.JSONResponse { uploadReq := &api.PerformUploadDeviceSignaturesRequest{} uploadRes := &api.PerformUploadDeviceSignaturesResponse{} diff --git a/clientapi/routing/key_crosssigning_test.go b/clientapi/routing/key_crosssigning_test.go new file mode 100644 index 000000000..0ebb91e07 --- /dev/null +++ b/clientapi/routing/key_crosssigning_test.go @@ -0,0 +1,316 @@ +package routing + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/element-hq/dendrite/setup/config" + "github.com/element-hq/dendrite/test" + "github.com/element-hq/dendrite/test/testrig" + "github.com/element-hq/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/gomatrixserverlib/fclient" + "github.com/matrix-org/gomatrixserverlib/spec" +) + +type mockKeyAPI struct { + t *testing.T + userResponses map[string]api.QueryKeysResponse +} + +func (m mockKeyAPI) QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse) { + res.MasterKeys = m.userResponses[req.UserID].MasterKeys + res.SelfSigningKeys = m.userResponses[req.UserID].SelfSigningKeys + res.UserSigningKeys = m.userResponses[req.UserID].UserSigningKeys + if m.t != nil { + m.t.Logf("QueryKeys: %+v => %+v", req, res) + } +} + +func (m mockKeyAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.PerformUploadDeviceKeysRequest, res *api.PerformUploadDeviceKeysResponse) { + // Just a dummy upload which always succeeds +} + +func getAccountByPassword(ctx context.Context, req *api.QueryAccountByPasswordRequest, res *api.QueryAccountByPasswordResponse) error { + res.Exists = true + res.Account = &api.Account{UserID: fmt.Sprintf("@%s:%s", req.Localpart, req.ServerName)} + return nil +} + +// Tests that if there is no existing master key for the user, the request is allowed +func Test_UploadCrossSigningDeviceKeys_ValidRequest(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{ + "master_key": {"user_id": "@user:example.com", "usage": ["master"], "keys": {"ed25519:1": "key1"}}, + "self_signing_key": {"user_id": "@user:example.com", "usage": ["self_signing"], "keys": {"ed25519:2": "key2"}}, + "user_signing_key": {"user_id": "@user:example.com", "usage": ["user_signing"], "keys": {"ed25519:3": "key3"}} + }`)) + req.Header.Set("Content-Type", "application/json") + + keyserverAPI := &mockKeyAPI{ + userResponses: map[string]api.QueryKeysResponse{ + "@user:example.com": {}, + }, + } + device := &api.Device{UserID: "@user:example.com", ID: "device"} + cfg := &config.ClientAPI{} + + res := UploadCrossSigningDeviceKeys(req, keyserverAPI, device, getAccountByPassword, cfg) + if res.Code != http.StatusOK { + t.Fatalf("expected status %d, got %d", http.StatusOK, res.Code) + } +} + +// Require UIA if there is an existing master key and there is no auth provided. +func Test_UploadCrossSigningDeviceKeys_Unauthorised(t *testing.T) { + userID := "@user:example.com" + + // Note that there is no auth field. + request := fclient.CrossSigningKeys{ + MasterKey: fclient.CrossSigningKey{ + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key1")}, + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + UserID: userID, + }, + SelfSigningKey: fclient.CrossSigningKey{ + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key2")}, + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeSelfSigning}, + UserID: userID, + }, + UserSigningKey: fclient.CrossSigningKey{ + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key3")}, + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeUserSigning}, + UserID: userID, + }, + } + + b := bytes.Buffer{} + m := json.NewEncoder(&b) + err := m.Encode(request) + if err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodPost, "/", &b) + req.Header.Set("Content-Type", "application/json") + + keyserverAPI := &mockKeyAPI{ + t: t, + userResponses: map[string]api.QueryKeysResponse{ + "@user:example.com": { + MasterKeys: map[string]fclient.CrossSigningKey{ + "@user:example.com": {UserID: "@user:example.com", Usage: []fclient.CrossSigningKeyPurpose{"master"}, Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key1")}}, + }, + SelfSigningKeys: nil, + UserSigningKeys: nil, + }, + }, + } + device := &api.Device{UserID: "@user:example.com", ID: "device"} + cfg := &config.ClientAPI{} + + res := UploadCrossSigningDeviceKeys(req, keyserverAPI, device, getAccountByPassword, cfg) + if res.Code != http.StatusUnauthorized { + t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, res.Code) + } +} + +// Invalid JSON is rejected +func Test_UploadCrossSigningDeviceKeys_InvalidJSON(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{ + "auth": {"type": "m.login.password", "session": "session", "user": "user", "password": "password"}, + "master_key": {"user_id": "@user:example.com", "usage": ["master"], "keys": {"ed25519:1": "key1"}}, + "self_signing_key": {"user_id": "@user:example.com", "usage": ["self_signing"], "keys": {"ed25519:2": "key2"}}, + "user_signing_key": {"user_id": "@user:example.com", "usage": ["user_signing"], "keys": {"ed25519:3": "key3"} + }`)) // Missing closing brace + req.Header.Set("Content-Type", "application/json") + + keyserverAPI := &mockKeyAPI{} + device := &api.Device{UserID: "@user:example.com", ID: "device"} + cfg := &config.ClientAPI{} + + res := UploadCrossSigningDeviceKeys(req, keyserverAPI, device, getAccountByPassword, cfg) + if res.Code != http.StatusBadRequest { + t.Fatalf("expected status %d, got %d", http.StatusBadRequest, res.Code) + } +} + +// Require UIA if an existing master key is present and the keys differ. +func Test_UploadCrossSigningDeviceKeys_ExistingKeysMismatch(t *testing.T) { + // Again, no auth provided + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{ + "master_key": {"user_id": "@user:example.com", "usage": ["master"], "keys": {"ed25519:1": "key1"}}, + "self_signing_key": {"user_id": "@user:example.com", "usage": ["self_signing"], "keys": {"ed25519:2": "key2"}}, + "user_signing_key": {"user_id": "@user:example.com", "usage": ["user_signing"], "keys": {"ed25519:3": "key3"}} + }`)) + req.Header.Set("Content-Type", "application/json") + + keyserverAPI := &mockKeyAPI{ + userResponses: map[string]api.QueryKeysResponse{ + "@user:example.com": { + MasterKeys: map[string]fclient.CrossSigningKey{ + "@user:example.com": {UserID: "@user:example.com", Usage: []fclient.CrossSigningKeyPurpose{"master"}, Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("different_key")}}, + }, + }, + }, + } + device := &api.Device{UserID: "@user:example.com", ID: "device"} + + cfg, _, _ := testrig.CreateConfig(t, test.DBTypeSQLite) + cfg.Global.ServerName = "example.com" + + res := UploadCrossSigningDeviceKeys(req, keyserverAPI, device, getAccountByPassword, &cfg.ClientAPI) + if res.Code != http.StatusUnauthorized { + t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, res.Code) + } +} + +func Test_KeysDiffer_MasterKeyMismatch(t *testing.T) { + existingMasterKey := fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("existing_key")}, + } + keyResp := api.QueryKeysResponse{} + uploadReq := &crossSigningRequest{ + PerformUploadDeviceKeysRequest: api.PerformUploadDeviceKeysRequest{ + CrossSigningKeys: fclient.CrossSigningKeys{ + MasterKey: fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("new_key")}, + }, + }, + }, + } + userID := "@user:example.com" + + result := keysDiffer(existingMasterKey, keyResp, uploadReq, userID) + if !result { + t.Fatalf("expected keys to differ, but they did not") + } +} + +func Test_KeysDiffer_SelfSigningKeyMismatch(t *testing.T) { + existingMasterKey := fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key")}, + } + keyResp := api.QueryKeysResponse{ + SelfSigningKeys: map[string]fclient.CrossSigningKey{ + "@user:example.com": { + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeSelfSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:2": spec.Base64Bytes("existing_key")}, + }, + }, + } + uploadReq := &crossSigningRequest{ + PerformUploadDeviceKeysRequest: api.PerformUploadDeviceKeysRequest{ + CrossSigningKeys: fclient.CrossSigningKeys{ + SelfSigningKey: fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeSelfSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:2": spec.Base64Bytes("new_key")}, + }, + }, + }, + } + userID := "@user:example.com" + + result := keysDiffer(existingMasterKey, keyResp, uploadReq, userID) + if !result { + t.Fatalf("expected keys to differ, but they did not") + } +} + +func Test_KeysDiffer_UserSigningKeyMismatch(t *testing.T) { + existingMasterKey := fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key")}, + } + keyResp := api.QueryKeysResponse{ + UserSigningKeys: map[string]fclient.CrossSigningKey{ + "@user:example.com": { + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeUserSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:3": spec.Base64Bytes("existing_key")}, + }, + }, + } + uploadReq := &crossSigningRequest{ + PerformUploadDeviceKeysRequest: api.PerformUploadDeviceKeysRequest{ + CrossSigningKeys: fclient.CrossSigningKeys{ + UserSigningKey: fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeUserSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:3": spec.Base64Bytes("new_key")}, + }, + }, + }, + } + userID := "@user:example.com" + + result := keysDiffer(existingMasterKey, keyResp, uploadReq, userID) + if !result { + t.Fatalf("expected keys to differ, but they did not") + } +} + +func Test_KeysDiffer_AllKeysMatch(t *testing.T) { + existingMasterKey := fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key")}, + } + keyResp := api.QueryKeysResponse{ + SelfSigningKeys: map[string]fclient.CrossSigningKey{ + "@user:example.com": { + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeSelfSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:2": spec.Base64Bytes("key")}, + }, + }, + UserSigningKeys: map[string]fclient.CrossSigningKey{ + "@user:example.com": { + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeUserSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:3": spec.Base64Bytes("key")}, + }, + }, + } + uploadReq := &crossSigningRequest{ + PerformUploadDeviceKeysRequest: api.PerformUploadDeviceKeysRequest{ + CrossSigningKeys: fclient.CrossSigningKeys{ + MasterKey: fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeMaster}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:1": spec.Base64Bytes("key")}, + }, + SelfSigningKey: fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeSelfSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:2": spec.Base64Bytes("key")}, + }, + UserSigningKey: fclient.CrossSigningKey{ + UserID: "@user:example.com", + Usage: []fclient.CrossSigningKeyPurpose{fclient.CrossSigningKeyPurposeUserSigning}, + Keys: map[gomatrixserverlib.KeyID]spec.Base64Bytes{"ed25519:3": spec.Base64Bytes("key")}, + }, + }, + }, + } + userID := "@user:example.com" + + result := keysDiffer(existingMasterKey, keyResp, uploadReq, userID) + if result { + t.Fatalf("expected keys to match, but they did not") + } +} diff --git a/clientapi/routing/membership.go b/clientapi/routing/membership.go index c2b26d088..2077bf8ee 100644 --- a/clientapi/routing/membership.go +++ b/clientapi/routing/membership.go @@ -11,9 +11,9 @@ import ( "crypto/ed25519" "fmt" "net/http" + "slices" "time" - "github.com/getsentry/sentry-go" appserviceAPI "github.com/element-hq/dendrite/appservice/api" "github.com/element-hq/dendrite/clientapi/auth/authtypes" "github.com/element-hq/dendrite/clientapi/httputil" @@ -24,6 +24,7 @@ import ( "github.com/element-hq/dendrite/roomserver/types" "github.com/element-hq/dendrite/setup/config" userapi "github.com/element-hq/dendrite/userapi/api" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" @@ -79,7 +80,8 @@ func SendBan( if errRes != nil { return *errRes } - allowedToBan := pl.UserLevel(*senderID) >= pl.Ban + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) + allowedToBan := privileged || pl.UserLevel(*senderID) >= pl.Ban if !allowedToBan { return util.JSONResponse{ Code: http.StatusForbidden, @@ -118,6 +120,12 @@ func sendMembership(ctx context.Context, profileAPI userapi.ClientUserAPI, devic false, ); err != nil { util.GetLogger(ctx).WithError(err).Error("SendEvents failed") + if err.Error() == api.InputWasRejected { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: spec.Forbidden("the event was rejected"), + } + } return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, @@ -185,7 +193,8 @@ func SendKick( if errRes != nil { return *errRes } - allowedToKick := pl.UserLevel(*senderID) >= pl.Kick || bodyUserID.String() == deviceUserID.String() + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) + allowedToKick := privileged || pl.UserLevel(*senderID) >= pl.Kick || bodyUserID.String() == deviceUserID.String() if !allowedToKick { return util.JSONResponse{ Code: http.StatusForbidden, @@ -680,3 +689,12 @@ func getPowerlevels(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI, } return pl, nil } + +// Returns true if the room is a room which supports privileged creators and the sender is a creator, else false. +func isPrivilegedCreator(ctx context.Context, rsAPI roomserverAPI.ClientRoomserverAPI, roomID string, senderID spec.SenderID) bool { + createEvent := roomserverAPI.GetStateEvent(ctx, rsAPI, roomID, gomatrixserverlib.StateKeyTuple{ + EventType: spec.MRoomCreate, + StateKey: "", + }) + return gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() && slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) +} diff --git a/clientapi/routing/redaction.go b/clientapi/routing/redaction.go index 795b311b0..32acf182e 100644 --- a/clientapi/routing/redaction.go +++ b/clientapi/routing/redaction.go @@ -98,10 +98,12 @@ func SendRedaction( } } + privileged := isPrivilegedCreator(req.Context(), rsAPI, roomID, *senderID) + // "Users may redact their own events, and any user with a power level greater than or equal // to the redact power level of the room may redact events there" // https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid - allowedToRedact := ev.SenderID() == *senderID + allowedToRedact := ev.SenderID() == *senderID || privileged if !allowedToRedact { plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{ EventType: spec.MRoomPowerLevels, diff --git a/clientapi/routing/room_hierarchy.go b/clientapi/routing/room_hierarchy.go index c8f335500..82ee35805 100644 --- a/clientapi/routing/room_hierarchy.go +++ b/clientapi/routing/room_hierarchy.go @@ -11,10 +11,10 @@ import ( "strconv" "sync" - "github.com/google/uuid" roomserverAPI "github.com/element-hq/dendrite/roomserver/api" "github.com/element-hq/dendrite/roomserver/types" userapi "github.com/element-hq/dendrite/userapi/api" + "github.com/google/uuid" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index d72638ee0..f0aa087db 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -1441,7 +1441,7 @@ func Setup( // Cross-signing device keys postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, userAPI, device, userAPI, cfg) + return UploadCrossSigningDeviceKeys(req, userAPI, device, userAPI.QueryAccountByPassword, cfg) }) postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { diff --git a/clientapi/routing/sendevent.go b/clientapi/routing/sendevent.go index a1a0300d2..a53cb0657 100644 --- a/clientapi/routing/sendevent.go +++ b/clientapi/routing/sendevent.go @@ -77,7 +77,6 @@ func SendEvent( JSON: spec.UnsupportedRoomVersion(err.Error()), } } - if txnID != nil { // Try to fetch response from transactionsCache if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok { @@ -367,6 +366,12 @@ func generateSendEvent( JSON: spec.InternalServerError{}, } } + if proto.Type == spec.MRoomCreate && proto.StateKey != nil && *proto.StateKey == "" { + return nil, &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.InvalidParam("cannot resend m.room.create event"), + } + } identity, err := rsAPI.SigningIdentityFor(ctx, *validRoomID, *fullUserID) if err != nil { @@ -414,12 +419,23 @@ func generateSendEvent( for i := range queryRes.StateEvents { stateEvents[i] = queryRes.StateEvents[i].PDU } - provider := gomatrixserverlib.NewAuthEvents(gomatrixserverlib.ToPDUs(stateEvents)) - if err = gomatrixserverlib.Allowed(e.PDU, &provider, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + provider, err := gomatrixserverlib.NewAuthEvents(gomatrixserverlib.ToPDUs(stateEvents)) + if err != nil { + return nil, &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: spec.Forbidden(err.Error()), + } + } + if err = gomatrixserverlib.Allowed(e.PDU, provider, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return rsAPI.QueryUserIDForSender(ctx, *validRoomID, senderID) }); err != nil { + code := 403 + validationErr, ok := err.(*gomatrixserverlib.EventValidationError) + if ok { + code = validationErr.Code + } return nil, &util.JSONResponse{ - Code: http.StatusForbidden, + Code: code, JSON: spec.Forbidden(err.Error()), // TODO: Is this error string comprehensible to the client? } } diff --git a/clientapi/routing/server_notices.go b/clientapi/routing/server_notices.go index 846158678..2e38c2083 100644 --- a/clientapi/routing/server_notices.go +++ b/clientapi/routing/server_notices.go @@ -14,6 +14,7 @@ import ( "time" "github.com/matrix-org/gomatrix" + "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/tokens" "github.com/matrix-org/util" "github.com/prometheus/client_golang/prometheus" @@ -100,7 +101,7 @@ func SendServerNotice( for _, membership := range []string{"join", "invite", "leave"} { userRooms, queryErr := rsAPI.QueryRoomsForUser(ctx, *userID, membership) if queryErr != nil { - return util.ErrorResponse(err) + return util.ErrorResponse(queryErr) } allUserRooms = append(allUserRooms, userRooms...) } @@ -139,7 +140,7 @@ func SendServerNotice( // create a new room for the user if len(commonRooms) == 0 { - powerLevelContent := eventutil.InitialPowerLevelsContent(senderUserID.String()) + powerLevelContent := eventutil.InitialPowerLevelsContent(gomatrixserverlib.MustGetRoomVersion(roomVersion), senderUserID.String()) powerLevelContent.Users[r.UserID] = -10 // taken from Synapse pl, err := json.Marshal(powerLevelContent) if err != nil { diff --git a/clientapi/routing/upgrade_room.go b/clientapi/routing/upgrade_room.go index d71cdeaf4..1eb356885 100644 --- a/clientapi/routing/upgrade_room.go +++ b/clientapi/routing/upgrade_room.go @@ -23,7 +23,8 @@ import ( ) type upgradeRoomRequest struct { - NewVersion string `json:"new_version"` + NewVersion string `json:"new_version"` + AdditionalCreators []string `json:"additional_creators"` } type upgradeRoomResponse struct { @@ -43,6 +44,13 @@ func UpgradeRoom( return *rErr } + if r.NewVersion == "" { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.InvalidParam("missing version to upgrade to"), + } + } + // Validate that the room version is supported if _, err := version.SupportedRoomVersion(gomatrixserverlib.RoomVersion(r.NewVersion)); err != nil { return util.JSONResponse{ @@ -59,7 +67,10 @@ func UpgradeRoom( JSON: spec.InternalServerError{}, } } - newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion)) + newRoomID, err := rsAPI.PerformRoomUpgrade(req.Context(), roomID, *userID, gomatrixserverlib.RoomVersion(r.NewVersion), r.AdditionalCreators) + if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("PerformRoomUpgrade failed") + } switch e := err.(type) { case nil: case roomserverAPI.ErrNotAllowed: diff --git a/cmd/dendrite-demo-pinecone/conn/client.go b/cmd/dendrite-demo-pinecone/conn/client.go index 8b21e5a84..891daff18 100644 --- a/cmd/dendrite-demo-pinecone/conn/client.go +++ b/cmd/dendrite-demo-pinecone/conn/client.go @@ -13,9 +13,9 @@ import ( "net/http" "strings" + "github.com/coder/websocket" "github.com/element-hq/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib/fclient" - "nhooyr.io/websocket" pineconeRouter "github.com/matrix-org/pinecone/router" pineconeSessions "github.com/matrix-org/pinecone/sessions" diff --git a/cmd/dendrite-demo-pinecone/monolith/monolith.go b/cmd/dendrite-demo-pinecone/monolith/monolith.go index d8b5f1c84..562d559d9 100644 --- a/cmd/dendrite-demo-pinecone/monolith/monolith.go +++ b/cmd/dendrite-demo-pinecone/monolith/monolith.go @@ -18,8 +18,6 @@ import ( "sync" "time" - "github.com/gorilla/mux" - "github.com/gorilla/websocket" "github.com/element-hq/dendrite/appservice" "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/conn" "github.com/element-hq/dendrite/cmd/dendrite-demo-pinecone/embed" @@ -43,6 +41,8 @@ import ( "github.com/element-hq/dendrite/setup/process" "github.com/element-hq/dendrite/userapi" userAPI "github.com/element-hq/dendrite/userapi/api" + "github.com/gorilla/mux" + "github.com/gorilla/websocket" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/sirupsen/logrus" @@ -138,6 +138,7 @@ func (p *P2PMonolith) SetupDendrite( rsAPI.SetFederationAPI(fsAPI, keyRing) userAPI := userapi.NewInternalAPI(processCtx, cfg, cm, &natsInstance, rsAPI, federation, enableMetrics, fsAPI.IsBlacklistedOrBackingOff) + rsAPI.SetUserAPI(userAPI) asAPI := appservice.NewInternalAPI(processCtx, cfg, &natsInstance, userAPI, rsAPI) diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index 8a0e3ee19..2c1eea384 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -19,15 +19,14 @@ import ( "path/filepath" "time" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/internal/caching" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/setup/process" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/appservice" "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/embed" "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" @@ -44,6 +43,7 @@ import ( "github.com/element-hq/dendrite/setup/mscs" "github.com/element-hq/dendrite/test" "github.com/element-hq/dendrite/userapi" + "github.com/gorilla/mux" "github.com/sirupsen/logrus" ) diff --git a/cmd/dendrite-upgrade-tests/main.go b/cmd/dendrite-upgrade-tests/main.go index 8e1243550..3da469411 100644 --- a/cmd/dendrite-upgrade-tests/main.go +++ b/cmd/dendrite-upgrade-tests/main.go @@ -39,6 +39,7 @@ var ( flagDockerHost = flag.String("docker-host", "localhost", "The hostname of the docker client. 'localhost' if running locally, 'host.docker.internal' if running in Docker.") flagDirect = flag.Bool("direct", false, "If a direct upgrade from the defined FROM version to TO should be done") flagSqlite = flag.Bool("sqlite", false, "Test SQLite instead of PostgreSQL") + flagRepository = flag.String("repository", "element-hq/dendrite", "The base repository to use when running upgrade tests.") alphaNumerics = regexp.MustCompile("[^a-zA-Z0-9]+") ) @@ -54,7 +55,7 @@ var latest, _ = semver.NewVersion("v6.6.6") // Dummy version, used as "HEAD" // due to the error: // When using COPY with more than one source file, the destination must be a directory and end with a / // We need to run a postgres anyway, so use the dockerfile associated with Complement instead. -const DockerfilePostgreSQL = `FROM golang:1.22-bookworm as build +const DockerfilePostgreSQL = `FROM golang:1.24-bookworm as build RUN apt-get update && apt-get install -y postgresql WORKDIR /build ARG BINARY @@ -98,7 +99,7 @@ ENV BINARY=dendrite EXPOSE 8008 8448 CMD /build/run_dendrite.sh` -const DockerfileSQLite = `FROM golang:1.22-bookworm as build +const DockerfileSQLite = `FROM golang:1.24-bookworm as build RUN apt-get update && apt-get install -y postgresql WORKDIR /build ARG BINARY @@ -187,7 +188,7 @@ func downloadArchive(cli *http.Client, tmpDir, archiveURL string, dockerfile []b } // buildDendrite builds Dendrite on the branchOrTagName given. Returns the image ID or an error -func buildDendrite(httpClient *http.Client, dockerClient *client.Client, tmpDir string, branchOrTagName, binary string) (string, error) { +func buildDendrite(httpClient *http.Client, dockerClient *client.Client, tmpDir string, branchOrTagName, binary, repository string) (string, error) { var tarball *bytes.Buffer var err error // If a custom HEAD location is given, use that, else pull from github. Mostly useful for CI @@ -210,7 +211,7 @@ func buildDendrite(httpClient *http.Client, dockerClient *client.Client, tmpDir log.Printf("%s: Downloading version %s to %s\n", branchOrTagName, branchOrTagName, tmpDir) // pull an archive, this contains a top-level directory which screws with the build context // which we need to fix up post download - u := fmt.Sprintf("https://github.com/element-hq/dendrite/archive/%s.tar.gz", branchOrTagName) + u := fmt.Sprintf("https://github.com/%s/archive/%s.tar.gz", repository, branchOrTagName) tarball, err = downloadArchive(httpClient, tmpDir, u, dockerfile()) if err != nil { return "", fmt.Errorf("failed to download archive %s: %w", u, err) @@ -254,8 +255,8 @@ func buildDendrite(httpClient *http.Client, dockerClient *client.Client, tmpDir return imageID, nil } -func getAndSortVersionsFromGithub(httpClient *http.Client) (semVers []*semver.Version, err error) { - u := "https://api.github.com/repos/element-hq/dendrite/tags" +func getAndSortVersionsFromGithub(httpClient *http.Client, repository string) (semVers []*semver.Version, err error) { + u := fmt.Sprintf("https://api.github.com/repos/%s/tags", repository) var res *http.Response for i := 0; i < 3; i++ { @@ -290,8 +291,8 @@ func getAndSortVersionsFromGithub(httpClient *http.Client) (semVers []*semver.Ve return semVers, nil } -func calculateVersions(cli *http.Client, from, to string, direct bool) []*semver.Version { - semvers, err := getAndSortVersionsFromGithub(cli) +func calculateVersions(cli *http.Client, from, to, repository string, direct bool) []*semver.Version { + semvers, err := getAndSortVersionsFromGithub(cli, repository) if err != nil { log.Fatalf("failed to collect semvers from github: %s", err) } @@ -348,7 +349,7 @@ func calculateVersions(cli *http.Client, from, to string, direct bool) []*semver return semvers } -func buildDendriteImages(httpClient *http.Client, dockerClient *client.Client, baseTempDir string, concurrency int, versions []*semver.Version) map[string]string { +func buildDendriteImages(httpClient *http.Client, dockerClient *client.Client, baseTempDir, repository string, concurrency int, versions []*semver.Version) map[string]string { // concurrently build all versions, this can be done in any order. The mutex protects the map branchToImageID := make(map[string]string) var mu sync.Mutex @@ -368,7 +369,7 @@ func buildDendriteImages(httpClient *http.Client, dockerClient *client.Client, b branchName, binary := versionToBranchAndBinary(version) log.Printf("Building version %s with binary %s", branchName, binary) tmpDir := baseTempDir + alphaNumerics.ReplaceAllString(branchName, "") - imgID, err := buildDendrite(httpClient, dockerClient, tmpDir, branchName, binary) + imgID, err := buildDendrite(httpClient, dockerClient, tmpDir, branchName, binary, repository) if err != nil { log.Fatalf("%s: failed to build dendrite image: %s", version, err) } @@ -583,10 +584,10 @@ func main() { os.Exit(1) } cleanup(dockerClient) - versions := calculateVersions(httpClient, *flagFrom, *flagTo, *flagDirect) + versions := calculateVersions(httpClient, *flagFrom, *flagTo, *flagRepository, *flagDirect) log.Printf("Testing dendrite versions: %v\n", versions) - branchToImageID := buildDendriteImages(httpClient, dockerClient, *flagTempDir, *flagBuildConcurrency, versions) + branchToImageID := buildDendriteImages(httpClient, dockerClient, *flagTempDir, *flagRepository, *flagBuildConcurrency, versions) // make a shared postgres volume volume, err := dockerClient.VolumeCreate(context.Background(), volume.CreateOptions{ diff --git a/cmd/dendrite/main.go b/cmd/dendrite/main.go index 1a1a9f4cb..5badbda26 100644 --- a/cmd/dendrite/main.go +++ b/cmd/dendrite/main.go @@ -10,13 +10,13 @@ import ( "flag" "time" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/caching" "github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/setup/process" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" @@ -94,6 +94,8 @@ func main() { dnsCache = fclient.NewDNSCache( cfg.Global.DNSCache.CacheSize, cfg.Global.DNSCache.CacheLifetime, + cfg.FederationAPI.AllowNetworkCIDRs, + cfg.FederationAPI.DenyNetworkCIDRs, ) logrus.Infof( "DNS cache enabled (size %d, lifetime %s)", diff --git a/cmd/generate-config/main.go b/cmd/generate-config/main.go index c6399ec50..63e1dde70 100644 --- a/cmd/generate-config/main.go +++ b/cmd/generate-config/main.go @@ -71,6 +71,10 @@ func main() { cfg.ClientAPI.RateLimiting.Enabled = false cfg.FederationAPI.DisableTLSValidation = false cfg.FederationAPI.DisableHTTPKeepalives = true + // Allow allow networks when running in CI, as otherwise connections + // to other servers might be blocked when running Complement/Sytest. + cfg.FederationAPI.DenyNetworkCIDRs = []string{} + cfg.FederationAPI.AllowNetworkCIDRs = []string{} // don't hit matrix.org when running tests!!! cfg.FederationAPI.KeyPerspectives = config.KeyPerspectives{} cfg.MediaAPI.BasePath = config.Path(filepath.Join(*dirPath, "media")) diff --git a/contrib/dendrite-demo-i2p/main.go b/contrib/dendrite-demo-i2p/main.go index 107bab888..139edaccf 100644 --- a/contrib/dendrite-demo-i2p/main.go +++ b/contrib/dendrite-demo-i2p/main.go @@ -11,13 +11,13 @@ import ( "os" "time" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/caching" "github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/setup/process" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" @@ -70,6 +70,8 @@ func main() { dnsCache = fclient.NewDNSCache( cfg.Global.DNSCache.CacheSize, cfg.Global.DNSCache.CacheLifetime, + cfg.FederationAPI.AllowNetworkCIDRs, + cfg.FederationAPI.DenyNetworkCIDRs, ) logrus.Infof( "DNS cache enabled (size %d, lifetime %s)", diff --git a/contrib/dendrite-demo-i2p/main_i2p.go b/contrib/dendrite-demo-i2p/main_i2p.go index ab86afd68..9104b7e11 100644 --- a/contrib/dendrite-demo-i2p/main_i2p.go +++ b/contrib/dendrite-demo-i2p/main_i2p.go @@ -19,14 +19,14 @@ import ( "text/template" "github.com/cretz/bine/tor" + "github.com/element-hq/dendrite/internal" + "github.com/element-hq/dendrite/internal/httputil" + "github.com/element-hq/dendrite/setup/process" "github.com/eyedeekay/goSam" "github.com/eyedeekay/onramp" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/gorilla/mux" "github.com/kardianos/minwinsvc" - "github.com/element-hq/dendrite/internal" - "github.com/element-hq/dendrite/internal/httputil" - "github.com/element-hq/dendrite/setup/process" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" diff --git a/contrib/dendrite-demo-tor/main.go b/contrib/dendrite-demo-tor/main.go index 22d7c715b..ab32e1db8 100644 --- a/contrib/dendrite-demo-tor/main.go +++ b/contrib/dendrite-demo-tor/main.go @@ -10,13 +10,13 @@ import ( "os" "time" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/caching" "github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/setup/process" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" @@ -65,6 +65,8 @@ func main() { dnsCache = fclient.NewDNSCache( cfg.Global.DNSCache.CacheSize, cfg.Global.DNSCache.CacheLifetime, + cfg.FederationAPI.AllowNetworkCIDRs, + cfg.FederationAPI.DenyNetworkCIDRs, ) logrus.Infof( "DNS cache enabled (size %d, lifetime %s)", diff --git a/contrib/dendrite-demo-tor/main_tor.go b/contrib/dendrite-demo-tor/main_tor.go index 7826759f4..8e73212a7 100644 --- a/contrib/dendrite-demo-tor/main_tor.go +++ b/contrib/dendrite-demo-tor/main_tor.go @@ -18,13 +18,13 @@ import ( "text/template" "github.com/cretz/bine/tor" + "github.com/element-hq/dendrite/internal" + "github.com/element-hq/dendrite/internal/httputil" + "github.com/element-hq/dendrite/setup/process" "github.com/eyedeekay/onramp" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/gorilla/mux" "github.com/kardianos/minwinsvc" - "github.com/element-hq/dendrite/internal" - "github.com/element-hq/dendrite/internal/httputil" - "github.com/element-hq/dendrite/setup/process" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" diff --git a/dendrite-sample.yaml b/dendrite-sample.yaml index 0ee381f02..2afdc33f1 100644 --- a/dendrite-sample.yaml +++ b/dendrite-sample.yaml @@ -254,6 +254,24 @@ federation_api: # last resort. prefer_direct_fetch: false + # deny_networks and allow_networks are the CIDR ranges used to prevent requests + # from accessing private IPs. If your system has specific IPs it should never + # contact, add them here with CIDR notation. + # + # The deny list is checked before the allow list. + deny_networks: + - "127.0.0.1/8" + - "10.0.0.0/8" + - "172.16.0.0/12" + - "192.168.0.0/16" + - "100.64.0.0/10" + - "169.254.0.0/16" + - "::1/128" + - "fe80::/64" + - "fc00::/7" + allow_networks: + - "0.0.0.0/0" # "Everything". The deny list will help limit this. + # Configuration for the Media API. media_api: # Storage path for uploaded media. May be relative or absolute. diff --git a/docs/Gemfile b/docs/Gemfile index a6aa152a2..fb1f2eeec 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -1,5 +1,2 @@ source "https://rubygems.org" -gem "github-pages", "~> 226", group: :jekyll_plugins -group :jekyll_plugins do - gem "jekyll-feed", "~> 0.15.1" -end +gem "github-pages", "~> 232", group: :jekyll_plugins diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index bf123b165..5c91a8197 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,66 +1,65 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + activesupport (8.0.2) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.2.0) + benchmark (0.4.1) + bigdecimal (3.2.2) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.11.1) + coffee-script-source (1.12.2) colorator (1.1.0) - commonmarker (0.23.10) - concurrent-ruby (1.2.0) - dnsruby (1.61.9) - simpleidn (~> 0.1) + commonmarker (0.23.11) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) + csv (3.3.5) + dnsruby (1.72.4) + base64 (~> 0.2.0) + logger (~> 1.6.5) + simpleidn (~> 0.2.1) + drb (2.2.3) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.8.1) - faraday (1.10.0) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - ffi (1.15.5) + execjs (2.10.0) + faraday (2.13.4) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.1) + net-http (>= 0.5.0) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (226) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.2) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.2.0) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) + gemoji (4.1.0) + github-pages (232) + github-pages-health-check (= 1.18.2) + jekyll (= 3.10.0) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.5.1) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) + jekyll-github-metadata (= 2.16.1) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) @@ -87,32 +86,34 @@ GEM jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.2) + jemoji (= 0.13.0) + kramdown (= 2.4.0) kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) + liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) - nokogiri (>= 1.13.4, < 2.0) - rouge (= 3.26.0) + nokogiri (>= 1.16.2, < 2.0) + rouge (= 3.30.0) terminal-table (~> 1.4) - github-pages-health-check (1.17.9) + webrick (~> 1.8) + github-pages-health-check (1.18.2) addressable (~> 2.3) dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) typhoeus (~> 1.3) - html-pipeline (2.14.1) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (0.9.5) + i18n (1.14.7) concurrent-ruby (~> 1.0) - jekyll (3.9.2) + jekyll (3.10.0) addressable (~> 2.4) colorator (~> 1.0) + csv (~> 3.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -121,27 +122,28 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) + webrick (>= 1.0) + jekyll-avatar (0.8.0) jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) + jekyll-coffeescript (1.2.2) coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) + coffee-script-source (~> 1.12) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.2.0) - commonmarker (~> 0.23.4) - jekyll (~> 3.9.0) + jekyll-commonmark-ghpages (0.5.1) + commonmarker (>= 0.23.7, < 1.1.0) + jekyll (>= 3.9, < 4.0) jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 4.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.13.0) + jekyll-github-metadata (2.16.1) jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) + octokit (>= 4, < 7, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) @@ -212,76 +214,71 @@ GEM jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.2) + json (2.13.1) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) - rb-fsevent (~> 0.10, >= 0.10.3) + liquid (4.0.4) + listen (3.9.0) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.6) mercenary (0.3.6) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.17.0) - multipart-post (2.1.1) - nokogiri (1.16.2-arm64-darwin) + minitest (5.25.5) + net-http (0.6.0) + uri + nokogiri (1.18.9-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.2-x86_64-linux) + nokogiri (1.18.9-x86_64-linux-gnu) racc (~> 1.4) - octokit (4.22.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.7) - racc (1.7.3) - rb-fsevent (0.11.1) - rb-inotify (0.10.1) + public_suffix (5.1.1) + racc (1.8.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - rexml (3.3.2) - strscan - rouge (3.26.0) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) + rexml (3.4.1) + rouge (3.30.0) + rubyzip (2.4.1) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.2) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) - simpleidn (0.2.1) - unf (~> 0.1.4) - strscan (3.1.0) + faraday (>= 0.17.3, < 3) + securerandom (0.4.1) + simpleidn (0.2.3) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (1.2.11) - thread_safe (~> 0.1) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unicode-display_width (1.8.0) - zeitwerk (2.6.6) + uri (1.0.3) + webrick (1.9.1) PLATFORMS arm64-darwin-21 x86_64-linux DEPENDENCIES - github-pages (~> 226) - jekyll-feed (~> 0.15.1) + github-pages (~> 232) BUNDLED WITH 2.3.7 diff --git a/docs/administration/4_adminapi.md b/docs/administration/4_adminapi.md index 40d02622b..56b4fad50 100644 --- a/docs/administration/4_adminapi.md +++ b/docs/administration/4_adminapi.md @@ -101,7 +101,7 @@ If successfully sent, the API will return the following response: ## GET `/_synapse/admin/v1/register` -Shared secret registration — please see the [user creation page](createusers) for +Shared secret registration — please see the [user creation page](1_createusers.md) for guidance on configuring and using this endpoint. ## GET `/_matrix/client/v3/admin/whois/{userId}` diff --git a/docs/development/CONTRIBUTING.md b/docs/development/CONTRIBUTING.md index 0d6f533c4..d12e151dd 100644 --- a/docs/development/CONTRIBUTING.md +++ b/docs/development/CONTRIBUTING.md @@ -34,27 +34,49 @@ The following items are unlikely to be accepted into a main Dendrite release for ## Sign off -We require that everyone who contributes to the project signs off their contributions -in accordance with the [Developer Certificate of Origin](https://github.com/matrix-org/matrix-spec/blob/main/CONTRIBUTING.rst#sign-off). -In effect, this means adding a statement to your pull requests or commit messages -along the lines of: +We ask that everybody who contributes to this project signs off their contributions, as explained below. + +We follow a simple 'inbound=outbound' model for contributions: the act of submitting an 'inbound' contribution means that the contributor agrees to license their contribution under the same terms as the project's overall 'outbound' license - in our case, this is Apache Software License v2 (see [LICENSE](../..//LICENSE)). + +In order to have a concrete record that your contribution is intentional and you agree to license it under the same terms as the project's license, we've adopted the same lightweight approach used by the [Linux Kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other projects: the [Developer Certificate of Origin](https://developercertificate.org/) (DCO). This is a simple declaration that you wrote the contribution or otherwise have the right to contribute it to Matrix: ``` -Signed-off-by: Full Name +Developer Certificate of Origin +Version 1.1 +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. +Developer's Certificate of Origin 1.1 +By making a contribution to this project, I certify that: +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. ``` -Unfortunately we can't accept contributions without a sign-off. +If you agree to this for your contribution, then all that's needed is to include the line in your commit or pull request comment: -Please note that we can only accept contributions under a legally identifiable name, -such as your name as it appears on government-issued documentation or common-law names -(claimed by legitimate usage or repute). We cannot accept sign-offs from a pseudonym or -alias and cannot accept anonymous contributions. +``` +Signed-off-by: Your Name +``` -If you would prefer to sign off privately instead (so as to not reveal your full -name on a public pull request), you can do so by emailing a sign-off declaration -and a link to your pull request directly to the [Matrix.org Foundation](https://matrix.org/foundation/) -at `dco@matrix.org`. Once a private sign-off has been made, you will not be required -to do so for future contributions. +Git allows you to add this signoff automatically when using the `-s` flag to `git commit`, which uses the name and email set in your `user.name` and `user.email` git configs. ## Getting up and running diff --git a/docs/installation/docker/1_docker.md b/docs/installation/docker/1_docker.md index 85de694f7..8eefe3a08 100644 --- a/docs/installation/docker/1_docker.md +++ b/docs/installation/docker/1_docker.md @@ -24,10 +24,10 @@ First we'll generate private key, which is used to sign events, the following wi mkdir -p ./config docker run --rm --entrypoint="/usr/bin/generate-keys" \ -v $(pwd)/config:/mnt \ - matrixdotorg/dendrite-monolith:latest \ + ghcr.io/element-hq/dendrite-monolith:latest \ -private-key /mnt/matrix_key.pem -# Windows equivalent: docker run --rm --entrypoint="/usr/bin/generate-keys" -v %cd%/config:/mnt matrixdotorg/dendrite-monolith:latest -private-key /mnt/matrix_key.pem +# Windows equivalent: docker run --rm --entrypoint="/usr/bin/generate-keys" -v %cd%/config:/mnt ghcr.io/element-hq/dendrite-monolith:latest -private-key /mnt/matrix_key.pem ``` (**NOTE**: This only needs to be executed **once**, as you otherwise overwrite the key) @@ -41,13 +41,13 @@ to the docker-compose file (`services.postgres.environment` values): mkdir -p ./config docker run --rm --entrypoint="/bin/sh" \ -v $(pwd)/config:/mnt \ - matrixdotorg/dendrite-monolith:latest \ + ghcr.io/element-hq/dendrite-monolith:latest \ -c "/usr/bin/generate-config \ -dir /var/dendrite/ \ -db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable \ -server YourDomainHere > /mnt/dendrite.yaml" -# Windows equivalent: docker run --rm --entrypoint="/bin/sh" -v %cd%/config:/mnt matrixdotorg/dendrite-monolith:latest -c "/usr/bin/generate-config -dir /var/dendrite/ -db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable -server YourDomainHere > /mnt/dendrite.yaml" +# Windows equivalent: docker run --rm --entrypoint="/bin/sh" -v %cd%/config:/mnt ghcr.io/element-hq/dendrite-monolith:latest -c "/usr/bin/generate-config -dir /var/dendrite/ -db postgres://dendrite:itsasecret@postgres/dendrite?sslmode=disable -server YourDomainHere > /mnt/dendrite.yaml" ``` You can then change `config/dendrite.yaml` to your liking. diff --git a/federationapi/consumers/receipts.go b/federationapi/consumers/receipts.go index f0267ebad..c12340282 100644 --- a/federationapi/consumers/receipts.go +++ b/federationapi/consumers/receipts.go @@ -11,7 +11,6 @@ import ( "encoding/json" "strconv" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/federationapi/queue" "github.com/element-hq/dendrite/federationapi/storage" fedTypes "github.com/element-hq/dendrite/federationapi/types" @@ -19,6 +18,7 @@ import ( "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/setup/process" syncTypes "github.com/element-hq/dendrite/syncapi/types" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/nats-io/nats.go" diff --git a/federationapi/queue/destinationqueue.go b/federationapi/queue/destinationqueue.go index 136ad5098..f514d1411 100644 --- a/federationapi/queue/destinationqueue.go +++ b/federationapi/queue/destinationqueue.go @@ -147,7 +147,7 @@ func (oq *destinationQueue) wakeQueueIfEventsPending(forceWakeup bool) { // or if forceWakeup is true. Otherwise there is no reason to start the // queue goroutine and waste resources. if forceWakeup || eventsPending() { - logrus.Info("Starting queue due to pending events or forceWakeup") + logrus.Debugf("Starting queue %q -> %q due to pending events or forceWakeup", oq.origin, oq.destination) oq.wakeQueueAndNotify() } } diff --git a/federationapi/routing/profile_test.go b/federationapi/routing/profile_test.go index f52d2e625..96482b5c6 100644 --- a/federationapi/routing/profile_test.go +++ b/federationapi/routing/profile_test.go @@ -14,7 +14,6 @@ import ( "net/url" "testing" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/clientapi/auth/authtypes" "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" fedAPI "github.com/element-hq/dendrite/federationapi" @@ -26,6 +25,7 @@ import ( "github.com/element-hq/dendrite/test" "github.com/element-hq/dendrite/test/testrig" userAPI "github.com/element-hq/dendrite/userapi/api" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/federationapi/routing/query_test.go b/federationapi/routing/query_test.go index b9a7ab297..27ef4c6a4 100644 --- a/federationapi/routing/query_test.go +++ b/federationapi/routing/query_test.go @@ -14,7 +14,6 @@ import ( "net/url" "testing" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" fedAPI "github.com/element-hq/dendrite/federationapi" "github.com/element-hq/dendrite/federationapi/routing" @@ -24,6 +23,7 @@ import ( "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/test" "github.com/element-hq/dendrite/test/testrig" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index 3073a512d..5043722e8 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -14,8 +14,6 @@ import ( "sync" "time" - "github.com/getsentry/sentry-go" - "github.com/gorilla/mux" fedInternal "github.com/element-hq/dendrite/federationapi/internal" "github.com/element-hq/dendrite/federationapi/producers" "github.com/element-hq/dendrite/internal" @@ -24,6 +22,8 @@ import ( roomserverAPI "github.com/element-hq/dendrite/roomserver/api" "github.com/element-hq/dendrite/setup/config" userapi "github.com/element-hq/dendrite/userapi/api" + "github.com/getsentry/sentry-go" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/federationapi/routing/send_test.go b/federationapi/routing/send_test.go index b0dfda96b..c20a9d592 100644 --- a/federationapi/routing/send_test.go +++ b/federationapi/routing/send_test.go @@ -12,7 +12,6 @@ import ( "net/http/httptest" "testing" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" fedAPI "github.com/element-hq/dendrite/federationapi" "github.com/element-hq/dendrite/federationapi/routing" @@ -22,6 +21,7 @@ import ( "github.com/element-hq/dendrite/setup/jetstream" "github.com/element-hq/dendrite/test" "github.com/element-hq/dendrite/test/testrig" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/federationapi/routing/threepid.go b/federationapi/routing/threepid.go index 4e3dfdd51..c2b4fa045 100644 --- a/federationapi/routing/threepid.go +++ b/federationapi/routing/threepid.go @@ -344,7 +344,7 @@ func buildMembershipEvent( protoEvent.Depth = queryRes.Depth protoEvent.PrevEvents = queryRes.LatestEvents - authEvents := gomatrixserverlib.NewAuthEvents(nil) + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) for i := range queryRes.StateEvents { err = authEvents.AddEvent(queryRes.StateEvents[i].PDU) @@ -357,7 +357,7 @@ func buildMembershipEvent( return nil, err } - refs, err := eventsNeeded.AuthEventReferences(&authEvents) + refs, err := eventsNeeded.AuthEventReferences(authEvents) if err != nil { return nil, err } @@ -421,7 +421,7 @@ func sendToRemoteServer( // found. Returning an error isn't necessary in this case as the event will be // rejected by gomatrixserverlib. func fillDisplayName( - builder *gomatrixserverlib.ProtoEvent, authEvents gomatrixserverlib.AuthEvents, + builder *gomatrixserverlib.ProtoEvent, authEvents *gomatrixserverlib.AuthEvents, ) error { var content gomatrixserverlib.MemberContent if err := json.Unmarshal(builder.Content, &content); err != nil { diff --git a/federationapi/storage/postgres/joined_hosts_table.go b/federationapi/storage/postgres/joined_hosts_table.go index 08dbf744b..4e6fed7ae 100644 --- a/federationapi/storage/postgres/joined_hosts_table.go +++ b/federationapi/storage/postgres/joined_hosts_table.go @@ -11,10 +11,10 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/federationapi/types" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/federationapi/storage/postgres/notary_server_keys_metadata_table.go b/federationapi/storage/postgres/notary_server_keys_metadata_table.go index 20573f39c..87bff1296 100644 --- a/federationapi/storage/postgres/notary_server_keys_metadata_table.go +++ b/federationapi/storage/postgres/notary_server_keys_metadata_table.go @@ -11,10 +11,10 @@ import ( "database/sql" "encoding/json" - "github.com/lib/pq" "github.com/element-hq/dendrite/federationapi/storage/tables" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/federationapi/storage/postgres/queue_json_table.go b/federationapi/storage/postgres/queue_json_table.go index 2b93bfdcd..ef0d9eab5 100644 --- a/federationapi/storage/postgres/queue_json_table.go +++ b/federationapi/storage/postgres/queue_json_table.go @@ -10,9 +10,9 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" ) const queueJSONSchema = ` diff --git a/federationapi/storage/postgres/queue_pdus_table.go b/federationapi/storage/postgres/queue_pdus_table.go index 460113344..7748d9ca3 100644 --- a/federationapi/storage/postgres/queue_pdus_table.go +++ b/federationapi/storage/postgres/queue_pdus_table.go @@ -10,9 +10,9 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/federationapi/storage/postgres/relay_servers_table.go b/federationapi/storage/postgres/relay_servers_table.go index 7ca7ad350..0a02cf919 100644 --- a/federationapi/storage/postgres/relay_servers_table.go +++ b/federationapi/storage/postgres/relay_servers_table.go @@ -10,9 +10,9 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/federationapi/storage/postgres/server_key_table.go b/federationapi/storage/postgres/server_key_table.go index 632dfd17c..656ffd684 100644 --- a/federationapi/storage/postgres/server_key_table.go +++ b/federationapi/storage/postgres/server_key_table.go @@ -11,9 +11,9 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/go.mod b/go.mod index f17c1a8b8..2bfea799b 100644 --- a/go.mod +++ b/go.mod @@ -2,88 +2,87 @@ module github.com/element-hq/dendrite require ( github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d - github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/MFAshby/stdemuxerhook v1.0.0 - github.com/Masterminds/semver/v3 v3.1.1 - github.com/blevesearch/bleve/v2 v2.4.0 + github.com/Masterminds/semver/v3 v3.3.1 + github.com/blevesearch/bleve/v2 v2.5.2 github.com/codeclysm/extract v2.2.0+incompatible + github.com/coder/websocket v1.8.13 github.com/cretz/bine v0.2.0 - github.com/dgraph-io/ristretto v0.1.1 - github.com/docker/docker v25.0.6+incompatible - github.com/docker/go-connections v0.4.0 + github.com/dgraph-io/ristretto v0.2.0 + github.com/docker/docker v26.1.5+incompatible + github.com/docker/go-connections v0.5.0 github.com/eyedeekay/goSam v0.32.54 github.com/eyedeekay/onramp v0.33.8 github.com/getsentry/sentry-go v0.14.0 github.com/gologme/log v1.3.0 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.0 - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.3 github.com/kardianos/minwinsvc v1.0.2 github.com/lib/pq v1.10.9 github.com/matrix-org/dugong v0.0.0-20210921133753-66e6b1c67e2e github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91 github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 - github.com/matrix-org/gomatrixserverlib v0.0.0-20240910190622-2c764912ce93 + github.com/matrix-org/gomatrixserverlib v0.0.0-20250815065806-6697d93cbcba github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 - github.com/mattn/go-sqlite3 v1.14.22 - github.com/nats-io/nats-server/v2 v2.10.20 - github.com/nats-io/nats.go v1.36.0 + github.com/mattn/go-sqlite3 v1.14.28 + github.com/nats-io/nats-server/v2 v2.11.7 + github.com/nats-io/nats.go v1.44.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/opentracing/opentracing-go v1.2.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.22.0 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.9.0 - github.com/tidwall/gjson v1.17.0 + github.com/stretchr/testify v1.10.0 + github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/uber/jaeger-lib v2.4.1+incompatible - github.com/yggdrasil-network/yggdrasil-go v0.5.6 - github.com/yggdrasil-network/yggquic v0.0.0-20240802104827-b4e97a928967 + github.com/yggdrasil-network/yggdrasil-go v0.5.12 + github.com/yggdrasil-network/yggquic v0.0.0-20241212194307-0d495106021f go.uber.org/atomic v1.11.0 - golang.org/x/crypto v0.27.0 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 - golang.org/x/image v0.18.0 + golang.org/x/crypto v0.40.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/image v0.27.0 golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b - golang.org/x/sync v0.8.0 - golang.org/x/term v0.24.0 - gopkg.in/h2non/bimg.v1 v1.1.9 + golang.org/x/sync v0.16.0 + golang.org/x/term v0.33.0 gopkg.in/yaml.v2 v2.4.0 - gotest.tools/v3 v3.4.0 + gotest.tools/v3 v3.5.2 maunium.net/go/mautrix v0.15.1 - modernc.org/sqlite v1.29.5 - nhooyr.io/websocket v1.8.7 + modernc.org/sqlite v1.34.5 ) require ( - github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2 // indirect + github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/RoaringBitmap/roaring v1.2.3 // indirect + github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/bits-and-blooms/bloom/v3 v3.7.0 // indirect - github.com/blevesearch/bleve_index_api v1.1.6 // indirect - github.com/blevesearch/geo v0.1.20 // indirect - github.com/blevesearch/go-faiss v1.0.13 // indirect + github.com/blevesearch/bleve_index_api v1.2.8 // indirect + github.com/blevesearch/geo v0.2.3 // indirect + github.com/blevesearch/go-faiss v1.0.25 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/scorch_segment_api/v2 v2.2.9 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect - github.com/blevesearch/vellum v1.0.10 // indirect - github.com/blevesearch/zapx/v11 v11.3.10 // indirect - github.com/blevesearch/zapx/v12 v12.3.10 // indirect - github.com/blevesearch/zapx/v13 v13.3.10 // indirect - github.com/blevesearch/zapx/v14 v14.3.10 // indirect - github.com/blevesearch/zapx/v15 v15.3.13 // indirect - github.com/blevesearch/zapx/v16 v16.0.12 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/blevesearch/vellum v1.1.0 // indirect + github.com/blevesearch/zapx/v11 v11.4.2 // indirect + github.com/blevesearch/zapx/v12 v12.4.2 // indirect + github.com/blevesearch/zapx/v13 v13.4.2 // indirect + github.com/blevesearch/zapx/v14 v14.4.2 // indirect + github.com/blevesearch/zapx/v15 v15.4.2 // indirect + github.com/blevesearch/zapx/v16 v16.2.4 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect @@ -96,65 +95,69 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect - github.com/golang/glog v1.0.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/pprof v0.0.0-20230808223545-4887780b67fb // indirect + github.com/google/go-tpm v0.9.5 // indirect + github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/h2non/filetype v1.1.3 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/go-set/v3 v3.0.0 // indirect github.com/hjson/hjson-go/v4 v4.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/juju/errors v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.3 // indirect - github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/mschoch/smat v0.2.0 // indirect - github.com/nats-io/jwt/v2 v2.5.8 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nats-io/jwt/v2 v2.7.4 // indirect + github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/oleiade/lane/v2 v2.0.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/quic-go/quic-go v0.45.2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/quic-go/quic-go v0.48.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/zerolog v1.29.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - go.etcd.io/bbolt v1.3.7 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/macaroon.v2 v2.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect maunium.net/go/maulogger/v2 v2.4.1 // indirect - modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.41.0 // indirect + modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect + modernc.org/memory v1.8.0 // indirect + nhooyr.io/websocket v1.8.7 // indirect ) -go 1.21.0 +go 1.23.0 + +toolchain go1.24.3 diff --git a/go.sum b/go.sum index b64125e3c..7a1d9b68a 100644 --- a/go.sum +++ b/go.sum @@ -1,98 +1,99 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2 h1:SBdYBKeXYUUFef5wi2CMhYmXFVGiYaRpTvbki0Bu+JQ= -github.com/Arceliar/ironwood v0.0.0-20240529054413-b8e59574e2b2/go.mod h1:6WP4799FX0OuWdENGQAh+0RXp9FLh0y7NZ7tM9cJyXk= +github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3 h1:d8N0z+udAnbU5PdjpLSNPTWlqeU/nnYsQ42B6+879aw= +github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/MFAshby/stdemuxerhook v1.0.0 h1:1XFGzakrsHMv76AeanPDL26NOgwjPl/OUxbGhJthwMc= github.com/MFAshby/stdemuxerhook v1.0.0/go.mod h1:nLMI9FUf9Hz98n+yAXsTMUR4RZQy28uCTLG1Fzvj/uY= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY= -github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= +github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= +github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= +github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= -github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls= github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg= -github.com/blevesearch/bleve/v2 v2.4.0 h1:2xyg+Wv60CFHYccXc+moGxbL+8QKT/dZK09AewHgKsg= -github.com/blevesearch/bleve/v2 v2.4.0/go.mod h1:IhQHoFAbHgWKYavb9rQgQEJJVMuY99cKdQ0wPpst2aY= -github.com/blevesearch/bleve_index_api v1.1.6 h1:orkqDFCBuNU2oHW9hN2YEJmet+TE9orml3FCGbl1cKk= -github.com/blevesearch/bleve_index_api v1.1.6/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8= -github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM= -github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w= -github.com/blevesearch/go-faiss v1.0.13 h1:zfFs7ZYD0NqXVSY37j0JZjZT1BhE9AE4peJfcx/NB4A= -github.com/blevesearch/go-faiss v1.0.13/go.mod h1:jrxHrbl42X/RnDPI+wBoZU8joxxuRwedrxqswQ3xfU8= +github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8= +github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo= +github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y= +github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= +github.com/blevesearch/geo v0.2.3 h1:K9/vbGI9ehlXdxjxDRJtoAMt7zGAsMIzc6n8zWcwnhg= +github.com/blevesearch/geo v0.2.3/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8= +github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U= +github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= -github.com/blevesearch/scorch_segment_api/v2 v2.2.9 h1:3nBaSBRFokjE4FtPW3eUDgcAu3KphBg1GP07zy/6Uyk= -github.com/blevesearch/scorch_segment_api/v2 v2.2.9/go.mod h1:ckbeb7knyOOvAdZinn/ASbB7EA3HoagnJkmEV3J7+sg= +github.com/blevesearch/scorch_segment_api/v2 v2.3.10 h1:Yqk0XD1mE0fDZAJXTjawJ8If/85JxnLd8v5vG/jWE/s= +github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= -github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI= -github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= -github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= -github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= -github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= -github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= -github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= -github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= -github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= -github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= -github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ= -github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= -github.com/blevesearch/zapx/v16 v16.0.12 h1:Uccxvjmn+hQ6ywQP+wIiTpdq9LnAviGoryJOmGwAo/I= -github.com/blevesearch/zapx/v16 v16.0.12/go.mod h1:MYnOshRfSm4C4drxx1LGRI+MVFByykJ2anDY1fxdk9Q= +github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= +github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= +github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs= +github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc= +github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE= +github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58= +github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks= +github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk= +github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0= +github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= +github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k= +github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= +github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww= +github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/codeclysm/extract v2.2.0+incompatible h1:q3wyckoA30bhUSiwdQezMqVhwd8+WGE64/GL//LtUhI= github.com/codeclysm/extract v2.2.0+incompatible/go.mod h1:2nhFMPHiU9At61hz+12bfrlpXSUrOnK+wR+KlGO4Uks= +github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= +github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= +github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= -github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g= +github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eyedeekay/goSam v0.32.54 h1:Uq1F9rePGi5aiHZ8J8ZC0HRpf4hvTUR+PJvmcCBpmWU= @@ -108,8 +109,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731/go.mod h1:XZwE+iIlAgr64OFbXKFNCllBwV4wEipPx8Hlo2gZdbM= @@ -150,23 +151,21 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -177,30 +176,31 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo= github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= +github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230808223545-4887780b67fb h1:oqpb3Cwpc7EOml5PVGMYbSGmwNui2R7i8IW83gs4W0c= -github.com/google/pprof v0.0.0-20230808223545-4887780b67fb/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/go-set/v3 v3.0.0 h1:CaJBQvQCOWoftrBcDt7Nwgo0kdpmrKxar/x2o6pV9JA= +github.com/hashicorp/go-set/v3 v3.0.0/go.mod h1:IEghM2MpE5IaNvL+D7X480dfNtxjRXZ6VMpK3C8s2ok= github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298= github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -213,9 +213,10 @@ github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5l github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -223,6 +224,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -234,8 +237,12 @@ github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91 h1:s7fexw github.com/matrix-org/go-sqlite3-js v0.0.0-20220419092513-28aa791a1c91/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo= github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U= github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= -github.com/matrix-org/gomatrixserverlib v0.0.0-20240910190622-2c764912ce93 h1:FbyZ/xkeBVYHi2xfwAVaNmDhP+4HNbt9e6ucOR+jvBk= -github.com/matrix-org/gomatrixserverlib v0.0.0-20240910190622-2c764912ce93/go.mod h1:HZGsVJ3bUE+DkZtufkH9H0mlsvbhEGK5CpX0Zlavylg= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744 h1:5GvC2FD9O/PhuyY95iJQdNYHbDioEhMWdeMP9maDUL8= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250813150445-9f5070a65744/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250814102638-60b9d3e5b634 h1:5MDrrj6hsTEW7Hv7rnWtSUQ4T4SUncFWQQG7vlrXnWw= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250814102638-60b9d3e5b634/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250815065806-6697d93cbcba h1:vUUjTOXZ/bYdF/SmJPH8HZ/UTmvw+ldngFKVLElmn+I= +github.com/matrix-org/gomatrixserverlib v0.0.0-20250815065806-6697d93cbcba/go.mod h1:b6KVfDjXjA5Q7vhpOaMqIhFYvu5BuFVZixlNeTV/CLc= github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7 h1:6t8kJr8i1/1I5nNttw6nn1ryQJgzVlBmSGgPiiaTdw4= github.com/matrix-org/pinecone v0.11.1-0.20230810010612-ea4c33717fd7/go.mod h1:ReWMS/LoVnOiRAdq9sNUC2NZnd1mZkMNB52QhpTRWjg= github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= @@ -246,16 +253,18 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -266,14 +275,16 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= -github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.20 h1:CXDTYNHeBiAKBTAIP2gjpgbWap2GhATnTLgP8etyvEI= -github.com/nats-io/nats-server/v2 v2.10.20/go.mod h1:hgcPnoUtMfxz1qVOvLZGurVypQ+Cg6GXVXjG53iHk+M= -github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU= -github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI= +github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= +github.com/nats-io/nats-server/v2 v2.11.7 h1:lINWQ/Hb3cnaoHmWTjj/7WppZnaSh9C/1cD//nHCbms= +github.com/nats-io/nats-server/v2 v2.11.7/go.mod h1:DchDPVzAsAPqhqm7VLedX0L7hjnV/SYtlmsl9F8U53s= +github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M= +github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= +github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -281,6 +292,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oleiade/lane/v2 v2.0.0 h1:XW/ex/Inr+bPkLd3O240xrFOhUkTd4Wy176+Gv0E3Qw= +github.com/oleiade/lane/v2 v2.0.0/go.mod h1:i5FBPFAYSWCgLh58UkUGCChjcCzef/MI7PlQm2TKCeg= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= @@ -298,32 +311,32 @@ github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwb github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/quic-go/quic-go v0.45.2 h1:DfqBmqjb4ExSdxRIb/+qXhPC+7k6+DUNZha4oeiC9fY= -github.com/quic-go/quic-go v0.45.2/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/shoenig/test v1.11.0 h1:NoPa5GIoBwuqzIviCrnUJa+t5Xb4xi5Z+zODJnIDsEQ= +github.com/shoenig/test v1.11.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -333,11 +346,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= -github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -356,29 +369,30 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/yggdrasil-network/yggdrasil-go v0.5.6 h1:thh5YQYXQgkhkSO6v2D9Ya9fLHXfY38VfsCTZTIbIeI= -github.com/yggdrasil-network/yggdrasil-go v0.5.6/go.mod h1:WAqMZ4e1QSf/EKbzfD77XXTSAIRO/0nWKCVpHsKLg40= -github.com/yggdrasil-network/yggquic v0.0.0-20240802104827-b4e97a928967 h1:IxtZy4a4ZFYc1OiEv1VUc8u4Xl1WF6986wfu1DbY/SI= -github.com/yggdrasil-network/yggquic v0.0.0-20240802104827-b4e97a928967/go.mod h1:RVLAuYojgYebPO/fJwWRSVlzKLXbZzZpWAStnBwiSsk= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/yggdrasil-network/yggdrasil-go v0.5.12 h1:SaQ8d59JP+uFy+nOWXTx1ETM5r2uCfe1Gt/d+IodHJw= +github.com/yggdrasil-network/yggdrasil-go v0.5.12/go.mod h1:u4DU6dpTfWmVs8r0WjW1T3UpGyeUh9vRrS8zngvncwM= +github.com/yggdrasil-network/yggquic v0.0.0-20241212194307-0d495106021f h1:nqinj7N9gyDNKvSAoQK8OTg1RnEE5Bu/01oaC1TMT1o= +github.com/yggdrasil-network/yggquic v0.0.0-20241212194307-0d495106021f/go.mod h1:TVCKOUWiXR9cAqr3eDpKvXkVkTph38xwk0wjcvfrtKI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -391,53 +405,41 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySFVJb9rsODuiO+WXu8HrUuySb7nYFw= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -445,61 +447,38 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -508,22 +487,20 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/h2non/bimg.v1 v1.1.9 h1:wZIUbeOnwr37Ta4aofhIv8OI8v4ujpjXC9mXnAGpQjM= -gopkg.in/h2non/bimg.v1 v1.1.9/go.mod h1:PgsZL7dLwUbsGm1NYps320GxGgvQNTnecMCZqxV11So= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI= @@ -533,27 +510,35 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8= maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho= maunium.net/go/mautrix v0.15.1 h1:pmCtMjYRpd83+2UL+KTRFYQo5to0373yulimvLK+1k0= maunium.net/go/mautrix v0.15.1/go.mod h1:icQIrvz2NldkRLTuzSGzmaeuMUmw+fzO7UVycPeauN8= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= -modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE= -modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= +modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/helm/dendrite/Chart.yaml b/helm/dendrite/Chart.yaml index 6f4a9825c..b9ea7bf38 100644 --- a/helm/dendrite/Chart.yaml +++ b/helm/dendrite/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: dendrite -version: "0.14.6" -appVersion: "0.13.8" +version: "0.15.1" +appVersion: "0.14.1" description: Dendrite Matrix Homeserver type: application icon: https://avatars.githubusercontent.com/u/8418310?s=48&v=4 diff --git a/helm/dendrite/README.md b/helm/dendrite/README.md index 9973c90bc..5cafac9d8 100644 --- a/helm/dendrite/README.md +++ b/helm/dendrite/README.md @@ -1,7 +1,7 @@ # dendrite -![Version: 0.14.4](https://img.shields.io/badge/Version-0.14.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.8](https://img.shields.io/badge/AppVersion-0.13.8-informational?style=flat-square) +![Version: 0.15.1](https://img.shields.io/badge/Version-0.15.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.14.1](https://img.shields.io/badge/AppVersion-0.14.1-informational?style=flat-square) Dendrite Matrix Homeserver Status: **NOT PRODUCTION READY** @@ -59,11 +59,15 @@ Create a folder `appservices` and place your configurations in there. The confi | persistence.search.existingClaim | string | `""` | Use an existing volume claim for the fulltext search index | | persistence.search.capacity | string | `"1Gi"` | PVC Storage Request for the search volume | | persistence.search.storageClass | string | `nil` | The storage class to use for volume claims. Defaults to persistence.storageClass If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) | +| extraArgs | list | `[]` | Add additional arguments to the dendrite command | | extraVolumes | list | `[]` | Add additional volumes to the Dendrite Pod | | extraVolumeMounts | list | `[]` | Configure additional mount points volumes in the Dendrite Pod | | strategy.type | string | `"Recreate"` | Strategy to use for rolling updates (e.g. Recreate, RollingUpdate) If you are using ReadWriteOnce volumes, you should probably use Recreate | | strategy.rollingUpdate.maxUnavailable | string | `"25%"` | Maximum number of pods that can be unavailable during the update process | | strategy.rollingUpdate.maxSurge | string | `"25%"` | Maximum number of pods that can be scheduled above the desired number of pods | +| nodeSelector | object | `{}` | Node selector configuration | +| tolerations | object | `{}` | Tolerations configuration | +| affinity | object | `{}` | Affinity configuration | | dendrite_config.version | int | `2` | | | dendrite_config.global.server_name | string | `""` | **REQUIRED** Servername for this Dendrite deployment. | | dendrite_config.global.private_key | string | `"/etc/dendrite/secrets/signing.key"` | The private key to use. (**NOTE**: This is overriden in Helm) | @@ -190,5 +194,3 @@ grafana: ``` PS: The label `release=kube-prometheus-stack` is setup with the helmchart of the Prometheus Operator. For Grafana Dashboards it may be necessary to enable scanning in the correct namespaces (or ALL), enabled by `sidecar.dashboards.searchNamespace` in [Helmchart of grafana](https://artifacthub.io/packages/helm/grafana/grafana) (which is part of PrometheusOperator, so `grafana.sidecar.dashboards.searchNamespace`) ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/internal/eventutil/eventcontent.go b/internal/eventutil/eventcontent.go index 1a3222f85..2dab1a4c0 100644 --- a/internal/eventutil/eventcontent.go +++ b/internal/eventutil/eventcontent.go @@ -37,7 +37,7 @@ type CanonicalAlias struct { // if they have not been specified. // http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels // https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L294 -func InitialPowerLevelsContent(roomCreator string) (c gomatrixserverlib.PowerLevelContent) { +func InitialPowerLevelsContent(roomVersion gomatrixserverlib.IRoomVersion, roomCreator string) (c gomatrixserverlib.PowerLevelContent) { c.Defaults() c.Events = map[string]int64{ "m.room.name": 50, @@ -49,7 +49,12 @@ func InitialPowerLevelsContent(roomCreator string) (c gomatrixserverlib.PowerLev "m.room.encryption": 100, "m.room.server_acl": 100, } - c.Users = map[string]int64{roomCreator: 100} + c.Users = map[string]int64{} + if roomVersion.PrivilegedCreators() { + c.Events["m.room.tombstone"] = 150 + } else { + c.Users[roomCreator] = 100 + } return c } diff --git a/internal/eventutil/events.go b/internal/eventutil/events.go index 852caab20..958999ee3 100644 --- a/internal/eventutil/events.go +++ b/internal/eventutil/events.go @@ -67,16 +67,24 @@ func BuildEvent( identity *fclient.SigningIdentity, evTime time.Time, eventsNeeded *gomatrixserverlib.StateNeeded, queryRes *api.QueryLatestEventsAndStateResponse, ) (*types.HeaderedEvent, error) { - if err := addPrevEventsToEvent(proto, eventsNeeded, queryRes); err != nil { - return nil, err - } - verImpl, err := gomatrixserverlib.GetRoomVersion(queryRes.RoomVersion) if err != nil { return nil, err } + proto.Version = verImpl + if err = addPrevEventsToEvent(proto, eventsNeeded, queryRes); err != nil { + return nil, err + } + builder := verImpl.NewEventBuilderFromProtoEvent(proto) + if verImpl.DomainlessRoomIDs() && builder.RoomID != "" && proto.Type == spec.MRoomCreate && proto.StateKey != nil && *proto.StateKey == "" { + return nil, gomatrixserverlib.EventValidationError{ + Message: "cannot resend m.room.create event", + Code: 400, + } + } + event, err := builder.Build( evTime, identity.ServerName, identity.KeyID, identity.PrivateKey, @@ -123,7 +131,7 @@ func addPrevEventsToEvent( builder.Depth = queryRes.Depth - authEvents := gomatrixserverlib.NewAuthEvents(nil) + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) for i := range queryRes.StateEvents { err := authEvents.AddEvent(queryRes.StateEvents[i].PDU) @@ -132,12 +140,26 @@ func addPrevEventsToEvent( } } - refs, err := eventsNeeded.AuthEventReferences(&authEvents) + refs, err := eventsNeeded.AuthEventReferences(authEvents) if err != nil { return fmt.Errorf("eventsNeeded.AuthEventReferences: %w", err) } + var authEventIDs []string + if builder.Version.DomainlessRoomIDs() && len(builder.RoomID) > 0 { + // the room ID is the create event so we shouldn't set it in auth_events + authEventIDs = make([]string, 0, len(refs)) + createEventID := fmt.Sprintf("$%s", builder.RoomID[1:]) + for _, id := range refs { + if id == createEventID { + continue + } + authEventIDs = append(authEventIDs, id) + } + } else { + authEventIDs = refs + } - builder.AuthEvents, builder.PrevEvents = truncateAuthAndPrevEvents(refs, queryRes.LatestEvents) + builder.AuthEvents, builder.PrevEvents = truncateAuthAndPrevEvents(authEventIDs, queryRes.LatestEvents) return nil } diff --git a/internal/transactionrequest.go b/internal/transactionrequest.go index 26f2bcab3..55ff3656a 100644 --- a/internal/transactionrequest.go +++ b/internal/transactionrequest.go @@ -12,13 +12,13 @@ import ( "fmt" "sync" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/federationapi/producers" "github.com/element-hq/dendrite/federationapi/types" "github.com/element-hq/dendrite/roomserver/api" rstypes "github.com/element-hq/dendrite/roomserver/types" syncTypes "github.com/element-hq/dendrite/syncapi/types" userAPI "github.com/element-hq/dendrite/userapi/api" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" @@ -134,6 +134,8 @@ func (t *TxnReq) ProcessTransaction(ctx context.Context) (*fclient.RespSend, *ut } event, err := verImpl.NewEventFromUntrustedJSON(pdu) if err != nil { + /* Do not reject the entire transaction for a single bad PDU, that's dumb. + if _, ok := err.(gomatrixserverlib.BadJSONError); ok { // Room version 6 states that homeservers should strictly enforce canonical JSON // on PDUs. @@ -146,7 +148,7 @@ func (t *TxnReq) ProcessTransaction(ctx context.Context) (*fclient.RespSend, *ut Code: 400, JSON: spec.BadJSON("PDU contains bad JSON"), } - } + } */ util.GetLogger(ctx).WithError(err).Debugf("Transaction: Failed to parse event JSON of event %s", string(pdu)) continue } @@ -216,13 +218,17 @@ func (t *TxnReq) processEDUs(ctx context.Context) { util.GetLogger(ctx).WithError(err).Debug("Failed to unmarshal typing event") continue } - if _, serverName, err := gomatrixserverlib.SplitID('@', typingPayload.UserID); err != nil { + _, serverName, err := gomatrixserverlib.SplitID('@', typingPayload.UserID) + if err != nil { continue } else if serverName == t.ourServerName { continue } else if serverName != t.Origin { continue } + if api.IsServerBannedFromRoom(ctx, t.rsAPI, typingPayload.RoomID, serverName) { + continue + } if err := t.producer.SendTyping(ctx, typingPayload.UserID, typingPayload.RoomID, typingPayload.Typing, 30*1000); err != nil { util.GetLogger(ctx).WithError(err).Error("Failed to send typing event to JetStream") } @@ -278,6 +284,9 @@ func (t *TxnReq) processEDUs(ctx context.Context) { util.GetLogger(ctx).Debugf("Dropping receipt event where sender domain (%q) doesn't match origin (%q)", domain, t.Origin) continue } + if api.IsServerBannedFromRoom(ctx, t.rsAPI, roomID, domain) { + continue + } if err := t.processReceiptEvent(ctx, userID, roomID, "m.read", mread.Data.TS, mread.EventIDs); err != nil { util.GetLogger(ctx).WithError(err).WithFields(logrus.Fields{ "sender": t.Origin, diff --git a/internal/version.go b/internal/version.go index 6e5a5f94e..c6929ee6b 100644 --- a/internal/version.go +++ b/internal/version.go @@ -17,8 +17,8 @@ var build string const ( VersionMajor = 0 - VersionMinor = 13 - VersionPatch = 8 + VersionMinor = 15 + VersionPatch = 2 VersionTag = "" // example: "rc1" gitRevLen = 7 // 7 matches the displayed characters on github.com diff --git a/mediaapi/README.md b/mediaapi/README.md deleted file mode 100644 index baf5587fc..000000000 --- a/mediaapi/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Media API - -This server is responsible for serving `/media` requests as per: - -http://matrix.org/docs/spec/client_server/r0.2.0.html#id43 - -## Scaling libraries - -### nfnt/resize (default) - -Thumbnailing uses https://github.com/nfnt/resize by default which is a pure golang image scaling library relying on image codecs from the standard library. It is ISC-licensed. - -It is multi-threaded and uses Lanczos3 so produces sharp images. Using Lanczos3 all the way makes it slower than some other approaches like bimg. (~845ms in total for pre-generating 32x32-crop, 96x96-crop, 320x240-scale, 640x480-scale and 800x600-scale from a given JPEG image on a given machine.) - -See the sample below for image quality with nfnt/resize: - -![](nfnt-96x96-crop.jpg) - -### bimg (uses libvips C library) - -Alternatively one can use `go build -tags bimg` to use bimg from https://github.com/h2non/bimg (MIT-licensed) which uses libvips from https://github.com/jcupitt/libvips (LGPL v2.1+ -licensed). libvips is a C library and must be installed/built separately. See the github page for details. Also note that libvips in turn has dependencies with a selection of FOSS licenses. - -bimg and libvips have significantly better performance than nfnt/resize but produce slightly less-sharp images. bimg uses a box filter for downscaling to within about 200% of the target scale and then uses Lanczos3 for the last bit. This is a much faster approach but comes at the expense of sharpness. (~295ms in total for pre-generating 32x32-crop, 96x96-crop, 320x240-scale, 640x480-scale and 800x600-scale from a given JPEG image on a given machine.) - -See the sample below for image quality with bimg: - -![](bimg-96x96-crop.jpg) diff --git a/mediaapi/mediaapi.go b/mediaapi/mediaapi.go index ac20c886f..307009323 100644 --- a/mediaapi/mediaapi.go +++ b/mediaapi/mediaapi.go @@ -7,6 +7,8 @@ package mediaapi import ( + "github.com/sirupsen/logrus" + "github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/mediaapi/routing" @@ -15,7 +17,6 @@ import ( userapi "github.com/element-hq/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" - "github.com/sirupsen/logrus" ) // AddPublicRoutes sets up and registers HTTP handlers for the MediaAPI component. diff --git a/mediaapi/routing/routing.go b/mediaapi/routing/routing.go index 006a54b9c..45da8eba6 100644 --- a/mediaapi/routing/routing.go +++ b/mediaapi/routing/routing.go @@ -11,13 +11,13 @@ import ( "net/http" "strings" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/federationapi/routing" "github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/mediaapi/storage" "github.com/element-hq/dendrite/mediaapi/types" "github.com/element-hq/dendrite/setup/config" userapi "github.com/element-hq/dendrite/userapi/api" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/mediaapi/storage/postgres/mediaapi.go b/mediaapi/storage/postgres/mediaapi.go index 870675687..199514df1 100644 --- a/mediaapi/storage/postgres/mediaapi.go +++ b/mediaapi/storage/postgres/mediaapi.go @@ -9,10 +9,10 @@ package postgres import ( // Import the postgres database driver. - _ "github.com/lib/pq" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/mediaapi/storage/shared" "github.com/element-hq/dendrite/setup/config" + _ "github.com/lib/pq" ) // NewDatabase opens a postgres database. diff --git a/mediaapi/thumbnailer/thumbnailer.go b/mediaapi/thumbnailer/thumbnailer.go index 31f616daa..82d7f0926 100644 --- a/mediaapi/thumbnailer/thumbnailer.go +++ b/mediaapi/thumbnailer/thumbnailer.go @@ -14,10 +14,11 @@ import ( "path/filepath" "sync" + log "github.com/sirupsen/logrus" + "github.com/element-hq/dendrite/mediaapi/storage" "github.com/element-hq/dendrite/mediaapi/types" "github.com/element-hq/dendrite/setup/config" - log "github.com/sirupsen/logrus" ) type thumbnailFitness struct { @@ -86,7 +87,7 @@ func getActiveThumbnailGeneration(dst types.Path, _ types.ThumbnailSize, activeT activeThumbnailGeneration.Lock() defer activeThumbnailGeneration.Unlock() if activeThumbnailGenerationResult, ok := activeThumbnailGeneration.PathToResult[string(dst)]; ok { - logger.Info("Waiting for another goroutine to generate the thumbnail.") + logger.Debugf("Waiting for another goroutine to generate the thumbnail %q", dst) // NOTE: Wait unlocks and locks again internally. There is still a deferred Unlock() that will unlock this. activeThumbnailGenerationResult.Cond.Wait() @@ -115,7 +116,7 @@ func broadcastGeneration(dst types.Path, activeThumbnailGeneration *types.Active activeThumbnailGeneration.Lock() defer activeThumbnailGeneration.Unlock() if activeThumbnailGenerationResult, ok := activeThumbnailGeneration.PathToResult[string(dst)]; ok { - logger.Info("Signalling other goroutines waiting for this goroutine to generate the thumbnail.") + logger.Debugf("Signalling other goroutines waiting for this goroutine to generate the thumbnail %q", dst) // Note: errorReturn is a named return value error that is signalled from here to waiting goroutines activeThumbnailGenerationResult.Err = errorReturn activeThumbnailGenerationResult.Cond.Broadcast() @@ -136,7 +137,7 @@ func isThumbnailExists( config.Width, config.Height, config.ResizeMethod, ) if err != nil { - logger.Error("Failed to query database for thumbnail.") + logger.Errorf("Failed to query database for thumbnail %q", dst) return false, err } if thumbnailMetadata != nil { diff --git a/mediaapi/thumbnailer/thumbnailer_bimg.go b/mediaapi/thumbnailer/thumbnailer_bimg.go deleted file mode 100644 index e2c297b5e..000000000 --- a/mediaapi/thumbnailer/thumbnailer_bimg.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2024 New Vector Ltd. -// Copyright 2017 Vector Creations Ltd -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -// Please see LICENSE files in the repository root for full details. - -//go:build bimg -// +build bimg - -package thumbnailer - -import ( - "context" - "os" - "time" - - "github.com/element-hq/dendrite/mediaapi/storage" - "github.com/element-hq/dendrite/mediaapi/types" - "github.com/element-hq/dendrite/setup/config" - log "github.com/sirupsen/logrus" - "gopkg.in/h2non/bimg.v1" -) - -// GenerateThumbnails generates the configured thumbnail sizes for the source file -func GenerateThumbnails( - ctx context.Context, - src types.Path, - configs []config.ThumbnailSize, - mediaMetadata *types.MediaMetadata, - activeThumbnailGeneration *types.ActiveThumbnailGeneration, - maxThumbnailGenerators int, - db storage.Database, - logger *log.Entry, -) (busy bool, errorReturn error) { - buffer, err := bimg.Read(string(src)) - if err != nil { - logger.WithError(err).WithField("src", src).Error("Failed to read src file") - return false, err - } - img := bimg.NewImage(buffer) - for _, config := range configs { - // Note: createThumbnail does locking based on activeThumbnailGeneration - busy, err = createThumbnail( - ctx, src, img, types.ThumbnailSize(config), mediaMetadata, activeThumbnailGeneration, - maxThumbnailGenerators, db, logger, - ) - if err != nil { - logger.WithError(err).WithField("src", src).Error("Failed to generate thumbnails") - return false, err - } - if busy { - return true, nil - } - } - return false, nil -} - -// GenerateThumbnail generates the configured thumbnail size for the source file -func GenerateThumbnail( - ctx context.Context, - src types.Path, - config types.ThumbnailSize, - mediaMetadata *types.MediaMetadata, - activeThumbnailGeneration *types.ActiveThumbnailGeneration, - maxThumbnailGenerators int, - db storage.Database, - logger *log.Entry, -) (busy bool, errorReturn error) { - buffer, err := bimg.Read(string(src)) - if err != nil { - logger.WithError(err).WithFields(log.Fields{ - "src": src, - }).Error("Failed to read src file") - return false, err - } - img := bimg.NewImage(buffer) - // Note: createThumbnail does locking based on activeThumbnailGeneration - busy, err = createThumbnail( - ctx, src, img, config, mediaMetadata, activeThumbnailGeneration, - maxThumbnailGenerators, db, logger, - ) - if err != nil { - logger.WithError(err).WithFields(log.Fields{ - "src": src, - }).Error("Failed to generate thumbnails") - return false, err - } - if busy { - return true, nil - } - return false, nil -} - -// createThumbnail checks if the thumbnail exists, and if not, generates it -// Thumbnail generation is only done once for each non-existing thumbnail. -func createThumbnail( - ctx context.Context, - src types.Path, - img *bimg.Image, - config types.ThumbnailSize, - mediaMetadata *types.MediaMetadata, - activeThumbnailGeneration *types.ActiveThumbnailGeneration, - maxThumbnailGenerators int, - db storage.Database, - logger *log.Entry, -) (busy bool, errorReturn error) { - logger = logger.WithFields(log.Fields{ - "Width": config.Width, - "Height": config.Height, - "ResizeMethod": config.ResizeMethod, - }) - - // Check if request is larger than original - if isLargerThanOriginal(config, img) { - return false, nil - } - - dst := GetThumbnailPath(src, config) - - // Note: getActiveThumbnailGeneration uses mutexes and conditions from activeThumbnailGeneration - isActive, busy, err := getActiveThumbnailGeneration(dst, config, activeThumbnailGeneration, maxThumbnailGenerators, logger) - if err != nil { - return false, err - } - if busy { - return true, nil - } - - if isActive { - // Note: This is an active request that MUST broadcastGeneration to wake up waiting goroutines! - // Note: broadcastGeneration uses mutexes and conditions from activeThumbnailGeneration - defer func() { - // Note: errorReturn is the named return variable so we wrap this in a closure to re-evaluate the arguments at defer-time - if err := recover(); err != nil { - broadcastGeneration(dst, activeThumbnailGeneration, config, err.(error), logger) - panic(err) - } - broadcastGeneration(dst, activeThumbnailGeneration, config, errorReturn, logger) - }() - } - - exists, err := isThumbnailExists(ctx, dst, config, mediaMetadata, db, logger) - if err != nil || exists { - return false, err - } - - start := time.Now() - width, height, err := resize(dst, img, config.Width, config.Height, config.ResizeMethod == "crop", logger) - if err != nil { - return false, err - } - logger.WithFields(log.Fields{ - "ActualWidth": width, - "ActualHeight": height, - "processTime": time.Now().Sub(start), - }).Info("Generated thumbnail") - - stat, err := os.Stat(string(dst)) - if err != nil { - return false, err - } - - thumbnailMetadata := &types.ThumbnailMetadata{ - MediaMetadata: &types.MediaMetadata{ - MediaID: mediaMetadata.MediaID, - Origin: mediaMetadata.Origin, - // Note: the code currently always creates a JPEG thumbnail - ContentType: types.ContentType("image/jpeg"), - FileSizeBytes: types.FileSizeBytes(stat.Size()), - }, - ThumbnailSize: types.ThumbnailSize{ - Width: config.Width, - Height: config.Height, - ResizeMethod: config.ResizeMethod, - }, - } - - err = db.StoreThumbnail(ctx, thumbnailMetadata) - if err != nil { - logger.WithError(err).WithFields(log.Fields{ - "ActualWidth": width, - "ActualHeight": height, - }).Error("Failed to store thumbnail metadata in database.") - return false, err - } - - return false, nil -} - -func isLargerThanOriginal(config types.ThumbnailSize, img *bimg.Image) bool { - imgSize, err := img.Size() - if err == nil && config.Width >= imgSize.Width && config.Height >= imgSize.Height { - return true - } - return false -} - -// resize scales an image to fit within the provided width and height -// If the source aspect ratio is different to the target dimensions, one edge will be smaller than requested -// If crop is set to true, the image will be scaled to fill the width and height with any excess being cropped off -func resize(dst types.Path, inImage *bimg.Image, w, h int, crop bool, logger *log.Entry) (int, int, error) { - inSize, err := inImage.Size() - if err != nil { - return -1, -1, err - } - - options := bimg.Options{ - Type: bimg.JPEG, - Quality: 85, - } - if crop { - options.Width = w - options.Height = h - options.Crop = true - } else { - inAR := float64(inSize.Width) / float64(inSize.Height) - outAR := float64(w) / float64(h) - - if inAR > outAR { - // input has wider AR than requested output so use requested width and calculate height to match input AR - options.Width = w - options.Height = int(float64(w) / inAR) - } else { - // input has narrower AR than requested output so use requested height and calculate width to match input AR - options.Width = int(float64(h) * inAR) - options.Height = h - } - } - - newImage, err := inImage.Process(options) - if err != nil { - return -1, -1, err - } - - if err = bimg.Write(string(dst), newImage); err != nil { - logger.WithError(err).Error("Failed to resize image") - return -1, -1, err - } - - return options.Width, options.Height, nil -} diff --git a/mediaapi/thumbnailer/thumbnailer_nfnt.go b/mediaapi/thumbnailer/thumbnailer_nfnt.go index f4a366067..144e385d9 100644 --- a/mediaapi/thumbnailer/thumbnailer_nfnt.go +++ b/mediaapi/thumbnailer/thumbnailer_nfnt.go @@ -4,9 +4,6 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -//go:build !bimg -// +build !bimg - package thumbnailer import ( @@ -20,18 +17,18 @@ import ( // Imported for png codec _ "image/png" + "os" + "time" // Imported for webp codec _ "golang.org/x/image/webp" - "os" - "time" + "github.com/nfnt/resize" + log "github.com/sirupsen/logrus" "github.com/element-hq/dendrite/mediaapi/storage" "github.com/element-hq/dendrite/mediaapi/types" "github.com/element-hq/dendrite/setup/config" - "github.com/nfnt/resize" - log "github.com/sirupsen/logrus" ) // GenerateThumbnails generates the configured thumbnail sizes for the source file @@ -191,7 +188,7 @@ func createThumbnail( "ActualWidth": width, "ActualHeight": height, "processTime": time.Since(start), - }).Info("Generated thumbnail") + }).Debugf("Generated thumbnail %q", dst) stat, err := os.Stat(string(dst)) if err != nil { diff --git a/relayapi/relayapi_test.go b/relayapi/relayapi_test.go index e4e8b25db..d27111bce 100644 --- a/relayapi/relayapi_test.go +++ b/relayapi/relayapi_test.go @@ -15,7 +15,6 @@ import ( "testing" "time" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/cmd/dendrite-demo-yggdrasil/signing" "github.com/element-hq/dendrite/internal/caching" "github.com/element-hq/dendrite/internal/httputil" @@ -23,6 +22,7 @@ import ( "github.com/element-hq/dendrite/relayapi" "github.com/element-hq/dendrite/test" "github.com/element-hq/dendrite/test/testrig" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/relayapi/routing/routing.go b/relayapi/routing/routing.go index 4d810a624..abb397151 100644 --- a/relayapi/routing/routing.go +++ b/relayapi/routing/routing.go @@ -11,11 +11,11 @@ import ( "net/http" "time" - "github.com/getsentry/sentry-go" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/internal/httputil" relayInternal "github.com/element-hq/dendrite/relayapi/internal" "github.com/element-hq/dendrite/setup/config" + "github.com/getsentry/sentry-go" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/relayapi/storage/postgres/relay_queue_json_table.go b/relayapi/storage/postgres/relay_queue_json_table.go index 5b7a23336..cc15a5ed1 100644 --- a/relayapi/storage/postgres/relay_queue_json_table.go +++ b/relayapi/storage/postgres/relay_queue_json_table.go @@ -10,9 +10,9 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" ) const relayQueueJSONSchema = ` diff --git a/relayapi/storage/postgres/relay_queue_table.go b/relayapi/storage/postgres/relay_queue_table.go index c8e634654..79d5ecd8d 100644 --- a/relayapi/storage/postgres/relay_queue_table.go +++ b/relayapi/storage/postgres/relay_queue_table.go @@ -10,9 +10,9 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/roomserver/acls/acls.go b/roomserver/acls/acls.go index 201f36df7..246342c93 100644 --- a/roomserver/acls/acls.go +++ b/roomserver/acls/acls.go @@ -14,9 +14,9 @@ import ( "regexp" "strings" "sync" + "time" "github.com/element-hq/dendrite/roomserver/storage/tables" - "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/sirupsen/logrus" ) @@ -27,9 +27,8 @@ type ServerACLDatabase interface { // RoomsWithACLs returns all room IDs for rooms with ACLs RoomsWithACLs(ctx context.Context) ([]string, error) - // GetBulkStateContent returns all state events which match a given room ID and a given state key tuple. Both must be satisfied for a match. - // If a tuple has the StateKey of '*' and allowWildcards=true then all state events with the EventType should be returned. - GetBulkStateContent(ctx context.Context, roomIDs []string, tuples []gomatrixserverlib.StateKeyTuple, allowWildcards bool) ([]tables.StrippedEvent, error) + // GetBulkStateACLs returns all server ACLs for the given rooms. + GetBulkStateACLs(ctx context.Context, roomIDs []string) ([]tables.StrippedEvent, error) } type ServerACLs struct { @@ -40,6 +39,16 @@ type ServerACLs struct { } func NewServerACLs(db ServerACLDatabase) *ServerACLs { + // Add some logging, as this can take a while on larger instances. + logrus.Infof("Loading server ACLs...") + start := time.Now() + aclCount := 0 + defer func() { + logrus.WithFields(logrus.Fields{ + "duration": time.Since(start), + "acls": aclCount, + }).Info("Finished loading server ACLs") + }() ctx := context.TODO() acls := &ServerACLs{ acls: make(map[string]*serverACL), @@ -48,20 +57,25 @@ func NewServerACLs(db ServerACLDatabase) *ServerACLs { aclRegexCache: make(map[string]**regexp.Regexp, 100), } - // Look up all of the rooms that the current state server knows about. + // Look up all rooms with ACLs. rooms, err := db.RoomsWithACLs(ctx) if err != nil { logrus.WithError(err).Fatalf("Failed to get known rooms") } - // For each room, let's see if we have a server ACL state event. If we - // do then we'll process it into memory so that we have the regexes to - // hand. - events, err := db.GetBulkStateContent(ctx, rooms, []gomatrixserverlib.StateKeyTuple{{EventType: MRoomServerACL, StateKey: ""}}, false) + // No rooms with ACLs, don't bother hitting the DB again. + if len(rooms) == 0 { + return acls + } + + // Get ACLs for the required rooms, bail if we are unable to get them. + events, err := db.GetBulkStateACLs(ctx, rooms) if err != nil { - logrus.WithError(err).Errorf("Failed to get server ACLs for all rooms: %q", err) + logrus.WithError(err).Fatal("Failed to get server ACLs for all rooms") } + aclCount = len(events) + for _, event := range events { acls.OnServerACLUpdate(event) } diff --git a/roomserver/acls/acls_test.go b/roomserver/acls/acls_test.go index 16f3887d2..d5a36a619 100644 --- a/roomserver/acls/acls_test.go +++ b/roomserver/acls/acls_test.go @@ -12,7 +12,6 @@ import ( "testing" "github.com/element-hq/dendrite/roomserver/storage/tables" - "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/stretchr/testify/assert" ) @@ -108,11 +107,11 @@ var ( type dummyACLDB struct{} -func (d dummyACLDB) RoomsWithACLs(ctx context.Context) ([]string, error) { +func (d dummyACLDB) RoomsWithACLs(_ context.Context) ([]string, error) { return []string{"1", "2"}, nil } -func (d dummyACLDB) GetBulkStateContent(ctx context.Context, roomIDs []string, tuples []gomatrixserverlib.StateKeyTuple, allowWildcards bool) ([]tables.StrippedEvent, error) { +func (d dummyACLDB) GetBulkStateACLs(_ context.Context, _ []string) ([]tables.StrippedEvent, error) { return []tables.StrippedEvent{ { RoomID: "1", diff --git a/roomserver/api/api.go b/roomserver/api/api.go index 9493995e1..35f1d0b62 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -243,7 +243,7 @@ type ClientRoomserverAPI interface { PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *PerformCreateRoomRequest) (string, *util.JSONResponse) // PerformRoomUpgrade upgrades a room to a newer version - PerformRoomUpgrade(ctx context.Context, roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion) (newRoomID string, err error) + PerformRoomUpgrade(ctx context.Context, roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string) (newRoomID string, err error) PerformAdminEvacuateRoom(ctx context.Context, roomID string) (affected []string, err error) PerformAdminEvacuateUser(ctx context.Context, userID string) (affected []string, err error) PerformAdminPurgeRoom(ctx context.Context, roomID string) error diff --git a/roomserver/api/input.go b/roomserver/api/input.go index 748e3ac78..06dd58127 100644 --- a/roomserver/api/input.go +++ b/roomserver/api/input.go @@ -15,6 +15,9 @@ import ( "github.com/matrix-org/gomatrixserverlib/spec" ) +// for detecting rejected events and returning 403 instead of 500ing +const InputWasRejected = "InputWasRejected" + type Kind int const ( @@ -108,5 +111,5 @@ func (r *InputRoomEventsResponse) Err() error { Message: r.ErrMsg, } } - return fmt.Errorf("InputRoomEventsResponse: %s", r.ErrMsg) + return fmt.Errorf(r.ErrMsg) } diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index e448dcfa9..e945cfcaf 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -17,6 +17,7 @@ type PerformCreateRoomRequest struct { Topic string StatePreset string CreationContent json.RawMessage + CreateEvent json.RawMessage InitialState []gomatrixserverlib.FledglingEvent RoomAliasName string RoomVersion gomatrixserverlib.RoomVersion diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index 7023e2c2f..141156397 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -8,6 +8,10 @@ package api import ( "context" + "encoding/json" + "fmt" + "net/http" + "time" "github.com/element-hq/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" @@ -216,3 +220,117 @@ func PopulatePublicRooms(ctx context.Context, roomIDs []string, rsAPI QueryBulkS } return chunk, nil } + +func GenerateCreateContent(ctx context.Context, roomVer gomatrixserverlib.RoomVersion, senderID string, createContentJSON json.RawMessage, additionalCreators []string) (map[string]any, error) { + createContent := map[string]any{} + if len(createContentJSON) > 0 { + if err := json.Unmarshal(createContentJSON, &createContent); err != nil { + return nil, fmt.Errorf("invalid create content: %s", err) + } + } + // TODO: Maybe, at some point, GMSL should return the events to create, so we can define the version + // entirely there. + switch roomVer { + case gomatrixserverlib.RoomVersionV11: + fallthrough + case gomatrixserverlib.RoomVersionV12: + // RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175 + default: + createContent["creator"] = senderID + } + createContent["room_version"] = string(roomVer) + + verImpl := gomatrixserverlib.MustGetRoomVersion(roomVer) + + if verImpl.PrivilegedCreators() { + var finalAdditionalCreators []string + creatorsSet := make(map[string]struct{}) + var unverifiedCreators []string + unverifiedCreators = append(unverifiedCreators, additionalCreators...) + // they get added to any additional creators specified already + existingAdditionalCreators, ok := createContent["additional_creators"].([]any) + if ok { + for _, add := range existingAdditionalCreators { + addStr, ok := add.(string) + if ok { + unverifiedCreators = append(unverifiedCreators, addStr) + } + } + } + + for _, add := range unverifiedCreators { + if _, exists := creatorsSet[add]; exists { + continue + } + _, err := spec.NewUserID(add, true) + if err != nil { + return nil, fmt.Errorf("invalid additional creator: '%s': %s", add, err) + } + finalAdditionalCreators = append(finalAdditionalCreators, add) + creatorsSet[add] = struct{}{} + } + if len(finalAdditionalCreators) > 0 { + createContent["additional_creators"] = finalAdditionalCreators + } + } + + return createContent, nil +} + +func GeneratePDU( + ctx context.Context, verImpl gomatrixserverlib.IRoomVersion, e gomatrixserverlib.FledglingEvent, authEvents *gomatrixserverlib.AuthEvents, depth int, prevEventID string, + identity *fclient.SigningIdentity, timestamp time.Time, senderID, roomID string, queryer QuerySenderIDAPI, +) (gomatrixserverlib.PDU, *util.JSONResponse) { + builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{ + SenderID: senderID, + RoomID: roomID, + Type: e.Type, + StateKey: &e.StateKey, + Depth: int64(depth), + }) + err := builder.SetContent(e.Content) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed") + return nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + if prevEventID != "" { + builder.PrevEvents = []string{prevEventID} + } + var ev gomatrixserverlib.PDU + if err = builder.AddAuthEvents(authEvents); err != nil { + util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed") + return nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + ev, err = builder.Build(timestamp, identity.ServerName, identity.KeyID, identity.PrivateKey) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("buildEvent failed") + return nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + + if err = gomatrixserverlib.Allowed(ev, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + return queryer.QueryUserIDForSender(ctx, roomID, senderID) + }); err != nil { + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed") + validationErr, ok := err.(*gomatrixserverlib.EventValidationError) + if ok { + return nil, &util.JSONResponse{ + Code: validationErr.Code, + JSON: spec.Forbidden(err.Error()), + } + } + return nil, &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: spec.Forbidden(err.Error()), + } + } + return ev, nil +} diff --git a/roomserver/internal/alias.go b/roomserver/internal/alias.go index b66cd09a8..c7e2283a2 100644 --- a/roomserver/internal/alias.go +++ b/roomserver/internal/alias.go @@ -11,6 +11,7 @@ import ( "database/sql" "errors" "fmt" + "slices" "time" asAPI "github.com/element-hq/dendrite/appservice/api" @@ -134,7 +135,7 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(ctx context.Context, senderID sp } if spec.SenderID(creatorID) != senderID { - var plEvent *types.HeaderedEvent + var createEvent, plEvent *types.HeaderedEvent var pls *gomatrixserverlib.PowerLevelContent plEvent, err = r.DB.GetStateEvent(ctx, roomID, spec.MRoomPowerLevels, "") @@ -147,7 +148,14 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(ctx context.Context, senderID sp return true, false, fmt.Errorf("plEvent.PowerLevels: %w", err) } - if pls.UserLevel(senderID) < pls.EventLevel(spec.MRoomCanonicalAlias, true) { + createEvent, err = r.DB.GetStateEvent(ctx, roomID, spec.MRoomCreate, "") + if err != nil { + return true, false, fmt.Errorf("r.DB.GetStateEvent: %w", err) + } + isPrivilegedCreator := gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() && + slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) + + if !isPrivilegedCreator && pls.UserLevel(senderID) < pls.EventLevel(spec.MRoomCanonicalAlias, true) { return true, false, nil } } diff --git a/roomserver/internal/input/input.go b/roomserver/internal/input/input.go index 46cd206ad..fb1323381 100644 --- a/roomserver/internal/input/input.go +++ b/roomserver/internal/input/input.go @@ -101,9 +101,12 @@ type worker struct { roomID string subscription *nats.Subscription sentryHub *sentry.Hub + ephemeralSeq uint64 + // last seq we fully processed + durableSeq uint64 } -func (r *Inputer) startWorkerForRoom(roomID string) { +func (r *Inputer) startWorkerForRoom(roomID string, seq uint64) { v, loaded := r.workers.LoadOrStore(roomID, &worker{ r: r, roomID: roomID, @@ -112,6 +115,9 @@ func (r *Inputer) startWorkerForRoom(roomID string) { w := v.(*worker) w.Lock() defer w.Unlock() + + w.ephemeralSeq = seq + if !loaded || w.subscription == nil { streamName := r.Cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEvent) consumer := r.Cfg.Matrix.JetStream.Prefixed("RoomInput" + jetstream.Tokenise(w.roomID)) @@ -226,7 +232,8 @@ func (r *Inputer) Start() error { "", // This is blank because we specified it in BindStream. func(m *nats.Msg) { roomID := m.Header.Get(jetstream.RoomID) - r.startWorkerForRoom(roomID) + meta, _ := m.Metadata() + r.startWorkerForRoom(roomID, meta.Sequence.Stream) _ = m.Ack() }, nats.HeadersOnly(), @@ -265,39 +272,61 @@ func (w *worker) _next() { msgs, err := w.subscription.Fetch(1, nats.Context(ctx)) switch err { case nil: + // Is the server shutting down? If so, stop processing. + if w.r.ProcessContext.Context().Err() != nil { + return + } // Make sure that once we're done here, we queue up another call // to _next in the inbox. defer w.Act(nil, w._next) - - // If no error was reported, but we didn't get exactly one message, - // then skip over this and try again on the next iteration. - if len(msgs) != 1 { + case nats.ErrTimeout, context.DeadlineExceeded, context.Canceled: + // Is the server shutting down? If so, stop processing. + if w.r.ProcessContext.Context().Err() != nil { return } - - case context.DeadlineExceeded, context.Canceled: // The context exceeded, so we've been waiting for more than a // minute for activity in this room. At this point we will shut // down the subscriber to free up resources. It'll get started // again if new activity happens. + w.Lock() + // inside the lock, let's check if the ephemeral consumer saw something new! + // If so, we do have new messages after all, they just came at a bad time. + if w.ephemeralSeq > w.durableSeq { + w.Unlock() + w.Act(nil, w._next) + return + } + if err = w.subscription.Unsubscribe(); err != nil { logrus.WithError(err).Errorf("Failed to unsubscribe to stream for room %q", w.roomID) } - w.Lock() w.subscription = nil w.Unlock() return - + case nats.ErrConsumerDeleted, nats.ErrConsumerNotFound: + w.Lock() + defer w.Unlock() + // The consumer is gone, therefore it's reached the inactivity + // threshold. Clean up and stop processing at this point, if a + // new event comes in for this room then the ordered consumer + // over the entire stream will recreate this anyway. + if err = w.subscription.Unsubscribe(); err != nil { + logrus.WithError(err).Errorf("Failed to unsubscribe to stream for room %q", w.roomID) + } + w.subscription = nil + return default: // Something went wrong while trying to fetch the next event // from the queue. In which case, we'll shut down the subscriber // and wait to be notified about new room activity again. Maybe // the problem will be corrected by then. + // atomically clear the subscription and unsubscribe + w.Lock() + logrus.WithError(err).Errorf("Failed to get next stream message for room %q", w.roomID) if err = w.subscription.Unsubscribe(); err != nil { logrus.WithError(err).Errorf("Failed to unsubscribe to stream for room %q", w.roomID) } - w.Lock() w.subscription = nil w.Unlock() return @@ -306,10 +335,19 @@ func (w *worker) _next() { // Since we either Ack() or Term() the message at this point, we can defer decrementing the room backpressure defer roomserverInputBackpressure.With(prometheus.Labels{"room_id": w.roomID}).Dec() + // If no error was reported, but we didn't get exactly one message, + // then skip over this and try again on the next iteration. + if len(msgs) != 1 { + return + } + // Try to unmarshal the input room event. If the JSON unmarshalling // fails then we'll terminate the message — this notifies NATS that // we are done with the message and never want to see it again. msg := msgs[0] + meta, _ := msg.Metadata() + w.durableSeq = meta.Sequence.Stream + var inputRoomEvent api.InputRoomEvent if err = json.Unmarshal(msg.Data, &inputRoomEvent); err != nil { // using AckWait here makes the call synchronous; 5 seconds is the default value used by NATS @@ -326,6 +364,7 @@ func (w *worker) _next() { // a string, because we might want to return that to the caller if // it was a synchronous request. var errString string + wasRejected := false if err = w.r.processRoomEvent( w.r.ProcessContext.Context(), spec.ServerName(msg.Header.Get("virtual_host")), @@ -339,6 +378,7 @@ func (w *worker) _next() { "event_id": inputRoomEvent.Event.EventID(), "type": inputRoomEvent.Event.Type(), }).Warn("Roomserver rejected event") + wasRejected = true default: if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { w.sentryHub.CaptureException(err) @@ -360,6 +400,10 @@ func (w *worker) _next() { _ = msg.AckSync() } + if wasRejected { + errString = api.InputWasRejected + } + // If it was a synchronous input request then the "sync" field // will be present in the message. That means that someone is // waiting for a response. The temporary inbox name is present in diff --git a/roomserver/internal/input/input_events.go b/roomserver/internal/input/input_events.go index b84db345e..65fd12c86 100644 --- a/roomserver/internal/input/input_events.go +++ b/roomserver/internal/input/input_events.go @@ -205,9 +205,27 @@ func (r *Inputer) processRoomEvent( } } + // Check that the auth events of the event are known. + // If they aren't then we will ask the federation API for them. + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + knownEvents := map[string]*types.Event{} + if err = r.fetchAuthEvents(ctx, logger, roomInfo, virtualHost, headered, authEvents, knownEvents, serverRes.ServerNames); err != nil { + return fmt.Errorf("r.fetchAuthEvents: %w", err) + } + isRejected := false var rejectionErr error + // Check if the event is allowed by its auth events. If it isn't then + // we consider the event to be "rejected" — it will still be persisted. + if err = gomatrixserverlib.Allowed(event, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + return r.Queryer.QueryUserIDForSender(ctx, roomID, senderID) + }); err != nil { + isRejected = true + rejectionErr = err + logger.WithError(rejectionErr).Warnf("Event %s not allowed by auth events", event.EventID()) + } + // At this point we are checking whether we know all of the prev events, and // if we know the state before the prev events. This is necessary before we // try to do `calculateAndSetState` on the event later, otherwise it will fail @@ -283,24 +301,6 @@ func (r *Inputer) processRoomEvent( } } - // Check that the auth events of the event are known. - // If they aren't then we will ask the federation API for them. - authEvents := gomatrixserverlib.NewAuthEvents(nil) - knownEvents := map[string]*types.Event{} - if err = r.fetchAuthEvents(ctx, logger, roomInfo, virtualHost, headered, &authEvents, knownEvents, serverRes.ServerNames); err != nil { - return fmt.Errorf("r.fetchAuthEvents: %w", err) - } - - // Check if the event is allowed by its auth events. If it isn't then - // we consider the event to be "rejected" — it will still be persisted. - if err = gomatrixserverlib.Allowed(event, &authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { - return r.Queryer.QueryUserIDForSender(ctx, roomID, senderID) - }); err != nil { - isRejected = true - rejectionErr = err - logger.WithError(rejectionErr).Warnf("Event %s not allowed by auth events", event.EventID()) - } - // Accumulate the auth event NIDs. authEventIDs := event.AuthEventIDs() authEventNIDs := make([]types.EventNID, 0, len(authEventIDs)) @@ -323,7 +323,7 @@ func (r *Inputer) processRoomEvent( ) } } - } else { + } else if !knownEvents[authEventID].Rejected { authEventNIDs = append(authEventNIDs, knownEvents[authEventID].EventNID) } } @@ -584,7 +584,8 @@ func (r *Inputer) processStateBefore( case input.HasState: // If we're overriding the state then we need to go and retrieve // them from the database. It's a hard error if they are missing. - stateEvents, err := r.DB.EventsFromIDs(ctx, roomInfo, input.StateEventIDs) + var stateEvents []types.Event + stateEvents, err = r.DB.EventsFromIDs(ctx, roomInfo, input.StateEventIDs) if err != nil { return "", nil, fmt.Errorf("r.DB.EventsFromIDs: %w", err) } @@ -620,7 +621,7 @@ func (r *Inputer) processStateBefore( StateToFetch: tuplesNeeded, } stateBeforeRes := &api.QueryStateAfterEventsResponse{} - if err := r.Queryer.QueryStateAfterEvents(ctx, stateBeforeReq, stateBeforeRes); err != nil { + if err = r.Queryer.QueryStateAfterEvents(ctx, stateBeforeReq, stateBeforeRes); err != nil { return "", nil, fmt.Errorf("r.Queryer.QueryStateAfterEvents: %w", err) } switch { @@ -640,10 +641,15 @@ func (r *Inputer) processStateBefore( // At this point, stateBeforeEvent should be populated either by // the supplied state in the input request, or from the prev events. // Check whether the event is allowed or not. - stateBeforeAuth := gomatrixserverlib.NewAuthEvents( + var stateBeforeAuth *gomatrixserverlib.AuthEvents + stateBeforeAuth, err = gomatrixserverlib.NewAuthEvents( gomatrixserverlib.ToPDUs(stateBeforeEvent), ) - if rejectionErr = gomatrixserverlib.Allowed(event, &stateBeforeAuth, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + if err != nil { + rejectionErr = fmt.Errorf("NewAuthEvents failed: %w", err) + return + } + if rejectionErr = gomatrixserverlib.Allowed(event, stateBeforeAuth, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return r.Queryer.QueryUserIDForSender(ctx, roomID, senderID) }); rejectionErr != nil { rejectionErr = fmt.Errorf("Allowed() failed for stateBeforeEvent: %w", rejectionErr) @@ -698,15 +704,14 @@ func (r *Inputer) fetchAuthEvents( } ev := authEvents[0] - isRejected := false if roomInfo != nil { - isRejected, err = r.DB.IsEventRejected(ctx, roomInfo.RoomNID, ev.EventID()) + ev.Rejected, err = r.DB.IsEventRejected(ctx, roomInfo.RoomNID, ev.EventID()) if err != nil && !errors.Is(err, sql.ErrNoRows) { return fmt.Errorf("r.DB.IsEventRejected failed: %w", err) } } known[authEventID] = &ev // don't take the pointer of the iterated event - if !isRejected { + if !ev.Rejected { if err = auth.AddEvent(ev.PDU); err != nil { return fmt.Errorf("auth.AddEvent: %w", err) } @@ -738,8 +743,13 @@ func (r *Inputer) fetchAuthEvents( return fmt.Errorf("no servers provided event auth for event ID %q, tried servers %v", event.EventID(), servers) } + // Start with a clean state and see if we can auth with what the remote + // server told us. Otherwise earlier topologically sorted events could + // fail to be authed by more recent referenced ones. + auth.Clear() + // Reuse these to reduce allocations. - authEventNIDs := make([]types.EventNID, 0, 5) + _authEventNIDs := [5]types.EventNID{} isRejected := false nextAuthEvent: for _, authEvent := range gomatrixserverlib.ReverseTopologicalOrdering( @@ -749,7 +759,11 @@ nextAuthEvent: // If we already know about this event from the database then we don't // need to store it again or do anything further with it, so just skip // over it rather than wasting cycles. - if ev, ok := known[authEvent.EventID()]; ok && ev != nil { + if ev, ok := known[authEvent.EventID()]; ok && ev != nil && !ev.Rejected { + // Need to add to the auth set for the next event being processed. + if err := auth.AddEvent(authEvent); err != nil { + return fmt.Errorf("auth.AddEvent: %w", err) + } continue nextAuthEvent } @@ -764,11 +778,11 @@ nextAuthEvent: // In order to store the new auth event, we need to know its auth chain // as NIDs for the `auth_event_nids` column. Let's see if we can find those. - authEventNIDs = authEventNIDs[:0] + authEventNIDs := _authEventNIDs[:0] for _, eventID := range authEvent.AuthEventIDs() { knownEvent, ok := known[eventID] if !ok { - continue nextAuthEvent + return fmt.Errorf("auth event ID %s not known but should be", eventID) } authEventNIDs = append(authEventNIDs, knownEvent.EventNID) } @@ -815,6 +829,7 @@ nextAuthEvent: // Now we know about this event, it was stored and the signatures were OK. known[authEvent.EventID()] = &types.Event{ EventNID: eventNID, + Rejected: isRejected, PDU: authEvent, } } diff --git a/roomserver/internal/input/input_events_test.go b/roomserver/internal/input/input_events_test.go index 05dc842ec..3376a79c5 100644 --- a/roomserver/internal/input/input_events_test.go +++ b/roomserver/internal/input/input_events_test.go @@ -50,7 +50,7 @@ func Test_EventAuth(t *testing.T) { }, test.WithStateKey(bob.ID), test.WithAuthIDs(authEventIDs)) // Add the auth events to the allower - allower := gomatrixserverlib.NewAuthEvents(nil) + allower, _ := gomatrixserverlib.NewAuthEvents(nil) for _, a := range authEvents { if err := allower.AddEvent(a); err != nil { t.Fatalf("allower.AddEvent failed: %v", err) @@ -58,7 +58,7 @@ func Test_EventAuth(t *testing.T) { } // Finally check that the event is NOT allowed - if err := gomatrixserverlib.Allowed(ev.PDU, &allower, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + if err := gomatrixserverlib.Allowed(ev.PDU, allower, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return spec.NewUserID(string(senderID), true) }); err == nil { t.Fatalf("event should not be allowed, but it was") diff --git a/roomserver/internal/input/input_missing.go b/roomserver/internal/input/input_missing.go index fdc37eeec..ea1b50107 100644 --- a/roomserver/internal/input/input_missing.go +++ b/roomserver/internal/input/input_missing.go @@ -961,14 +961,14 @@ serverLoop: } func checkAllowedByState(e gomatrixserverlib.PDU, stateEvents []gomatrixserverlib.PDU, userIDForSender spec.UserIDForSender) error { - authUsingState := gomatrixserverlib.NewAuthEvents(nil) + authUsingState, _ := gomatrixserverlib.NewAuthEvents(nil) for i := range stateEvents { err := authUsingState.AddEvent(stateEvents[i]) if err != nil { return err } } - return gomatrixserverlib.Allowed(e, &authUsingState, userIDForSender) + return gomatrixserverlib.Allowed(e, authUsingState, userIDForSender) } func (t *missingStateReq) hadEvent(eventID string) { diff --git a/roomserver/internal/perform/perform_backfill.go b/roomserver/internal/perform/perform_backfill.go index 8ad255c22..e01b24882 100644 --- a/roomserver/internal/perform/perform_backfill.go +++ b/roomserver/internal/perform/perform_backfill.go @@ -128,9 +128,9 @@ func (r *Backfiller) backfillViaFederation(ctx context.Context, req *api.Perform logrus.WithError(err).WithField("room_id", req.RoomID).Infof("backfilled %d events", len(events)) // persist these new events - auth checks have already been done - roomNID, backfilledEventMap := persistEvents(ctx, r.DB, r.Querier, events) + roomNID, storedEvents := persistEvents(ctx, r.DB, r.Querier, events) - for _, ev := range backfilledEventMap { + for _, ev := range storedEvents { // now add state for these events stateIDs, ok := requester.eventIDToBeforeStateIDs[ev.EventID()] if !ok { @@ -591,10 +591,10 @@ func joinEventsFromHistoryVisibility( return evs, visibility, err } -func persistEvents(ctx context.Context, db storage.Database, querier api.QuerySenderIDAPI, events []gomatrixserverlib.PDU) (types.RoomNID, map[string]types.Event) { +func persistEvents(ctx context.Context, db storage.Database, querier api.QuerySenderIDAPI, events []gomatrixserverlib.PDU) (types.RoomNID, []types.Event) { var roomNID types.RoomNID var eventNID types.EventNID - backfilledEventMap := make(map[string]types.Event) + storedEvents := make([]types.Event, 0, len(events)) for j, ev := range events { nidMap, err := db.EventNIDs(ctx, ev.AuthEventIDs()) if err != nil { // this shouldn't happen as RequestBackfill already found them @@ -647,10 +647,10 @@ func persistEvents(ctx context.Context, db storage.Database, querier api.QuerySe ev = redactedEvent events[j] = ev } - backfilledEventMap[ev.EventID()] = types.Event{ + storedEvents = append(storedEvents, types.Event{ EventNID: eventNID, PDU: ev, - } + }) } - return roomNID, backfilledEventMap + return roomNID, storedEvents } diff --git a/roomserver/internal/perform/perform_create_room.go b/roomserver/internal/perform/perform_create_room.go index d5ddb5cdb..00bdff605 100644 --- a/roomserver/internal/perform/perform_create_room.go +++ b/roomserver/internal/perform/perform_create_room.go @@ -13,12 +13,12 @@ import ( "fmt" "net/http" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/internal/eventutil" "github.com/element-hq/dendrite/roomserver/api" "github.com/element-hq/dendrite/roomserver/storage" "github.com/element-hq/dendrite/roomserver/types" "github.com/element-hq/dendrite/setup/config" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/gomatrixserverlib/spec" @@ -39,6 +39,7 @@ type Creator struct { // PerformCreateRoom handles all the steps necessary to create a new room. // nolint: gocyclo func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) (string, *util.JSONResponse) { + // Make sure we know the room version verImpl, err := gomatrixserverlib.GetRoomVersion(createRequest.RoomVersion) if err != nil { return "", &util.JSONResponse{ @@ -47,17 +48,7 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo } } - createContent := map[string]interface{}{} - if len(createRequest.CreationContent) > 0 { - if err = json.Unmarshal(createRequest.CreationContent, &createContent); err != nil { - util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed") - return "", &util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.BadJSON("invalid create content"), - } - } - } - + // Allocate the room _, err = c.DB.AssignRoomNID(ctx, roomID, createRequest.RoomVersion) if err != nil { util.GetLogger(ctx).WithError(err).Error("failed to assign roomNID") @@ -67,6 +58,7 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo } } + // Allocate the user var senderID spec.SenderID if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs { // create user room key if needed @@ -83,17 +75,73 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo senderID = spec.SenderID(userID.String()) } - // TODO: Maybe, at some point, GMSL should return the events to create, so we can define the version - // entirely there. - switch createRequest.RoomVersion { - case gomatrixserverlib.RoomVersionV11: - // RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175 - default: - createContent["creator"] = senderID + // get the signing identity + identity, err := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping + if err != nil { + logrus.WithError(err).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + + // Make the create event if we need to + var ( + createEvent gomatrixserverlib.PDU + jsonErr *util.JSONResponse + ) + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + if createRequest.CreateEvent != nil { + createEvent, err = verImpl.NewEventFromTrustedJSON(createRequest.CreateEvent, false) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.NewEventFromTrustedJSON failed to verify create event") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + if err = authEvents.AddEvent(createEvent); err != nil { + util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.AuthEvents.AddEvent failed") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } + } else { + var additionalCreators []string + if createRequest.StatePreset == spec.PresetTrustedPrivateChat { + additionalCreators = createRequest.InvitedUsers + } + createContent, contentErr := api.GenerateCreateContent(ctx, createRequest.RoomVersion, string(senderID), createRequest.CreationContent, additionalCreators) + if contentErr != nil { + util.GetLogger(ctx).WithError(contentErr).Error("GenerateCreateContent failed") + return "", &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.BadJSON("invalid create content"), + } + } + createEvent, jsonErr = api.GeneratePDU( + ctx, verImpl, + gomatrixserverlib.FledglingEvent{ + Type: spec.MRoomCreate, + Content: createContent, + }, + authEvents, 1, "", identity, createRequest.EventTime, string(senderID), roomID.String(), c.RSAPI, + ) + if jsonErr != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to make the create event") + return "", jsonErr + } + if err = authEvents.AddEvent(createEvent); err != nil { + util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed") + return "", &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, + } + } } - createContent["room_version"] = createRequest.RoomVersion - powerLevelContent := eventutil.InitialPowerLevelsContent(string(senderID)) + powerLevelContent := eventutil.InitialPowerLevelsContent(verImpl, string(senderID)) joinRuleContent := gomatrixserverlib.JoinRuleContent{ JoinRule: spec.Invite, } @@ -122,8 +170,10 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo case spec.PresetTrustedPrivateChat: joinRuleContent.JoinRule = spec.Invite historyVisibilityContent.HistoryVisibility = historyVisibilityShared - for _, invitee := range createRequest.InvitedUsers { - powerLevelContent.Users[invitee] = 100 + if !verImpl.PrivilegedCreators() { + for _, invitee := range createRequest.InvitedUsers { + powerLevelContent.Users[invitee] = 100 + } } guestsCanJoin = true case spec.PresetPublicChat: @@ -131,10 +181,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo historyVisibilityContent.HistoryVisibility = historyVisibilityShared } - createEvent := gomatrixserverlib.FledglingEvent{ - Type: spec.MRoomCreate, - Content: createContent, - } powerLevelEvent := gomatrixserverlib.FledglingEvent{ Type: spec.MRoomPowerLevels, Content: powerLevelContent, @@ -158,16 +204,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo AvatarURL: createRequest.UserAvatarURL, } - // get the signing identity - identity, err := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping - if err != nil { - logrus.WithError(err).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - // If we are creating a room with pseudo IDs, create and sign the MXIDMapping if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs { var pseudoIDKey ed25519.PrivateKey @@ -279,7 +315,6 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo switch createRequest.InitialState[i].Type { case spec.MRoomCreate: continue - case spec.MRoomPowerLevels: powerLevelEvent = createRequest.InitialState[i] @@ -321,7 +356,8 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo // harder to reason about, hence sticking to a strict static ordering. // TODO: Synapse has txn/token ID on each event. Do we need to do this here? eventsToMake := []gomatrixserverlib.FledglingEvent{ - createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent, + // we made the create event already hence it isn't here. + membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent, } if guestAccessEvent != nil { eventsToMake = append(eventsToMake, *guestAccessEvent) @@ -342,61 +378,19 @@ func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roo // TODO: invite events // TODO: 3pid invite events - var builtEvents []*types.HeaderedEvent - authEvents := gomatrixserverlib.NewAuthEvents(nil) - if err != nil { - util.GetLogger(ctx).WithError(err).Error("rsapi.QuerySenderIDForUser failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } + builtEvents := []*types.HeaderedEvent{ + {PDU: createEvent}, } for i, e := range eventsToMake { - depth := i + 1 // depth starts at 1 - - builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{ - SenderID: string(senderID), - RoomID: roomID.String(), - Type: e.Type, - StateKey: &e.StateKey, - Depth: int64(depth), - }) - err = builder.SetContent(e.Content) - if err != nil { - util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - if i > 0 { - builder.PrevEvents = []string{builtEvents[i-1].EventID()} - } - var ev gomatrixserverlib.PDU - if err = builder.AddAuthEvents(&authEvents); err != nil { - util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - ev, err = builder.Build(createRequest.EventTime, identity.ServerName, identity.KeyID, identity.PrivateKey) - if err != nil { - util.GetLogger(ctx).WithError(err).Error("buildEvent failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - - if err = gomatrixserverlib.Allowed(ev, &authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { - return c.RSAPI.QueryUserIDForSender(ctx, roomID, senderID) - }); err != nil { - util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed") - return "", &util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } + depth := i + 2 // depth starts at 2 since we made the create event already + + ev, jsonErr := api.GeneratePDU( + ctx, verImpl, e, + authEvents, depth, builtEvents[len(builtEvents)-1].EventID(), + identity, createRequest.EventTime, string(senderID), roomID.String(), c.RSAPI, + ) + if jsonErr != nil { + return "", jsonErr } // Add the event to the list of auth events diff --git a/roomserver/internal/perform/perform_invite.go b/roomserver/internal/perform/perform_invite.go index f609e6ae5..615951bfb 100644 --- a/roomserver/internal/perform/perform_invite.go +++ b/roomserver/internal/perform/perform_invite.go @@ -31,7 +31,11 @@ type QueryState struct { } func (q *QueryState) GetAuthEvents(ctx context.Context, event gomatrixserverlib.PDU) (gomatrixserverlib.AuthEventProvider, error) { - return helpers.GetAuthEvents(ctx, q.Database, event.Version(), event, event.AuthEventIDs()) + authEventIDs := event.AuthEventIDs() + if gomatrixserverlib.MustGetRoomVersion(event.Version()).DomainlessRoomIDs() { + authEventIDs = append(authEventIDs, "$"+event.RoomID().String()[1:]) + } + return helpers.GetAuthEvents(ctx, q.Database, event.Version(), event, authEventIDs) } func (q *QueryState) GetState(ctx context.Context, roomID spec.RoomID, stateWanted []gomatrixserverlib.StateKeyTuple) ([]gomatrixserverlib.PDU, error) { diff --git a/roomserver/internal/perform/perform_join.go b/roomserver/internal/perform/perform_join.go index c58d48ff4..a1b54e3f8 100644 --- a/roomserver/internal/perform/perform_join.go +++ b/roomserver/internal/perform/perform_join.go @@ -156,19 +156,11 @@ func (r *Joiner) performJoinRoomByID( } } - // Get the domain part of the room ID. roomID, err := spec.NewRoomID(req.RoomIDOrAlias) if err != nil { return "", "", rsAPI.ErrInvalidID{Err: fmt.Errorf("room ID %q is invalid: %w", req.RoomIDOrAlias, err)} } - // If the server name in the room ID isn't ours then it's a - // possible candidate for finding the room via federation. Add - // it to the list of servers to try. - if !r.Cfg.Matrix.IsLocalServerName(roomID.Domain()) { - req.ServerNames = append(req.ServerNames, roomID.Domain()) - } - // Force a federated join if we aren't in the room and we've been // given some server names to try joining by. inRoomReq := &rsAPI.QueryServerJoinedToRoomRequest{ diff --git a/roomserver/internal/perform/perform_upgrade.go b/roomserver/internal/perform/perform_upgrade.go index cf18924ea..8fbed0205 100644 --- a/roomserver/internal/perform/perform_upgrade.go +++ b/roomserver/internal/perform/perform_upgrade.go @@ -10,6 +10,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "time" "github.com/element-hq/dendrite/internal/eventutil" @@ -30,14 +31,15 @@ type Upgrader struct { // PerformRoomUpgrade upgrades a room from one version to another func (r *Upgrader) PerformRoomUpgrade( ctx context.Context, - roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, + roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string, ) (newRoomID string, err error) { - return r.performRoomUpgrade(ctx, roomID, userID, roomVersion) + return r.performRoomUpgrade(ctx, roomID, userID, roomVersion, additionalCreators) } +// nolint:gocyclo func (r *Upgrader) performRoomUpgrade( ctx context.Context, - roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, + roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion, additionalCreators []string, ) (string, error) { evTime := time.Now() @@ -64,35 +66,110 @@ func (r *Upgrader) performRoomUpgrade( return "", api.ErrNotAllowed{Err: fmt.Errorf("You don't have permission to upgrade the room, power level too low.")} } - // TODO (#267): Check room ID doesn't clash with an existing one, and we - // probably shouldn't be using pseudo-random strings, maybe GUIDs? - newRoomID := fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()) - // Get the existing room state for the old room. oldRoomReq := &api.QueryLatestEventsAndStateRequest{ RoomID: roomID, } oldRoomRes := &api.QueryLatestEventsAndStateResponse{} - if err := r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil { + if err = r.URSAPI.QueryLatestEventsAndState(ctx, oldRoomReq, oldRoomRes); err != nil { return "", fmt.Errorf("Failed to get latest state: %s", err) } + var oldCreateEvent *types.HeaderedEvent + for _, ev := range oldRoomRes.StateEvents { + if ev.Type() == spec.MRoomCreate && ev.StateKeyEquals("") { + oldCreateEvent = ev + break + } + } - // Make the tombstone event - tombstoneEvent, pErr := r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID) - if pErr != nil { - return "", pErr + // Make the create event and calculate the new room ID. + var newRoomID string + newRoomVerImpl := gomatrixserverlib.MustGetRoomVersion(roomVersion) + var tombstoneEvent *types.HeaderedEvent + var newCreateEvent gomatrixserverlib.PDU + var pErr error + if !newRoomVerImpl.DomainlessRoomIDs() { + // TODO (#267): Check room ID doesn't clash with an existing one, and we + // probably shouldn't be using pseudo-random strings, maybe GUIDs? + newRoomID = fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()) + + // Make the tombstone event + tombstoneEvent, pErr = r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID) + if pErr != nil { + return "", pErr + } + } + content := struct { + Federate *bool `json:"m.federate,omitempty"` + Type string `json:"type,omitempty"` + Predecessor struct { + RoomID string `json:"room_id"` + EventID string `json:"event_id,omitempty"` + } `json:"predecessor"` + }{} + // keep existing values in old room e.g type/m.federate + if err = json.Unmarshal(oldCreateEvent.Content(), &content); err != nil { + return "", fmt.Errorf("failed to copy old create event content to new create event: %s", err) + } + content.Predecessor.RoomID = roomID + content.Predecessor.EventID = "" + if tombstoneEvent != nil { + content.Predecessor.EventID = tombstoneEvent.EventID() + } + contentJSON, err := json.Marshal(content) + if err != nil { + return "", fmt.Errorf("Failed to make content for new create event: %s", err) + } + // make the create event up-front so the roomserver can calculate the room NID to store. + createContent, err := api.GenerateCreateContent(ctx, roomVersion, userID.String(), contentJSON, additionalCreators) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("GenerateCreateContent failed") + return "", fmt.Errorf("failed to GenerateCreateContent") + } + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) + identity, err := r.Cfg.Matrix.SigningIdentityFor(userID.Domain()) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("Failed to get signing identity") + return "", fmt.Errorf("No SigningIdentityFor domain %s", userID.Domain()) + } + createEvent, jsonErr := api.GeneratePDU( + ctx, gomatrixserverlib.MustGetRoomVersion(roomVersion), + gomatrixserverlib.FledglingEvent{ + Type: spec.MRoomCreate, + Content: createContent, + }, + // newRoomID will be empty for domainless rooms + authEvents, 1, "", identity, evTime, userID.String(), newRoomID, r.URSAPI, + ) + if jsonErr != nil { + util.GetLogger(ctx).Error("Failed to make the create event") + return "", fmt.Errorf("failed to create new create event PDU") + } + newCreateEvent = createEvent + if newRoomVerImpl.DomainlessRoomIDs() { + newRoomID = newCreateEvent.RoomID().String() } + if tombstoneEvent == nil { + // Make the tombstone event + tombstoneEvent, pErr = r.makeTombstoneEvent(ctx, evTime, *senderID, userID.Domain(), roomID, newRoomID) + if pErr != nil { + return "", pErr + } + } + + creators := gomatrixserverlib.CreatorsFromCreateEvent(newCreateEvent) + // Generate the initial events we need to send into the new room. This includes copied state events and bans // as well as the power level events needed to set up the room - eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, *senderID, roomID, roomVersion, tombstoneEvent) + eventsToMake, pErr := r.generateInitialEvents(ctx, oldRoomRes, *senderID, roomID, roomVersion, creators) if pErr != nil { return "", pErr } // Send the setup events to the new room - if pErr = r.sendInitialEvents(ctx, evTime, *senderID, userID.Domain(), newRoomID, roomVersion, eventsToMake); pErr != nil { - return "", pErr + if pErr = r.sendInitialEvents(ctx, evTime, *senderID, userID.Domain(), newRoomID, roomVersion, newCreateEvent, eventsToMake); pErr != nil { + return "", fmt.Errorf("sendInitialEvents: %s", pErr) } // 5. Send the tombstone event to the old room @@ -296,13 +373,25 @@ func (r *Upgrader) userIsAuthorized(ctx context.Context, senderID spec.SenderID, if err != nil { return false } + createEvent := api.GetStateEvent(ctx, r.URSAPI, roomID, gomatrixserverlib.StateKeyTuple{ + EventType: spec.MRoomCreate, + StateKey: "", + }) + if gomatrixserverlib.MustGetRoomVersion(createEvent.Version()).PrivilegedCreators() && + slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) { + return true + } // Check for power level required to send tombstone event (marks the current room as obsolete), // if not found, use the StateDefault power level return pl.UserLevel(senderID) >= pl.EventLevel("m.room.tombstone", true) } +// Return the events to create AFTER the new create event // nolint:gocyclo -func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, senderID spec.SenderID, roomID string, newVersion gomatrixserverlib.RoomVersion, tombstoneEvent *types.HeaderedEvent) ([]gomatrixserverlib.FledglingEvent, error) { +func (r *Upgrader) generateInitialEvents( + ctx context.Context, oldRoom *api.QueryLatestEventsAndStateResponse, senderID spec.SenderID, _ string, newVersion gomatrixserverlib.RoomVersion, + creators []string) ([]gomatrixserverlib.FledglingEvent, error) { + state := make(map[gomatrixserverlib.StateKeyTuple]*types.HeaderedEvent, len(oldRoom.StateEvents)) for _, event := range oldRoom.StateEvents { if event.StateKey() == nil { @@ -350,37 +439,10 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query } } - oldCreateEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomCreate, StateKey: ""}] oldMembershipEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomMember, StateKey: string(senderID)}] oldPowerLevelsEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomPowerLevels, StateKey: ""}] oldJoinRulesEvent := state[gomatrixserverlib.StateKeyTuple{EventType: spec.MRoomJoinRules, StateKey: ""}] - // Create the new room create event. Using a map here instead of CreateContent - // means that we preserve any other interesting fields that might be present - // in the create event (such as for the room types MSC). - newCreateContent := map[string]interface{}{} - _ = json.Unmarshal(oldCreateEvent.Content(), &newCreateContent) - - switch newVersion { - case gomatrixserverlib.RoomVersionV11: - // RoomVersionV11 removed the creator field from the create content: https://github.com/matrix-org/matrix-spec-proposals/pull/2175 - // So if we are upgrading from pre v11, we need to remove the field. - delete(newCreateContent, "creator") - default: - newCreateContent["creator"] = senderID - } - - newCreateContent["room_version"] = newVersion - newCreateContent["predecessor"] = gomatrixserverlib.PreviousRoom{ - EventID: tombstoneEvent.EventID(), - RoomID: roomID, - } - newCreateEvent := gomatrixserverlib.FledglingEvent{ - Type: spec.MRoomCreate, - StateKey: "", - Content: newCreateContent, - } - // Now create the new membership event. Same rules apply as above, so // that we preserve fields we don't otherwise know about. We'll always // set the membership to join though, because that is necessary to auth @@ -405,7 +467,9 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query return nil, fmt.Errorf("Power level event content was invalid") } - tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(powerLevelContent, senderID) + verImpl := gomatrixserverlib.MustGetRoomVersion(newVersion) + + tempPowerLevelsEvent, powerLevelsOverridden := createTemporaryPowerLevels(verImpl, powerLevelContent, senderID, creators) // Now do the join rules event, same as the create and membership // events. We'll set a sane default of "invite" so that if the @@ -423,7 +487,7 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query eventsToMake := make([]gomatrixserverlib.FledglingEvent, 0, len(state)) eventsToMake = append( - eventsToMake, newCreateEvent, newMembershipEvent, + eventsToMake, newMembershipEvent, tempPowerLevelsEvent, newJoinRulesEvent, ) @@ -467,12 +531,16 @@ func (r *Upgrader) generateInitialEvents(ctx context.Context, oldRoom *api.Query return eventsToMake, nil } -func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, senderID spec.SenderID, userDomain spec.ServerName, newRoomID string, newVersion gomatrixserverlib.RoomVersion, eventsToMake []gomatrixserverlib.FledglingEvent) error { +func (r *Upgrader) sendInitialEvents( + ctx context.Context, evTime time.Time, senderID spec.SenderID, userDomain spec.ServerName, newRoomID string, + newVersion gomatrixserverlib.RoomVersion, newCreateEvent gomatrixserverlib.PDU, eventsToMake []gomatrixserverlib.FledglingEvent) error { + var err error var builtEvents []*types.HeaderedEvent - authEvents := gomatrixserverlib.NewAuthEvents(nil) + builtEvents = append(builtEvents, &types.HeaderedEvent{PDU: newCreateEvent}) + authEvents, _ := gomatrixserverlib.NewAuthEvents([]gomatrixserverlib.PDU{newCreateEvent}) for i, e := range eventsToMake { - depth := i + 1 // depth starts at 1 + depth := i + 2 // depth starts at 2 since we made the create event already. proto := gomatrixserverlib.ProtoEvent{ SenderID: string(senderID), @@ -485,9 +553,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send if err != nil { return fmt.Errorf("failed to set content of new %q event: %w", proto.Type, err) } - if i > 0 { - proto.PrevEvents = []string{builtEvents[i-1].EventID()} - } + proto.PrevEvents = []string{builtEvents[i].EventID()} var verImpl gomatrixserverlib.IRoomVersion verImpl, err = gomatrixserverlib.GetRoomVersion(newVersion) @@ -495,7 +561,7 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send return err } builder := verImpl.NewEventBuilderFromProtoEvent(&proto) - if err = builder.AddAuthEvents(&authEvents); err != nil { + if err = builder.AddAuthEvents(authEvents); err != nil { return err } @@ -503,13 +569,12 @@ func (r *Upgrader) sendInitialEvents(ctx context.Context, evTime time.Time, send event, err = builder.Build(evTime, userDomain, r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey) if err != nil { return fmt.Errorf("failed to build new %q event: %w", builder.Type, err) - } - if err = gomatrixserverlib.Allowed(event, &authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + if err = gomatrixserverlib.Allowed(event, authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return r.URSAPI.QueryUserIDForSender(ctx, roomID, senderID) }); err != nil { - return fmt.Errorf("Failed to auth new %q event: %w", builder.Type, err) + return fmt.Errorf("Failed to auth new initial %q event: %w", builder.Type, err) } // Add the event to the list of auth events @@ -586,8 +651,11 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, send for i := range queryRes.StateEvents { stateEvents[i] = queryRes.StateEvents[i].PDU } - provider := gomatrixserverlib.NewAuthEvents(stateEvents) - if err = gomatrixserverlib.Allowed(headeredEvent.PDU, &provider, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { + provider, err := gomatrixserverlib.NewAuthEvents(stateEvents) + if err != nil { + return nil, err + } + if err = gomatrixserverlib.Allowed(headeredEvent.PDU, provider, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) { return r.URSAPI.QueryUserIDForSender(ctx, roomID, senderID) }); err != nil { return nil, api.ErrNotAllowed{Err: fmt.Errorf("failed to auth new %q event: %w", proto.Type, err)} // TODO: Is this error string comprehensible to the client? @@ -596,7 +664,7 @@ func (r *Upgrader) makeHeaderedEvent(ctx context.Context, evTime time.Time, send return headeredEvent, nil } -func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelContent, senderID spec.SenderID) (gomatrixserverlib.FledglingEvent, bool) { +func createTemporaryPowerLevels(roomVersion gomatrixserverlib.IRoomVersion, powerLevelContent *gomatrixserverlib.PowerLevelContent, senderID spec.SenderID, creators []string) (gomatrixserverlib.FledglingEvent, bool) { // Work out what power level we need in order to be able to send events // of all types into the room. neededPowerLevel := powerLevelContent.StateDefault @@ -616,14 +684,20 @@ func createTemporaryPowerLevels(powerLevelContent *gomatrixserverlib.PowerLevelC // so that we can modify them without modifying the original. tempPowerLevelContent.Users = make(map[string]int64, len(powerLevelContent.Users)) for key, value := range powerLevelContent.Users { + if roomVersion.PrivilegedCreators() && slices.Contains(creators, key) { + continue // don't set the creator in the users map! + } tempPowerLevelContent.Users[key] = value } - // If the user who is upgrading the room doesn't already have sufficient - // power, then elevate their power levels. - if tempPowerLevelContent.UserLevel(senderID) < neededPowerLevel { - tempPowerLevelContent.Users[string(senderID)] = neededPowerLevel - powerLevelsOverridden = true + // the upgrader will be the creator so is guaranteed to have enough perms to do this. + if !roomVersion.PrivilegedCreators() { + // If the user who is upgrading the room doesn't already have sufficient + // power, then elevate their power levels. + if tempPowerLevelContent.UserLevel(senderID) < neededPowerLevel { + tempPowerLevelContent.Users[string(senderID)] = neededPowerLevel + powerLevelsOverridden = true + } } // Then return the temporary power levels event. diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index e5ee8f832..37de303b0 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -13,7 +13,7 @@ import ( "errors" "fmt" - //"github.com/element-hq/dendrite/roomserver/internal" + // "github.com/element-hq/dendrite/roomserver/internal" "github.com/element-hq/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" @@ -747,7 +747,7 @@ func GetAuthChain( // from the database and the `eventsToFetch` will be updated with any new // events that we have learned about and need to find. When `eventsToFetch` // is eventually empty, we should have reached the end of the chain. - eventsToFetch := authEventIDs + eventsToFetch := append([]string{}, authEventIDs...) authEventsMap := make(map[string]gomatrixserverlib.PDU) for len(eventsToFetch) > 0 { @@ -779,7 +779,7 @@ func GetAuthChain( // We've now retrieved all of the events we can. Flatten them down into an // array and return them. - var authEvents []gomatrixserverlib.PDU + authEvents := make([]gomatrixserverlib.PDU, 0, len(authEventsMap)) for _, event := range authEventsMap { authEvents = append(authEvents, event) } diff --git a/roomserver/roomserver_test.go b/roomserver/roomserver_test.go index 48911d2bb..659ad7141 100644 --- a/roomserver/roomserver_test.go +++ b/roomserver/roomserver_test.go @@ -1075,7 +1075,7 @@ func TestUpgrade(t *testing.T) { if err != nil { t.Fatalf("upgrade userID is invalid") } - newRoomID, err := rsAPI.PerformRoomUpgrade(processCtx.Context(), roomID, *userID, rsAPI.DefaultRoomVersion()) + newRoomID, err := rsAPI.PerformRoomUpgrade(processCtx.Context(), roomID, *userID, rsAPI.DefaultRoomVersion(), nil) if err != nil && tc.wantNewRoom { t.Fatal(err) } diff --git a/roomserver/state/state.go b/roomserver/state/state.go index 9a7669a44..a5c84d1f3 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -888,6 +888,8 @@ func (v *StateResolution) resolveConflicts( case gomatrixserverlib.StateResV1: return v.resolveConflictsV1(ctx, notConflicted, conflicted) case gomatrixserverlib.StateResV2: + fallthrough + case gomatrixserverlib.StateResV2_1: return v.resolveConflictsV2(ctx, notConflicted, conflicted) } return nil, fmt.Errorf("unsupported state resolution algorithm %v", stateResAlgo) diff --git a/roomserver/storage/interface.go b/roomserver/storage/interface.go index 49086dbaf..3bdeeef8b 100644 --- a/roomserver/storage/interface.go +++ b/roomserver/storage/interface.go @@ -187,6 +187,8 @@ type Database interface { // RoomsWithACLs returns all room IDs for rooms with ACLs RoomsWithACLs(ctx context.Context) ([]string, error) + // GetBulkStateACLs returns all server ACLs for the given rooms. + GetBulkStateACLs(ctx context.Context, roomIDs []string) ([]tables.StrippedEvent, error) QueryAdminEventReports(ctx context.Context, from uint64, limit uint64, backwards bool, userID string, roomID string) ([]api.QueryAdminEventReportsResponse, int64, error) QueryAdminEventReport(ctx context.Context, reportID uint64) (api.QueryAdminEventReportResponse, error) AdminDeleteEventReport(ctx context.Context, reportID uint64) error diff --git a/roomserver/storage/postgres/deltas/2021041615092700_state_blocks_refactor.go b/roomserver/storage/postgres/deltas/2021041615092700_state_blocks_refactor.go index c6bb1863d..3c0a3fa00 100644 --- a/roomserver/storage/postgres/deltas/2021041615092700_state_blocks_refactor.go +++ b/roomserver/storage/postgres/deltas/2021041615092700_state_blocks_refactor.go @@ -11,8 +11,8 @@ import ( "database/sql" "fmt" - "github.com/lib/pq" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" "github.com/matrix-org/util" "github.com/sirupsen/logrus" ) diff --git a/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha.go b/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha.go index 045d7ab65..00cc4c66c 100644 --- a/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha.go +++ b/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha.go @@ -11,8 +11,8 @@ import ( "database/sql" "fmt" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" + "github.com/lib/pq" "github.com/matrix-org/util" ) diff --git a/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha_test.go b/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha_test.go index 7a5a029ab..9ff1003b1 100644 --- a/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha_test.go +++ b/roomserver/storage/postgres/deltas/20230516154000_drop_reference_sha_test.go @@ -3,10 +3,10 @@ package deltas import ( "testing" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/test" "github.com/element-hq/dendrite/test/testrig" + "github.com/lib/pq" "github.com/stretchr/testify/assert" ) diff --git a/roomserver/storage/postgres/event_state_keys_table.go b/roomserver/storage/postgres/event_state_keys_table.go index 624ccd793..01cfdbfd4 100644 --- a/roomserver/storage/postgres/event_state_keys_table.go +++ b/roomserver/storage/postgres/event_state_keys_table.go @@ -11,11 +11,11 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/storage/tables" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" ) const eventStateKeysSchema = ` diff --git a/roomserver/storage/postgres/event_types_table.go b/roomserver/storage/postgres/event_types_table.go index 32d2d4b1a..cd406c156 100644 --- a/roomserver/storage/postgres/event_types_table.go +++ b/roomserver/storage/postgres/event_types_table.go @@ -11,11 +11,11 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/storage/tables" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" ) const eventTypesSchema = ` diff --git a/roomserver/storage/postgres/events_table.go b/roomserver/storage/postgres/events_table.go index 70eefbe7b..03215f191 100644 --- a/roomserver/storage/postgres/events_table.go +++ b/roomserver/storage/postgres/events_table.go @@ -13,12 +13,12 @@ import ( "fmt" "sort" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/storage/postgres/deltas" "github.com/element-hq/dendrite/roomserver/storage/tables" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" ) const eventsSchema = ` diff --git a/roomserver/storage/postgres/rooms_table.go b/roomserver/storage/postgres/rooms_table.go index f8a6efb23..4e040b9a7 100644 --- a/roomserver/storage/postgres/rooms_table.go +++ b/roomserver/storage/postgres/rooms_table.go @@ -11,11 +11,11 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/storage/tables" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" ) diff --git a/roomserver/storage/postgres/state_block_table.go b/roomserver/storage/postgres/state_block_table.go index e77d6e51d..27b728111 100644 --- a/roomserver/storage/postgres/state_block_table.go +++ b/roomserver/storage/postgres/state_block_table.go @@ -12,11 +12,11 @@ import ( "database/sql" "fmt" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/storage/tables" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" "github.com/matrix-org/util" ) diff --git a/roomserver/storage/postgres/user_room_keys_table.go b/roomserver/storage/postgres/user_room_keys_table.go index 7ddbde760..29f6f856b 100644 --- a/roomserver/storage/postgres/user_room_keys_table.go +++ b/roomserver/storage/postgres/user_room_keys_table.go @@ -12,11 +12,11 @@ import ( "database/sql" "errors" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/storage/tables" "github.com/element-hq/dendrite/roomserver/types" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/roomserver/storage/shared/storage.go b/roomserver/storage/shared/storage.go index a4eb0eb9b..31c16c34b 100644 --- a/roomserver/storage/shared/storage.go +++ b/roomserver/storage/shared/storage.go @@ -1052,6 +1052,7 @@ func (d *EventDatabase) MaybeRedactEvent( return err } + // TODO HYDRA: we need to load the create event here switch { case powerlevels.UserLevel(redactionEvent.SenderID()) >= powerlevels.Redact: // 1. The power level of the redaction event’s sender is greater than or equal to the redact level. @@ -1437,6 +1438,63 @@ func (d *Database) GetRoomsByMembership(ctx context.Context, userID spec.UserID, return roomIDs, nil } +// GetBulkStateACLs is a lighter weight form of GetBulkStateContent, which only returns ACL state events. +func (d *Database) GetBulkStateACLs(ctx context.Context, roomIDs []string) ([]tables.StrippedEvent, error) { + tuples := []gomatrixserverlib.StateKeyTuple{{EventType: "m.room.server_acl", StateKey: ""}} + + var eventNIDs []types.EventNID + eventNIDToVer := make(map[types.EventNID]gomatrixserverlib.RoomVersion) + // TODO: This feels like this is going to be really slow... + for _, roomID := range roomIDs { + roomInfo, err2 := d.roomInfo(ctx, nil, roomID) + if err2 != nil { + return nil, fmt.Errorf("GetBulkStateACLs: failed to load room info for room %s : %w", roomID, err2) + } + // for unknown rooms or rooms which we don't have the current state, skip them. + if roomInfo == nil || roomInfo.IsStub() { + continue + } + // No querier needed, as we don't actually do state resolution + stateRes := state.NewStateResolution(d, roomInfo, nil) + entries, err2 := stateRes.LoadStateAtSnapshotForStringTuples(ctx, roomInfo.StateSnapshotNID(), tuples) + if err2 != nil { + return nil, fmt.Errorf("GetBulkStateACLs: failed to load state for room %s : %w", roomID, err2) + } + for _, entry := range entries { + eventNIDs = append(eventNIDs, entry.EventNID) + eventNIDToVer[entry.EventNID] = roomInfo.RoomVersion + } + } + eventIDs, err := d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs) + if err != nil { + eventIDs = map[types.EventNID]string{} + } + events, err := d.EventJSONTable.BulkSelectEventJSON(ctx, nil, eventNIDs) + if err != nil { + return nil, fmt.Errorf("GetBulkStateACLs: failed to load event JSON for event nids: %w", err) + } + result := make([]tables.StrippedEvent, len(events)) + for i := range events { + roomVer := eventNIDToVer[events[i].EventNID] + verImpl, err := gomatrixserverlib.GetRoomVersion(roomVer) + if err != nil { + return nil, err + } + ev, err := verImpl.NewEventFromTrustedJSONWithEventID(eventIDs[events[i].EventNID], events[i].EventJSON, false) + if err != nil { + return nil, fmt.Errorf("GetBulkStateACLs: failed to load event JSON for event NID %v : %w", events[i].EventNID, err) + } + result[i] = tables.StrippedEvent{ + EventType: ev.Type(), + RoomID: ev.RoomID().String(), + StateKey: *ev.StateKey(), + ContentValue: tables.ExtractContentValue(&types.HeaderedEvent{PDU: ev}), + } + } + + return result, nil +} + // GetBulkStateContent returns all state events which match a given room ID and a given state key tuple. Both must be satisfied for a match. // If a tuple has the StateKey of '*' and allowWildcards=true then all state events with the EventType should be returned. func (d *Database) GetBulkStateContent(ctx context.Context, roomIDs []string, tuples []gomatrixserverlib.StateKeyTuple, allowWildcards bool) ([]tables.StrippedEvent, error) { @@ -1487,6 +1545,9 @@ func (d *Database) GetBulkStateContent(ctx context.Context, roomIDs []string, tu if roomInfo == nil || roomInfo.IsStub() { continue } + // TODO: This is inefficient as we're loading the _entire_ state, but only care about a subset of it. + // This is why GetBulkStateACLs exists. LoadStateAtSnapshotForStringTuples only loads the state we care about, + // but is unfortunately not able to load wildcard state keys. entries, err2 := d.loadStateAtSnapshot(ctx, roomInfo.StateSnapshotNID()) if err2 != nil { return nil, fmt.Errorf("GetBulkStateContent: failed to load state for room %s : %w", roomID, err2) diff --git a/roomserver/storage/sqlite3/deltas/20230516154000_drop_reference_sha.go b/roomserver/storage/sqlite3/deltas/20230516154000_drop_reference_sha.go index da37a7fef..da9f1c221 100644 --- a/roomserver/storage/sqlite3/deltas/20230516154000_drop_reference_sha.go +++ b/roomserver/storage/sqlite3/deltas/20230516154000_drop_reference_sha.go @@ -11,8 +11,8 @@ import ( "database/sql" "fmt" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" + "github.com/lib/pq" "github.com/matrix-org/util" ) diff --git a/roomserver/types/types.go b/roomserver/types/types.go index 759066bc0..b0c393c3f 100644 --- a/roomserver/types/types.go +++ b/roomserver/types/types.go @@ -228,6 +228,7 @@ func (s StateAtEventAndReferences) EventIDs() string { // It is when performing bulk event lookup in the database. type Event struct { EventNID EventNID + Rejected bool gomatrixserverlib.PDU } diff --git a/setup/base/base.go b/setup/base/base.go index 9b8f1f92f..ffc2be376 100644 --- a/setup/base/base.go +++ b/setup/base/base.go @@ -28,10 +28,10 @@ import ( "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/gorilla/mux" - "github.com/kardianos/minwinsvc" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/httputil" + "github.com/gorilla/mux" + "github.com/kardianos/minwinsvc" "github.com/sirupsen/logrus" @@ -82,6 +82,7 @@ func CreateFederationClient(cfg *config.Dendrite, dnsCache *fclient.DNSCache) fc fclient.WithSkipVerify(cfg.FederationAPI.DisableTLSValidation), fclient.WithKeepAlives(!cfg.FederationAPI.DisableHTTPKeepalives), fclient.WithUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString())), + fclient.WithAllowDenyNetworks(cfg.FederationAPI.AllowNetworkCIDRs, cfg.FederationAPI.DenyNetworkCIDRs), } if cfg.Global.DNSCache.Enabled { opts = append(opts, fclient.WithDNSCache(dnsCache)) diff --git a/setup/config/config_federationapi.go b/setup/config/config_federationapi.go index 073c46e03..ed417a743 100644 --- a/setup/config/config_federationapi.go +++ b/setup/config/config_federationapi.go @@ -46,6 +46,10 @@ type FederationAPI struct { // Should we prefer direct key fetches over perspective ones? PreferDirectFetch bool `yaml:"prefer_direct_fetch"` + + // Deny/Allow lists used for restricting request scopes. + DenyNetworkCIDRs []string `yaml:"deny_networks"` + AllowNetworkCIDRs []string `yaml:"allow_networks"` } func (c *FederationAPI) Defaults(opts DefaultOpts) { @@ -53,6 +57,20 @@ func (c *FederationAPI) Defaults(opts DefaultOpts) { c.P2PFederationRetriesUntilAssumedOffline = 1 c.DisableTLSValidation = false c.DisableHTTPKeepalives = false + c.DenyNetworkCIDRs = []string{ + "127.0.0.1/8", + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "100.64.0.0/10", + "169.254.0.0/16", + "::1/128", + "fe80::/64", + "fc00::/7", + } + c.AllowNetworkCIDRs = []string{ + "0.0.0.0/0", + } if opts.Generate { c.KeyPerspectives = KeyPerspectives{ { diff --git a/setup/config/config_jetstream.go b/setup/config/config_jetstream.go index a048e4d09..c37f917cc 100644 --- a/setup/config/config_jetstream.go +++ b/setup/config/config_jetstream.go @@ -15,6 +15,8 @@ type JetStream struct { // The prefix to use for stream names for this homeserver - really only // useful if running more than one Dendrite on the same NATS deployment. TopicPrefix string `yaml:"topic_prefix"` + // The JetStream domain, if needed. + JetStreamDomain string `yaml:"js_domain"` // Keep all storage in memory. This is mostly useful for unit tests. InMemory bool `yaml:"in_memory"` // Disable logging. This is mostly useful for unit tests. diff --git a/setup/jetstream/helpers.go b/setup/jetstream/helpers.go index 533652160..672f3e6ac 100644 --- a/setup/jetstream/helpers.go +++ b/setup/jetstream/helpers.go @@ -22,7 +22,7 @@ func JetStreamConsumer( f func(ctx context.Context, msgs []*nats.Msg) bool, opts ...nats.SubOpt, ) error { - defer func() { + defer func(durable string) { // If there are existing consumers from before they were pull // consumers, we need to clean up the old push consumers. However, // in order to not affect the interest-based policies, we need to @@ -33,86 +33,93 @@ func JetStreamConsumer( logrus.WithContext(ctx).Warnf("Failed to clean up old consumer %q", durable) } } - }() + }(durable) - name := durable + "Pull" - sub, err := js.PullSubscribe(subj, name, opts...) + durable = durable + "Pull" + sub, err := js.PullSubscribe(subj, durable, opts...) if err != nil { sentry.CaptureException(err) - return fmt.Errorf("nats.SubscribeSync: %w", err) + logrus.WithContext(ctx).WithError(err).Warnf("Failed to configure durable %q", durable) + return err } - go func() { - for { - // If the parent context has given up then there's no point in - // carrying on doing anything, so stop the listener. - select { - case <-ctx.Done(): - if err := sub.Unsubscribe(); err != nil { - logrus.WithContext(ctx).Warnf("Failed to unsubscribe %q", durable) - } - return - default: - } - // The context behaviour here is surprising — we supply a context - // so that we can interrupt the fetch if we want, but NATS will still - // enforce its own deadline (roughly 5 seconds by default). Therefore - // it is our responsibility to check whether our context expired or - // not when a context error is returned. Footguns. Footguns everywhere. - msgs, err := sub.Fetch(batch, nats.Context(ctx)) - if err != nil { - if err == context.Canceled || err == context.DeadlineExceeded { - // Work out whether it was the JetStream context that expired - // or whether it was our supplied context. - select { - case <-ctx.Done(): - // The supplied context expired, so we want to stop the - // consumer altogether. - return - default: - // The JetStream context expired, so the fetch probably - // just timed out and we should try again. - continue - } - } else if errors.Is(err, nats.ErrConsumerDeleted) { - // The consumer was deleted so stop. + go jetStreamConsumerWorker(ctx, sub, subj, batch, f) + return nil +} + +func jetStreamConsumerWorker( + ctx context.Context, sub *nats.Subscription, subj string, batch int, + f func(ctx context.Context, msgs []*nats.Msg) bool, +) { + for { + // If the parent context has given up then there's no point in + // carrying on doing anything, so stop the listener. + select { + case <-ctx.Done(): + return + default: + } + // The context behaviour here is surprising — we supply a context + // so that we can interrupt the fetch if we want, but NATS will still + // enforce its own deadline (roughly 5 seconds by default). Therefore + // it is our responsibility to check whether our context expired or + // not when a context error is returned. Footguns. Footguns everywhere. + msgs, err := sub.Fetch(batch, nats.Context(ctx)) + if err != nil { + if err == context.Canceled || err == context.DeadlineExceeded { + // Work out whether it was the JetStream context that expired + // or whether it was our supplied context. + select { + case <-ctx.Done(): + // The supplied context expired, so we want to stop the + // consumer altogether. return - } else { - // Unfortunately, there's no ErrServerShutdown or similar, so we need to compare the string - if err.Error() == "nats: Server Shutdown" { - logrus.WithContext(ctx).Warn("nats server shutting down") - return - } - // Something else went wrong, so we'll panic. - sentry.CaptureException(err) - logrus.WithContext(ctx).WithField("subject", subj).Fatal(err) + default: + // The JetStream context expired, so the fetch probably + // just timed out and we should try again. + continue } + } else if errors.Is(err, nats.ErrTimeout) { + // Pull request was invalidated, try again. + continue + } else if errors.Is(err, nats.ErrConsumerLeadershipChanged) { + // Leadership changed so pending pull requests became invalidated, + // just try again. + continue + } else if err.Error() == "nats: Server Shutdown" { + // The server is shutting down, but we'll rely on reconnect + // behaviour to try and either connect us to another node (if + // clustered) or to reconnect when the server comes back up. + continue + } else { + // Something else went wrong. + logrus.WithContext(ctx).WithField("subject", subj).WithError(err).Warn("Error on pull subscriber fetch") + return } - if len(msgs) < 1 { + } + if len(msgs) < 1 { + continue + } + for _, msg := range msgs { + if err = msg.InProgress(nats.Context(ctx)); err != nil { + logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.InProgress: %w", err)) + sentry.CaptureException(err) continue } + } + if f(ctx, msgs) { for _, msg := range msgs { - if err = msg.InProgress(nats.Context(ctx)); err != nil { - logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.InProgress: %w", err)) + if err = msg.AckSync(nats.Context(ctx)); err != nil { + logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.AckSync: %w", err)) sentry.CaptureException(err) - continue } } - if f(ctx, msgs) { - for _, msg := range msgs { - if err = msg.AckSync(nats.Context(ctx)); err != nil { - logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.AckSync: %w", err)) - sentry.CaptureException(err) - } - } - } else { - for _, msg := range msgs { - if err = msg.Nak(nats.Context(ctx)); err != nil { - logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.Nak: %w", err)) - sentry.CaptureException(err) - } + } else { + for _, msg := range msgs { + if err = msg.Nak(nats.Context(ctx)); err != nil { + logrus.WithContext(ctx).WithField("subject", subj).Warn(fmt.Errorf("msg.Nak: %w", err)) + sentry.CaptureException(err) } } } - }() - return nil + } } diff --git a/setup/jetstream/nats.go b/setup/jetstream/nats.go index cc896f8ee..b7b69476f 100644 --- a/setup/jetstream/nats.go +++ b/setup/jetstream/nats.go @@ -8,7 +8,6 @@ import ( "sync" "time" - "github.com/getsentry/sentry-go" "github.com/sirupsen/logrus" "github.com/element-hq/dendrite/setup/config" @@ -36,17 +35,20 @@ func DeleteAllStreams(js natsclient.JetStreamContext, cfg *config.JetStream) { func (s *NATSInstance) Prepare(process *process.ProcessContext, cfg *config.JetStream) (natsclient.JetStreamContext, *natsclient.Conn) { natsLock.Lock() defer natsLock.Unlock() - // check if we need an in-process NATS Server - if len(cfg.Addresses) != 0 { - // reuse existing connections - if s.nc != nil { - return s.js, s.nc - } + var err error + + // If an existing connection exists, return it. + if s.nc != nil && s.js != nil { + return s.js, s.nc + } + + // For connecting to an external NATS server. + if len(cfg.Addresses) > 0 { s.js, s.nc = setupNATS(process, cfg, nil) return s.js, s.nc } - if s.Server == nil { - var err error + + if len(cfg.Addresses) == 0 && s.Server == nil { opts := &natsserver.Options{ ServerName: "monolith", DontListen: true, @@ -58,46 +60,61 @@ func (s *NATSInstance) Prepare(process *process.ProcessContext, cfg *config.JetS NoLog: cfg.NoLog, SyncAlways: true, } - s.Server, err = natsserver.NewServer(opts) - if err != nil { + if s.Server, err = natsserver.NewServer(opts); err != nil { panic(err) } if !cfg.NoLog { s.SetLogger(NewLogAdapter(), opts.Debug, opts.Trace) } - go func() { - process.ComponentStarted() - s.Start() - }() + process.ComponentStarted() + go s.Start() go func() { <-process.WaitForShutdown() s.Shutdown() s.WaitForShutdown() process.ComponentFinished() }() + if !s.ReadyForConnections(time.Second * 60) { + logrus.Fatalln("NATS did not start in time, shutting down") + process.ShutdownDendrite() + s.Shutdown() + s.WaitForShutdown() + process.ComponentFinished() + return nil, nil + } } - if !s.ReadyForConnections(time.Second * 60) { - logrus.Fatalln("NATS did not start in time") - } - // reuse existing connections - if s.nc != nil { - return s.js, s.nc - } - nc, err := natsclient.Connect("", natsclient.InProcessServer(s)) - if err != nil { + + // No existing process connection, create a new one. + if s.nc, err = natsclient.Connect("", natsclient.InProcessServer(s.Server)); err != nil { logrus.Fatalln("Failed to create NATS client") } - js, _ := setupNATS(process, cfg, nc) - s.js = js - s.nc = nc - return js, nc + s.js, s.nc = setupNATS(process, cfg, s.nc) + return s.js, s.nc } // nolint:gocyclo func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStreamContext, *natsclient.Conn) { + jsOpts := []natsclient.JSOpt{} + if cfg.JetStreamDomain != "" { + jsOpts = append(jsOpts, natsclient.Domain(cfg.JetStreamDomain)) + } + if nc == nil { var err error - opts := []natsclient.Option{} + opts := []natsclient.Option{ + natsclient.Name("Dendrite"), + natsclient.MaxReconnects(-1), // Try forever + natsclient.ReconnectJitter(time.Second, time.Second), + natsclient.ReconnectWait(time.Second * 10), + natsclient.ReconnectHandler(func(c *natsclient.Conn) { + js, jerr := c.JetStream(jsOpts...) + if jerr != nil { + logrus.WithError(jerr).Panic("Unable to get JetStream context in reconnect handler") + return + } + checkAndConfigureStreams(process, cfg, js) + }), + } if cfg.DisableTLSValidation { opts = append(opts, natsclient.Secure(&tls.Config{ InsecureSkipVerify: true, @@ -113,15 +130,19 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc } } - s, err := nc.JetStream() + js, err := nc.JetStream(jsOpts...) if err != nil { logrus.WithError(err).Panic("Unable to get JetStream context") return nil, nil } + checkAndConfigureStreams(process, cfg, js) + return js, nc +} +func checkAndConfigureStreams(process *process.ProcessContext, cfg *config.JetStream, js natsclient.JetStreamContext) { for _, stream := range streams { // streams are defined in streams.go name := cfg.Prefixed(stream.Name) - info, err := s.StreamInfo(name) + info, err := js.StreamInfo(name) if err != nil && err != natsclient.ErrStreamNotFound { logrus.WithError(err).Fatal("Unable to get stream info") } @@ -153,11 +174,11 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc case info.Config.MaxAge != stream.MaxAge: // Try updating the stream first, as many things can be updated // non-destructively. - if info, err = s.UpdateStream(stream); err != nil { + if info, err = js.UpdateStream(stream); err != nil { logrus.WithError(err).Warnf("Unable to update stream %q, recreating...", name) // We failed to update the stream, this is a last attempt to get // things working but may result in data loss. - if err = s.DeleteStream(name); err != nil { + if err = js.DeleteStream(name); err != nil { logrus.WithError(err).Fatalf("Unable to delete stream %q", name) } info = nil @@ -176,7 +197,7 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc namespaced := *stream namespaced.Name = name namespaced.Subjects = subjects - if _, err = s.AddStream(&namespaced); err != nil { + if _, err = js.AddStream(&namespaced); err != nil { logger := logrus.WithError(err).WithFields(logrus.Fields{ "stream": namespaced.Name, "subjects": namespaced.Subjects, @@ -193,10 +214,9 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc // we can't recover anything that was queued on the disk but we // will still be able to start and run hopefully in the meantime. logger.WithError(err).Error("Unable to add stream") - sentry.CaptureException(fmt.Errorf("Unable to add stream %q: %w", namespaced.Name, err)) namespaced.Storage = natsclient.MemoryStorage - if _, err = s.AddStream(&namespaced); err != nil { + if _, err = js.AddStream(&namespaced); err != nil { // We tried to add the stream in-memory instead but something // went wrong. That's an unrecoverable situation so we will // give up at this point. @@ -208,7 +228,6 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc // disk will be left alone, but our ability to recover from a // future crash will be limited. Yell about it. err := fmt.Errorf("Stream %q is running in-memory; this may be due to data corruption in the JetStream storage directory", namespaced.Name) - sentry.CaptureException(err) process.Degraded(err) } } @@ -229,15 +248,13 @@ func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsc streamName := cfg.Matrix.JetStream.Prefixed(stream) for _, consumer := range consumers { consumerName := cfg.Matrix.JetStream.Prefixed(consumer) + "Pull" - consumerInfo, err := s.ConsumerInfo(streamName, consumerName) + consumerInfo, err := js.ConsumerInfo(streamName, consumerName) if err != nil || consumerInfo == nil { continue } - if err = s.DeleteConsumer(streamName, consumerName); err != nil { + if err = js.DeleteConsumer(streamName, consumerName); err != nil { logrus.WithError(err).Errorf("Unable to clean up old consumer %q for stream %q", consumer, stream) } } } - - return s, nc } diff --git a/setup/mscs/msc2836/msc2836_test.go b/setup/mscs/msc2836/msc2836_test.go index 63bc2c181..5b85e6707 100644 --- a/setup/mscs/msc2836/msc2836_test.go +++ b/setup/mscs/msc2836/msc2836_test.go @@ -14,9 +14,9 @@ import ( "testing" "time" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/setup/process" "github.com/element-hq/dendrite/syncapi/synctypes" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" diff --git a/syncapi/consumers/keychange.go b/syncapi/consumers/keychange.go index 51169d6ba..39f213060 100644 --- a/syncapi/consumers/keychange.go +++ b/syncapi/consumers/keychange.go @@ -10,7 +10,6 @@ import ( "context" "encoding/json" - "github.com/getsentry/sentry-go" roomserverAPI "github.com/element-hq/dendrite/roomserver/api" "github.com/element-hq/dendrite/setup/config" "github.com/element-hq/dendrite/setup/jetstream" @@ -20,6 +19,7 @@ import ( "github.com/element-hq/dendrite/syncapi/streams" "github.com/element-hq/dendrite/syncapi/types" "github.com/element-hq/dendrite/userapi/api" + "github.com/getsentry/sentry-go" "github.com/nats-io/nats.go" "github.com/sirupsen/logrus" ) diff --git a/syncapi/consumers/roomserver.go b/syncapi/consumers/roomserver.go index 907c2b8a9..75afa1c96 100644 --- a/syncapi/consumers/roomserver.go +++ b/syncapi/consumers/roomserver.go @@ -14,7 +14,6 @@ import ( "errors" "fmt" - "github.com/getsentry/sentry-go" "github.com/element-hq/dendrite/internal/fulltext" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/api" @@ -28,6 +27,7 @@ import ( "github.com/element-hq/dendrite/syncapi/streams" "github.com/element-hq/dendrite/syncapi/synctypes" "github.com/element-hq/dendrite/syncapi/types" + "github.com/getsentry/sentry-go" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/nats-io/nats.go" "github.com/sirupsen/logrus" diff --git a/syncapi/internal/keychange.go b/syncapi/internal/keychange.go index 4e98188ee..4945c4c19 100644 --- a/syncapi/internal/keychange.go +++ b/syncapi/internal/keychange.go @@ -35,6 +35,7 @@ func DeviceOTKCounts(ctx context.Context, keyAPI api.SyncKeyAPI, userID, deviceI return queryRes.Error } res.DeviceListsOTKCount = queryRes.Count.KeyCount + res.DeviceListsUnusedFallbackAlgorithms = queryRes.UnusedFallbackAlgorithms return nil } diff --git a/syncapi/routing/messages.go b/syncapi/routing/messages.go index 8d1434abc..2fabb182d 100644 --- a/syncapi/routing/messages.go +++ b/syncapi/routing/messages.go @@ -216,14 +216,6 @@ func OnIncomingMessagesRequest( // TODO: Implement filtering (#587) - // Check the room ID's format. - if _, _, err = gomatrixserverlib.SplitID('!', roomID); err != nil { - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.MissingParam("Bad room ID: " + err.Error()), - } - } - // If the user already left the room, grep events from before that if membershipResp.Membership == spec.Leave { var token types.TopologyToken diff --git a/syncapi/storage/postgres/account_data_table.go b/syncapi/storage/postgres/account_data_table.go index 51d12591b..c8feaa6a8 100644 --- a/syncapi/storage/postgres/account_data_table.go +++ b/syncapi/storage/postgres/account_data_table.go @@ -11,12 +11,12 @@ import ( "context" "database/sql" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/syncapi/storage/tables" "github.com/element-hq/dendrite/syncapi/synctypes" "github.com/element-hq/dendrite/syncapi/types" + "github.com/lib/pq" ) const accountDataSchema = ` @@ -92,6 +92,7 @@ func (s *accountDataStatements) SelectAccountDataInRange( accountDataEventFilter *synctypes.EventFilter, ) (data map[string][]string, pos types.StreamPosition, err error) { data = make(map[string][]string) + pos = r.Low() rows, err := sqlutil.TxStmt(txn, s.selectAccountDataInRangeStmt).QueryContext( ctx, userID, r.Low(), r.High(), @@ -122,7 +123,7 @@ func (s *accountDataStatements) SelectAccountDataInRange( pos = id } } - if pos == 0 { + if len(data) == 0 { pos = r.High() } return data, pos, rows.Err() diff --git a/syncapi/storage/postgres/current_room_state_table.go b/syncapi/storage/postgres/current_room_state_table.go index 506ebf0c0..2e75fefb8 100644 --- a/syncapi/storage/postgres/current_room_state_table.go +++ b/syncapi/storage/postgres/current_room_state_table.go @@ -13,7 +13,6 @@ import ( "encoding/json" "errors" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" rstypes "github.com/element-hq/dendrite/roomserver/types" @@ -21,6 +20,7 @@ import ( "github.com/element-hq/dendrite/syncapi/storage/tables" "github.com/element-hq/dendrite/syncapi/synctypes" "github.com/element-hq/dendrite/syncapi/types" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/syncapi/storage/postgres/output_room_events_table.go b/syncapi/storage/postgres/output_room_events_table.go index 03633f5c8..1ca426f60 100644 --- a/syncapi/storage/postgres/output_room_events_table.go +++ b/syncapi/storage/postgres/output_room_events_table.go @@ -14,7 +14,6 @@ import ( "fmt" "sort" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/roomserver/api" @@ -23,6 +22,7 @@ import ( "github.com/element-hq/dendrite/syncapi/storage/tables" "github.com/element-hq/dendrite/syncapi/synctypes" "github.com/element-hq/dendrite/syncapi/types" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib" ) diff --git a/syncapi/storage/postgres/syncserver.go b/syncapi/storage/postgres/syncserver.go index e6f79f465..321b55b7f 100644 --- a/syncapi/storage/postgres/syncserver.go +++ b/syncapi/storage/postgres/syncserver.go @@ -12,11 +12,11 @@ import ( "database/sql" // Import the postgres database driver. - _ "github.com/lib/pq" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/setup/config" "github.com/element-hq/dendrite/syncapi/storage/postgres/deltas" "github.com/element-hq/dendrite/syncapi/storage/shared" + _ "github.com/lib/pq" ) // SyncServerDatasource represents a sync server datasource which manages diff --git a/syncapi/storage/sqlite3/account_data_table.go b/syncapi/storage/sqlite3/account_data_table.go index b98cf2278..a84deb879 100644 --- a/syncapi/storage/sqlite3/account_data_table.go +++ b/syncapi/storage/sqlite3/account_data_table.go @@ -84,6 +84,8 @@ func (s *accountDataStatements) SelectAccountDataInRange( filter *synctypes.EventFilter, ) (data map[string][]string, pos types.StreamPosition, err error) { data = make(map[string][]string) + pos = r.Low() + stmt, params, err := prepareWithFilters( s.db, txn, selectAccountDataInRangeSQL, []interface{}{ @@ -119,7 +121,7 @@ func (s *accountDataStatements) SelectAccountDataInRange( pos = id } } - if pos == 0 { + if len(data) == 0 { pos = r.High() } return data, pos, rows.Err() diff --git a/syncapi/storage/sqlite3/filtering.go b/syncapi/storage/sqlite3/filtering.go index 50cdd537d..2f0e4ed17 100644 --- a/syncapi/storage/sqlite3/filtering.go +++ b/syncapi/storage/sqlite3/filtering.go @@ -46,7 +46,7 @@ func prepareWithFilters( params, offset = append(params, v), offset+1 } } else { - query += ` AND sender NOT = ""` + query += ` AND sender != ""` } } if types != nil { @@ -66,7 +66,7 @@ func prepareWithFilters( params, offset = append(params, v), offset+1 } } else { - query += ` AND type NOT = ""` + query += ` AND type != ""` } } if containsURL != nil { diff --git a/syncapi/streams/stream_pdu.go b/syncapi/streams/stream_pdu.go index e55ff168f..4daa1afcb 100644 --- a/syncapi/streams/stream_pdu.go +++ b/syncapi/streams/stream_pdu.go @@ -404,7 +404,9 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse( // "state" section and kept in "timeline". sEvents := gomatrixserverlib.HeaderedReverseTopologicalOrdering( gomatrixserverlib.ToPDUs(removeDuplicates(delta.StateEvents, events)), - gomatrixserverlib.TopologicalOrderByAuthEvents, + // sorting by auth events is not stable unless we know the create and historical PL events, + // which we don't for deltas like this. + gomatrixserverlib.TopologicalOrderByPrevEvents, ) delta.StateEvents = make([]*rstypes.HeaderedEvent, len(sEvents)) var skipped int diff --git a/syncapi/syncapi_test.go b/syncapi/syncapi_test.go index 9a9c9b9cd..f84613902 100644 --- a/syncapi/syncapi_test.go +++ b/syncapi/syncapi_test.go @@ -11,11 +11,11 @@ import ( "testing" "time" - "github.com/gorilla/mux" "github.com/element-hq/dendrite/internal/caching" "github.com/element-hq/dendrite/internal/httputil" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/setup/config" + "github.com/gorilla/mux" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/nats-io/nats.go" @@ -548,6 +548,13 @@ func testHistoryVisibility(t *testing.T, dbType test.DBType) { DisplayName: "BOB", } + filters := []string{ + // check that lazy loading doesn't break history visibility + `{"lazy_load_members":true}`, + // Test all kind of filters, including "bad" ones + `{"lazy_load_members":true,"types":null,"not_types":[],"rooms":null,"not_rooms":[],"senders":null,"not_senders":[],"contains_url":null,"io.element.relation_senders":[],"io.element.relation_types":["io.element.thread"]}`, + } + ctx := context.Background() // check guest and normal user accounts for _, accType := range []userapi.AccountType{userapi.AccountTypeGuest, userapi.AccountTypeUser} { @@ -614,79 +621,82 @@ func testHistoryVisibility(t *testing.T, dbType test.DBType) { for _, tc := range testCases { testname := fmt.Sprintf("%s - %s", tc.historyVisibility, userType) t.Run(testname, func(t *testing.T) { - // create a room with the given visibility - room := test.NewRoom(t, alice, test.RoomHistoryVisibility(tc.historyVisibility)) - - // send the events/messages to NATS to create the rooms - beforeJoinBody := fmt.Sprintf("Before invite in a %s room", tc.historyVisibility) - beforeJoinEv := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": beforeJoinBody}) - eventsToSend := append(room.Events(), beforeJoinEv) - if err := api.SendEvents(ctx, rsAPI, api.KindNew, eventsToSend, "test", "test", "test", nil, false); err != nil { - t.Fatalf("failed to send events: %v", err) - } - syncUntil(t, routers, aliceDev.AccessToken, false, - func(syncBody string) bool { - path := fmt.Sprintf(`rooms.join.%s.timeline.events.#(content.body=="%s")`, room.ID, beforeJoinBody) - return gjson.Get(syncBody, path).Exists() - }, - ) - - // There is only one event, we expect only to be able to see this, if the room is world_readable - w := httptest.NewRecorder() - routers.Client.ServeHTTP(w, test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/messages", room.ID), test.WithQueryParams(map[string]string{ - "access_token": bobDev.AccessToken, - "dir": "b", - "filter": `{"lazy_load_members":true}`, // check that lazy loading doesn't break history visibility - }))) - if w.Code != 200 { - t.Logf("%s", w.Body.String()) - t.Fatalf("got HTTP %d want %d", w.Code, 200) - } - // We only care about the returned events at this point - var res struct { - Chunk []synctypes.ClientEvent `json:"chunk"` - } - if err := json.NewDecoder(w.Body).Decode(&res); err != nil { - t.Errorf("failed to decode response body: %s", err) - } - - verifyEventVisible(t, tc.wantResult.seeWithoutJoin, beforeJoinEv, res.Chunk) + for _, filter := range filters { + t.Logf("Using filter: %s", filter) + // create a room with the given visibility + room := test.NewRoom(t, alice, test.RoomHistoryVisibility(tc.historyVisibility)) + + // send the events/messages to NATS to create the rooms + beforeJoinBody := fmt.Sprintf("Before invite in a %s room", tc.historyVisibility) + beforeJoinEv := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": beforeJoinBody}) + eventsToSend := append(room.Events(), beforeJoinEv) + if err := api.SendEvents(ctx, rsAPI, api.KindNew, eventsToSend, "test", "test", "test", nil, false); err != nil { + t.Fatalf("failed to send events: %v", err) + } + syncUntil(t, routers, aliceDev.AccessToken, false, + func(syncBody string) bool { + path := fmt.Sprintf(`rooms.join.%s.timeline.events.#(content.body=="%s")`, room.ID, beforeJoinBody) + return gjson.Get(syncBody, path).Exists() + }, + ) + + // There is only one event, we expect only to be able to see this, if the room is world_readable + w := httptest.NewRecorder() + routers.Client.ServeHTTP(w, test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/messages", room.ID), test.WithQueryParams(map[string]string{ + "access_token": bobDev.AccessToken, + "dir": "b", + "filter": filter, + }))) + if w.Code != 200 { + t.Logf("%s", w.Body.String()) + t.Fatalf("got HTTP %d want %d", w.Code, 200) + } + // We only care about the returned events at this point + var res struct { + Chunk []synctypes.ClientEvent `json:"chunk"` + } + if err := json.NewDecoder(w.Body).Decode(&res); err != nil { + t.Errorf("failed to decode response body: %s", err) + } - // Create invite, a message, join the room and create another message. - inviteEv := room.CreateAndInsert(t, alice, "m.room.member", map[string]interface{}{"membership": "invite"}, test.WithStateKey(bob.ID)) - afterInviteEv := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": fmt.Sprintf("After invite in a %s room", tc.historyVisibility)}) - joinEv := room.CreateAndInsert(t, bob, "m.room.member", map[string]interface{}{"membership": "join"}, test.WithStateKey(bob.ID)) - afterJoinBody := fmt.Sprintf("After join in a %s room", tc.historyVisibility) - msgEv := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": afterJoinBody}) + verifyEventVisible(t, tc.wantResult.seeWithoutJoin, beforeJoinEv, res.Chunk) - eventsToSend = append([]*rstypes.HeaderedEvent{}, inviteEv, afterInviteEv, joinEv, msgEv) + // Create invite, a message, join the room and create another message. + inviteEv := room.CreateAndInsert(t, alice, "m.room.member", map[string]interface{}{"membership": "invite"}, test.WithStateKey(bob.ID)) + afterInviteEv := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": fmt.Sprintf("After invite in a %s room", tc.historyVisibility)}) + joinEv := room.CreateAndInsert(t, bob, "m.room.member", map[string]interface{}{"membership": "join"}, test.WithStateKey(bob.ID)) + afterJoinBody := fmt.Sprintf("After join in a %s room", tc.historyVisibility) + msgEv := room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": afterJoinBody}) - if err := api.SendEvents(ctx, rsAPI, api.KindNew, eventsToSend, "test", "test", "test", nil, false); err != nil { - t.Fatalf("failed to send events: %v", err) - } - syncUntil(t, routers, aliceDev.AccessToken, false, - func(syncBody string) bool { - path := fmt.Sprintf(`rooms.join.%s.timeline.events.#(content.body=="%s")`, room.ID, afterJoinBody) - return gjson.Get(syncBody, path).Exists() - }, - ) + eventsToSend = append([]*rstypes.HeaderedEvent{}, inviteEv, afterInviteEv, joinEv, msgEv) - // Verify the messages after/before invite are visible or not - w = httptest.NewRecorder() - routers.Client.ServeHTTP(w, test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/messages", room.ID), test.WithQueryParams(map[string]string{ - "access_token": bobDev.AccessToken, - "dir": "b", - }))) - if w.Code != 200 { - t.Logf("%s", w.Body.String()) - t.Fatalf("got HTTP %d want %d", w.Code, 200) - } - if err := json.NewDecoder(w.Body).Decode(&res); err != nil { - t.Errorf("failed to decode response body: %s", err) + if err := api.SendEvents(ctx, rsAPI, api.KindNew, eventsToSend, "test", "test", "test", nil, false); err != nil { + t.Fatalf("failed to send events: %v", err) + } + syncUntil(t, routers, aliceDev.AccessToken, false, + func(syncBody string) bool { + path := fmt.Sprintf(`rooms.join.%s.timeline.events.#(content.body=="%s")`, room.ID, afterJoinBody) + return gjson.Get(syncBody, path).Exists() + }, + ) + + // Verify the messages after/before invite are visible or not + w = httptest.NewRecorder() + routers.Client.ServeHTTP(w, test.NewRequest(t, "GET", fmt.Sprintf("/_matrix/client/v3/rooms/%s/messages", room.ID), test.WithQueryParams(map[string]string{ + "access_token": bobDev.AccessToken, + "dir": "b", + }))) + if w.Code != 200 { + t.Logf("%s", w.Body.String()) + t.Fatalf("got HTTP %d want %d", w.Code, 200) + } + if err := json.NewDecoder(w.Body).Decode(&res); err != nil { + t.Errorf("failed to decode response body: %s", err) + } + // verify results + verifyEventVisible(t, tc.wantResult.seeBeforeJoin, beforeJoinEv, res.Chunk) + verifyEventVisible(t, tc.wantResult.seeAfterInvite, afterInviteEv, res.Chunk) } - // verify results - verifyEventVisible(t, tc.wantResult.seeBeforeJoin, beforeJoinEv, res.Chunk) - verifyEventVisible(t, tc.wantResult.seeAfterInvite, afterInviteEv, res.Chunk) }) } } diff --git a/syncapi/types/types.go b/syncapi/types/types.go index de20a6088..be0fde5c1 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -350,13 +350,14 @@ type ToDeviceResponse struct { // Response represents a /sync API response. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync type Response struct { - NextBatch StreamingToken `json:"next_batch"` - AccountData *ClientEvents `json:"account_data,omitempty"` - Presence *ClientEvents `json:"presence,omitempty"` - Rooms *RoomsResponse `json:"rooms,omitempty"` - ToDevice *ToDeviceResponse `json:"to_device,omitempty"` - DeviceLists *DeviceLists `json:"device_lists,omitempty"` - DeviceListsOTKCount map[string]int `json:"device_one_time_keys_count,omitempty"` + NextBatch StreamingToken `json:"next_batch"` + AccountData *ClientEvents `json:"account_data,omitempty"` + Presence *ClientEvents `json:"presence,omitempty"` + Rooms *RoomsResponse `json:"rooms,omitempty"` + ToDevice *ToDeviceResponse `json:"to_device,omitempty"` + DeviceLists *DeviceLists `json:"device_lists,omitempty"` + DeviceListsOTKCount map[string]int `json:"device_one_time_keys_count,omitempty"` + DeviceListsUnusedFallbackAlgorithms []string `json:"device_unused_fallback_key_types"` } func (r Response) MarshalJSON() ([]byte, error) { @@ -419,6 +420,7 @@ func NewResponse() *Response { res.DeviceLists = &DeviceLists{} res.ToDevice = &ToDeviceResponse{} res.DeviceListsOTKCount = map[string]int{} + res.DeviceListsUnusedFallbackAlgorithms = []string{} return &res } diff --git a/sytest-blacklist b/sytest-blacklist index d6fadc7e1..72f62d5e2 100644 --- a/sytest-blacklist +++ b/sytest-blacklist @@ -17,4 +17,12 @@ If a device list update goes missing, the server resyncs on the next one Leaves are present in non-gapped incremental syncs # We don't have any state to calculate m.room.guest_access when accepting invites -Guest users can accept invites to private rooms over federation \ No newline at end of file +Guest users can accept invites to private rooms over federation + +# Tests Synapse specific behavior +/state returns M_NOT_FOUND for an outlier +/state_ids returns M_NOT_FOUND for an outlier + +# this is a silly restriction as it basically stops servers from communicating, so we relax it +# see https://github.com/element-hq/synapse/issues/7543 +Server rejects invalid JSON in a version 6 room \ No newline at end of file diff --git a/sytest-whitelist b/sytest-whitelist index 35d700d0a..910e8c56b 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -774,8 +774,6 @@ Remote user can backfill in a room with version 10 Can reject invites over federation for rooms with version 10 Can receive redactions from regular users over federation in room version 10 New federated private chats get full presence information (SYN-115) -/state returns M_NOT_FOUND for an outlier -/state_ids returns M_NOT_FOUND for an outlier Outbound federation requests missing prev_events and then asks for /state_ids and resolves the state Invited user can reject invite for empty room Invited user can reject local invite after originator leaves @@ -793,4 +791,6 @@ remote user can join room with version 11 User can invite remote user to room with version 11 Remote user can backfill in a room with version 11 Can reject invites over federation for rooms with version 11 -Can receive redactions from regular users over federation in room version 11 \ No newline at end of file +Can receive redactions from regular users over federation in room version 11 +Can upload self-signing keys +uploading signed devices gets propagated over federation diff --git a/test/room.go b/test/room.go index 51e563798..dbe99a2ea 100644 --- a/test/room.go +++ b/test/room.go @@ -43,7 +43,7 @@ type Room struct { visibility gomatrixserverlib.HistoryVisibility creator *User - authEvents gomatrixserverlib.AuthEvents + authEvents *gomatrixserverlib.AuthEvents currentState map[string]*rstypes.HeaderedEvent events []*rstypes.HeaderedEvent } @@ -55,10 +55,11 @@ func NewRoom(t *testing.T, creator *User, modifiers ...roomModifier) *Room { if creator.srvName == "" { t.Fatalf("NewRoom: creator doesn't belong to a server: %+v", *creator) } + authEvents, _ := gomatrixserverlib.NewAuthEvents(nil) r := &Room{ ID: fmt.Sprintf("!%d:%s", counter, creator.srvName), creator: creator, - authEvents: gomatrixserverlib.NewAuthEvents(nil), + authEvents: authEvents, preset: PresetPublicChat, Version: gomatrixserverlib.RoomVersionV9, currentState: make(map[string]*rstypes.HeaderedEvent), @@ -73,7 +74,7 @@ func NewRoom(t *testing.T, creator *User, modifiers ...roomModifier) *Room { func (r *Room) MustGetAuthEventRefsForEvent(t *testing.T, needed gomatrixserverlib.StateNeeded) []string { t.Helper() - a, err := needed.AuthEventReferences(&r.authEvents) + a, err := needed.AuthEventReferences(r.authEvents) if err != nil { t.Fatalf("MustGetAuthEvents: %v", err) } @@ -93,7 +94,7 @@ func (r *Room) insertCreateEvents(t *testing.T) { t.Helper() var joinRule gomatrixserverlib.JoinRuleContent var hisVis gomatrixserverlib.HistoryVisibilityContent - plContent := eventutil.InitialPowerLevelsContent(r.creator.ID) + plContent := eventutil.InitialPowerLevelsContent(gomatrixserverlib.MustGetRoomVersion(r.Version), r.creator.ID) switch r.preset { case PresetTrustedPrivateChat: fallthrough @@ -175,7 +176,7 @@ func (r *Room) CreateEvent(t *testing.T, creator *User, eventType string, conten builder.PrevEvents = []string{r.events[len(r.events)-1].EventID()} } - err = builder.AddAuthEvents(&r.authEvents) + err = builder.AddAuthEvents(r.authEvents) if err != nil { t.Fatalf("CreateEvent[%s]: failed to AuthEventReferences: %s", eventType, err) } @@ -191,7 +192,7 @@ func (r *Room) CreateEvent(t *testing.T, creator *User, eventType string, conten if err != nil { t.Fatalf("CreateEvent[%s]: failed to build event: %s", eventType, err) } - if err = gomatrixserverlib.Allowed(ev, &r.authEvents, UserIDForSender); err != nil { + if err = gomatrixserverlib.Allowed(ev, r.authEvents, UserIDForSender); err != nil { t.Fatalf("CreateEvent[%s]: failed to verify event was allowed: %s", eventType, err) } headeredEvent := &rstypes.HeaderedEvent{PDU: ev} diff --git a/userapi/api/api.go b/userapi/api/api.go index 6da12fc9e..264821296 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -788,12 +788,30 @@ type OneTimeKeysCount struct { KeyCount map[string]int } +// FallbackKeys represents a set of fallback keys for a single device +// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-upload +type FallbackKeys struct { + // The user who owns this device + UserID string + // The device ID of this device + DeviceID string + // A map of algorithm:key_id => key JSON + KeyJSON map[string]json.RawMessage +} + +// Split a key in KeyJSON into algorithm and key ID +func (k *FallbackKeys) Split(keyIDWithAlgo string) (algo string, keyID string) { + segments := strings.Split(keyIDWithAlgo, ":") + return segments[0], segments[1] +} + // PerformUploadKeysRequest is the request to PerformUploadKeys type PerformUploadKeysRequest struct { - UserID string // Required - User performing the request - DeviceID string // Optional - Device performing the request, for fetching OTK count - DeviceKeys []DeviceKeys - OneTimeKeys []OneTimeKeys + UserID string // Required - User performing the request + DeviceID string // Optional - Device performing the request, for fetching OTK count + DeviceKeys []DeviceKeys + OneTimeKeys []OneTimeKeys + FallbackKeys []FallbackKeys // OnlyDisplayNameUpdates should be `true` if ALL the DeviceKeys are present to update // the display name for their respective device, and NOT to modify the keys. The key // itself doesn't change but it's easier to pretend upload new keys and reuse the same code paths. @@ -810,8 +828,9 @@ type PerformUploadKeysResponse struct { // A fatal error when processing e.g database failures Error *KeyError // A map of user_id -> device_id -> Error for tracking failures. - KeyErrors map[string]map[string]*KeyError - OneTimeKeyCounts []OneTimeKeysCount + KeyErrors map[string]map[string]*KeyError + OneTimeKeyCounts []OneTimeKeysCount + FallbackKeysUnusedAlgorithms []string } // PerformDeleteKeysRequest asks the keyserver to forget about certain @@ -917,8 +936,9 @@ type QueryOneTimeKeysRequest struct { type QueryOneTimeKeysResponse struct { // OTK key counts, in the extended /sync form described by https://matrix.org/docs/spec/client_server/r0.6.1#id84 - Count OneTimeKeysCount - Error *KeyError + Count OneTimeKeysCount + UnusedFallbackAlgorithms []string + Error *KeyError } type QueryDeviceMessagesRequest struct { diff --git a/userapi/consumers/roomserver.go b/userapi/consumers/roomserver.go index a79441f33..b33799dc9 100644 --- a/userapi/consumers/roomserver.go +++ b/userapi/consumers/roomserver.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "slices" "strings" "sync" "time" @@ -604,7 +605,9 @@ func (s *OutputRoomEventConsumer) notifyLocal(ctx context.Context, event *rstype // ordering guarantees we must provide. go func() { // This background processing cannot be tied to a request. - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + // We're generous with the "global" timeout, each HTTP request gets its own context with + // at most 30 seconds below. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() var rejected []*pushgateway.Device @@ -621,7 +624,10 @@ func (s *OutputRoomEventConsumer) notifyLocal(ctx context.Context, event *rstype // device, rather than per URL. For now, we must // notify each one separately. for _, dev := range devices { - rej, err := s.notifyHTTP(ctx, event, url, format, []*pushgateway.Device{dev}, mem.Localpart, roomName, int(userNumUnreadNotifs)) + // Give each HTTP request its own context. + httpCtx, httpCancel := context.WithTimeout(ctx, 30*time.Second) + rej, err := s.notifyHTTP(httpCtx, event, url, format, []*pushgateway.Device{dev}, mem.Localpart, roomName, int(userNumUnreadNotifs)) + httpCancel() if err != nil { log.WithFields(log.Fields{ "event_id": event.EventID(), @@ -722,25 +728,34 @@ func (rse *ruleSetEvalContext) HasPowerLevel(senderID spec.SenderID, levelKey st req := &rsapi.QueryLatestEventsAndStateRequest{ RoomID: rse.roomID, StateToFetch: []gomatrixserverlib.StateKeyTuple{ - {EventType: spec.MRoomPowerLevels}, + {EventType: spec.MRoomPowerLevels, StateKey: ""}, + {EventType: spec.MRoomCreate, StateKey: ""}, }, } var res rsapi.QueryLatestEventsAndStateResponse if err := rse.rsAPI.QueryLatestEventsAndState(rse.ctx, req, &res); err != nil { return false, err } - for _, ev := range res.StateEvents { - if ev.Type() != spec.MRoomPowerLevels { - continue + var createEvent, plEvent *rstypes.HeaderedEvent + for i, ev := range res.StateEvents { + if ev.Type() == spec.MRoomCreate { + createEvent = res.StateEvents[i] + } else if ev.Type() == spec.MRoomPowerLevels { + plEvent = res.StateEvents[i] } - - plc, err := gomatrixserverlib.NewPowerLevelContentFromEvent(ev.PDU) - if err != nil { - return false, err - } - return plc.UserLevel(senderID) >= plc.NotificationLevel(levelKey), nil } - return true, nil + verImpl := gomatrixserverlib.MustGetRoomVersion(createEvent.Version()) + if verImpl.PrivilegedCreators() && slices.Contains(gomatrixserverlib.CreatorsFromCreateEvent(createEvent), string(senderID)) { + return true, nil + } + if plEvent == nil { + return true, nil // unsure, but this is what we did before + } + plc, err := gomatrixserverlib.NewPowerLevelContentFromEvent(plEvent.PDU) + if err != nil { + return false, err + } + return plc.UserLevel(senderID) >= plc.NotificationLevel(levelKey), nil } // localPushDevices pushes to the configured devices of a local diff --git a/userapi/internal/key_api.go b/userapi/internal/key_api.go index 09ead2c57..6cb11bcd2 100644 --- a/userapi/internal/key_api.go +++ b/userapi/internal/key_api.go @@ -44,14 +44,22 @@ func (a *UserInternalAPI) PerformUploadKeys(ctx context.Context, req *api.Perfor if len(req.DeviceKeys) > 0 { a.uploadLocalDeviceKeys(ctx, req, res) } - if len(req.OneTimeKeys) > 0 { - a.uploadOneTimeKeys(ctx, req, res) + if len(req.OneTimeKeys) > 0 || len(req.FallbackKeys) > 0 { + a.uploadOneTimeAndFallbackKeys(ctx, req, res) } otks, err := a.KeyDatabase.OneTimeKeysCount(ctx, req.UserID, req.DeviceID) if err != nil { return err } + algos, err := a.KeyDatabase.UnusedFallbackKeyAlgorithms(ctx, req.UserID, req.DeviceID) + if err != nil { + res.Error = &api.KeyError{ + Err: fmt.Sprintf("Failed to query unused fallback algorithms: %s", err), + } + return nil + } res.OneTimeKeyCounts = []api.OneTimeKeysCount{*otks} + res.FallbackKeysUnusedAlgorithms = algos return nil } @@ -169,7 +177,15 @@ func (a *UserInternalAPI) QueryOneTimeKeys(ctx context.Context, req *api.QueryOn } return nil } + algos, err := a.KeyDatabase.UnusedFallbackKeyAlgorithms(ctx, req.UserID, req.DeviceID) + if err != nil { + res.Error = &api.KeyError{ + Err: fmt.Sprintf("Failed to query unused fallback algorithms: %s", err), + } + return nil + } res.Count = *count + res.UnusedFallbackAlgorithms = algos return nil } @@ -507,6 +523,9 @@ func (a *UserInternalAPI) queryRemoteKeysOnServer( for userID := range userIDsForAllDevices { err := a.Updater.ManualUpdate(context.Background(), spec.ServerName(serverName), userID) if err != nil { + if errors.Is(err, context.Canceled) { + return + } logrus.WithFields(logrus.Fields{ logrus.ErrorKey: err, "user_id": userID, @@ -520,6 +539,9 @@ func (a *UserInternalAPI) queryRemoteKeysOnServer( // user so the fact that we're populating all devices here isn't a problem so long as we have devices. err = a.populateResponseWithDeviceKeysFromDatabase(ctx, res, respMu, userID, nil) if err != nil { + if errors.Is(err, context.Canceled) { + return + } logrus.WithFields(logrus.Fields{ logrus.ErrorKey: err, "user_id": userID, @@ -715,7 +737,7 @@ func (a *UserInternalAPI) uploadLocalDeviceKeys(ctx context.Context, req *api.Pe } } -func (a *UserInternalAPI) uploadOneTimeKeys(ctx context.Context, req *api.PerformUploadKeysRequest, res *api.PerformUploadKeysResponse) { +func (a *UserInternalAPI) uploadOneTimeAndFallbackKeys(ctx context.Context, req *api.PerformUploadKeysRequest, res *api.PerformUploadKeysResponse) { if req.UserID == "" { res.Error = &api.KeyError{ Err: "user ID missing", @@ -768,7 +790,32 @@ func (a *UserInternalAPI) uploadOneTimeKeys(ctx context.Context, req *api.Perfor // collect counts res.OneTimeKeyCounts = append(res.OneTimeKeyCounts, *counts) } - + if len(req.FallbackKeys) > 0 { + if err := a.KeyDatabase.DeleteFallbackKeys(ctx, req.UserID, req.DeviceID); err != nil { + res.KeyError(req.UserID, req.DeviceID, &api.KeyError{ + Err: fmt.Sprintf("%s device %s : failed to clear fallback keys: %s", req.UserID, req.DeviceID, err.Error()), + }) + return + } + for _, key := range req.FallbackKeys { + // grab existing keys based on (user/device/algorithm/key ID) + keyIDsWithAlgorithms := make([]string, len(key.KeyJSON)) + i := 0 + for keyIDWithAlgo := range key.KeyJSON { + keyIDsWithAlgorithms[i] = keyIDWithAlgo + i++ + } + unused, err := a.KeyDatabase.StoreFallbackKeys(ctx, key) + if err != nil { + res.KeyError(req.UserID, req.DeviceID, &api.KeyError{ + Err: fmt.Sprintf("%s device %s : failed to store fallback keys: %s", req.UserID, req.DeviceID, err.Error()), + }) + continue + } + // collect counts + res.FallbackKeysUnusedAlgorithms = unused + } + } } func emitDeviceKeyChanges(producer KeyChangeProducer, existing, new []api.DeviceMessage, onlyUpdateDisplayName bool) error { diff --git a/userapi/internal/user_api.go b/userapi/internal/user_api.go index 666e75f93..48fb441a2 100644 --- a/userapi/internal/user_api.go +++ b/userapi/internal/user_api.go @@ -337,7 +337,7 @@ func (a *UserInternalAPI) PerformDeviceDeletion(ctx context.Context, req *api.Pe deleteReq := &api.PerformDeleteKeysRequest{ UserID: req.UserID, } - for _, keyID := range req.DeviceIDs { + for _, keyID := range deletedDeviceIDs { deleteReq.KeyIDs = append(deleteReq.KeyIDs, gomatrixserverlib.KeyID(keyID)) } deleteRes := &api.PerformDeleteKeysResponse{} diff --git a/userapi/storage/interface.go b/userapi/storage/interface.go index 7767f6cdb..2a46a7fd7 100644 --- a/userapi/storage/interface.go +++ b/userapi/storage/interface.go @@ -167,6 +167,15 @@ type KeyDatabase interface { // OneTimeKeysCount returns a count of all OTKs for this device. OneTimeKeysCount(ctx context.Context, userID, deviceID string) (*api.OneTimeKeysCount, error) + // StoreFallbackKeys persists the given fallback keys. + StoreFallbackKeys(ctx context.Context, keys api.FallbackKeys) ([]string, error) + + // UnusedFallbackKeyAlgorithms returns unused fallback algorithms for this user/device. + UnusedFallbackKeyAlgorithms(ctx context.Context, userID, deviceID string) ([]string, error) + + // DeleteFallbackKeys deletes all fallback keys for the user. + DeleteFallbackKeys(ctx context.Context, userID, deviceID string) error + // DeviceKeysJSON populates the KeyJSON for the given keys. If any proided `keys` have a `KeyJSON` or `StreamID` already then it will be replaced. DeviceKeysJSON(ctx context.Context, keys []api.DeviceMessage) error diff --git a/userapi/storage/postgres/devices_table.go b/userapi/storage/postgres/devices_table.go index a12e4b173..b5feea07f 100644 --- a/userapi/storage/postgres/devices_table.go +++ b/userapi/storage/postgres/devices_table.go @@ -12,13 +12,13 @@ import ( "fmt" "time" - "github.com/lib/pq" "github.com/element-hq/dendrite/clientapi/userutil" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/userapi/api" "github.com/element-hq/dendrite/userapi/storage/postgres/deltas" "github.com/element-hq/dendrite/userapi/storage/tables" + "github.com/lib/pq" "github.com/matrix-org/gomatrixserverlib/spec" ) diff --git a/userapi/storage/postgres/fallback_keys_table.go b/userapi/storage/postgres/fallback_keys_table.go new file mode 100644 index 000000000..acae7ed68 --- /dev/null +++ b/userapi/storage/postgres/fallback_keys_table.go @@ -0,0 +1,134 @@ +// Copyright 2024 New Vector Ltd. +// Copyright 2017 Vector Creations Ltd +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +package postgres + +import ( + "context" + "database/sql" + "encoding/json" + "time" + + "github.com/element-hq/dendrite/internal" + "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/element-hq/dendrite/userapi/api" + "github.com/element-hq/dendrite/userapi/storage/tables" +) + +var fallbackKeysSchema = ` +-- Stores one-time public keys for users +CREATE TABLE IF NOT EXISTS keyserver_fallback_keys ( + user_id TEXT NOT NULL, + device_id TEXT NOT NULL, + key_id TEXT NOT NULL, + algorithm TEXT NOT NULL, + ts_added_secs BIGINT NOT NULL, + key_json TEXT NOT NULL, + used BOOLEAN NOT NULL, + -- Clobber based on tuple of user/device/algorithm. + CONSTRAINT keyserver_fallback_keys_unique UNIQUE (user_id, device_id, algorithm) +); + +CREATE INDEX IF NOT EXISTS keyserver_fallback_keys_idx ON keyserver_fallback_keys (user_id, device_id); +` + +const upsertFallbackKeysSQL = "" + + "INSERT INTO keyserver_fallback_keys (user_id, device_id, key_id, algorithm, ts_added_secs, key_json, used)" + + " VALUES ($1, $2, $3, $4, $5, $6, false)" + + " ON CONFLICT ON CONSTRAINT keyserver_fallback_keys_unique" + + " DO UPDATE SET key_id = $3, key_json = $6, used = false" + +const selectFallbackUnusedAlgorithmsSQL = "" + + "SELECT algorithm FROM keyserver_fallback_keys WHERE user_id = $1 AND device_id = $2 AND used = false" + +const selectFallbackKeysByAlgorithmSQL = "" + + "SELECT key_id, key_json FROM keyserver_fallback_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 ORDER BY used ASC LIMIT 1" + +const deleteFallbackKeysSQL = "" + + "DELETE FROM keyserver_fallback_keys WHERE user_id = $1 AND device_id = $2" + +const updateFallbackKeyUsedSQL = "" + + "UPDATE keyserver_fallback_keys SET used=true WHERE user_id = $1 AND device_id = $2 AND key_id = $3 AND algorithm = $4" + +type fallbackKeysStatements struct { + db *sql.DB + upsertKeysStmt *sql.Stmt + selectUnusedAlgorithmsStmt *sql.Stmt + selectKeyByAlgorithmStmt *sql.Stmt + deleteFallbackKeysStmt *sql.Stmt + updateFallbackKeyUsedStmt *sql.Stmt +} + +func NewPostgresFallbackKeysTable(db *sql.DB) (tables.FallbackKeys, error) { + s := &fallbackKeysStatements{ + db: db, + } + _, err := db.Exec(fallbackKeysSchema) + if err != nil { + return nil, err + } + return s, sqlutil.StatementList{ + {&s.upsertKeysStmt, upsertFallbackKeysSQL}, + {&s.selectUnusedAlgorithmsStmt, selectFallbackUnusedAlgorithmsSQL}, + {&s.selectKeyByAlgorithmStmt, selectFallbackKeysByAlgorithmSQL}, + {&s.deleteFallbackKeysStmt, deleteFallbackKeysSQL}, + {&s.updateFallbackKeyUsedStmt, updateFallbackKeyUsedSQL}, + }.Prepare(db) +} + +func (s *fallbackKeysStatements) SelectUnusedFallbackKeyAlgorithms(ctx context.Context, userID, deviceID string) ([]string, error) { + rows, err := s.selectUnusedAlgorithmsStmt.QueryContext(ctx, userID, deviceID) + if err != nil { + return nil, err + } + defer internal.CloseAndLogIfError(ctx, rows, "selectKeysCountStmt: rows.close() failed") + algos := []string{} + for rows.Next() { + var algorithm string + if err = rows.Scan(&algorithm); err != nil { + return nil, err + } + algos = append(algos, algorithm) + } + return algos, rows.Err() +} + +func (s *fallbackKeysStatements) InsertFallbackKeys(ctx context.Context, txn *sql.Tx, keys api.FallbackKeys) ([]string, error) { + now := time.Now().Unix() + for keyIDWithAlgo, keyJSON := range keys.KeyJSON { + algo, keyID := keys.Split(keyIDWithAlgo) + _, err := sqlutil.TxStmt(txn, s.upsertKeysStmt).ExecContext( + ctx, keys.UserID, keys.DeviceID, keyID, algo, now, string(keyJSON), + ) + if err != nil { + return nil, err + } + } + return s.SelectUnusedFallbackKeyAlgorithms(ctx, keys.UserID, keys.DeviceID) +} + +func (s *fallbackKeysStatements) DeleteFallbackKeys(ctx context.Context, txn *sql.Tx, userID, deviceID string) error { + _, err := sqlutil.TxStmt(txn, s.deleteFallbackKeysStmt).ExecContext(ctx, userID, deviceID) + return err +} + +func (s *fallbackKeysStatements) SelectAndUpdateFallbackKey( + ctx context.Context, txn *sql.Tx, userID, deviceID, algorithm string, +) (map[string]json.RawMessage, error) { + var keyID string + var keyJSON string + err := sqlutil.TxStmtContext(ctx, txn, s.selectKeyByAlgorithmStmt).QueryRowContext(ctx, userID, deviceID, algorithm).Scan(&keyID, &keyJSON) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + _, err = sqlutil.TxStmtContext(ctx, txn, s.updateFallbackKeyUsedStmt).ExecContext(ctx, userID, deviceID, algorithm, keyID) + return map[string]json.RawMessage{ + algorithm + ":" + keyID: json.RawMessage(keyJSON), + }, err +} diff --git a/userapi/storage/postgres/one_time_keys_table.go b/userapi/storage/postgres/one_time_keys_table.go index 397cb7d66..a38da25ba 100644 --- a/userapi/storage/postgres/one_time_keys_table.go +++ b/userapi/storage/postgres/one_time_keys_table.go @@ -12,11 +12,11 @@ import ( "encoding/json" "time" - "github.com/lib/pq" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" "github.com/element-hq/dendrite/userapi/api" "github.com/element-hq/dendrite/userapi/storage/tables" + "github.com/lib/pq" ) var oneTimeKeysSchema = ` @@ -53,7 +53,7 @@ const deleteOneTimeKeySQL = "" + "DELETE FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 AND key_id = $4" const selectKeyByAlgorithmSQL = "" + - "SELECT key_id, key_json FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 LIMIT 1" + "SELECT key_id, key_json FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 ORDER BY ts_added_secs ASC LIMIT 1" const deleteOneTimeKeysSQL = "" + "DELETE FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2" diff --git a/userapi/storage/postgres/storage.go b/userapi/storage/postgres/storage.go index 696e1aa6a..c7fb9d29b 100644 --- a/userapi/storage/postgres/storage.go +++ b/userapi/storage/postgres/storage.go @@ -141,6 +141,10 @@ func NewKeyDatabase(conMan *sqlutil.Connections, dbProperties *config.DatabaseOp if err != nil { return nil, err } + fk, err := NewPostgresFallbackKeysTable(db) + if err != nil { + return nil, err + } dk, err := NewPostgresDeviceKeysTable(db) if err != nil { return nil, err @@ -164,6 +168,7 @@ func NewKeyDatabase(conMan *sqlutil.Connections, dbProperties *config.DatabaseOp return &shared.KeyDatabase{ OneTimeKeysTable: otk, + FallbackKeysTable: fk, DeviceKeysTable: dk, KeyChangesTable: kc, StaleDeviceListsTable: sdl, diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index 2b1885cd0..44ace733e 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -57,6 +57,7 @@ type Database struct { type KeyDatabase struct { OneTimeKeysTable tables.OneTimeKeys + FallbackKeysTable tables.FallbackKeys DeviceKeysTable tables.DeviceKeys KeyChangesTable tables.KeyChanges StaleDeviceListsTable tables.StaleDeviceLists @@ -937,6 +938,22 @@ func (d *KeyDatabase) OneTimeKeysCount(ctx context.Context, userID, deviceID str return d.OneTimeKeysTable.CountOneTimeKeys(ctx, userID, deviceID) } +func (d *KeyDatabase) StoreFallbackKeys(ctx context.Context, keys api.FallbackKeys) (unused []string, err error) { + _ = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + unused, err = d.FallbackKeysTable.InsertFallbackKeys(ctx, txn, keys) + return err + }) + return +} + +func (d *KeyDatabase) DeleteFallbackKeys(ctx context.Context, userID, deviceID string) error { + return d.FallbackKeysTable.DeleteFallbackKeys(ctx, nil, userID, deviceID) +} + +func (d *KeyDatabase) UnusedFallbackKeyAlgorithms(ctx context.Context, userID, deviceID string) ([]string, error) { + return d.FallbackKeysTable.SelectUnusedFallbackKeyAlgorithms(ctx, userID, deviceID) +} + func (d *KeyDatabase) DeviceKeysJSON(ctx context.Context, keys []api.DeviceMessage) error { return d.DeviceKeysTable.SelectDeviceKeysJSON(ctx, keys) } @@ -999,6 +1016,12 @@ func (d *KeyDatabase) ClaimKeys(ctx context.Context, userToDeviceToAlgorithm map if err != nil { return err } + if len(keyJSON) == 0 { + keyJSON, err = d.FallbackKeysTable.SelectAndUpdateFallbackKey(ctx, txn, userID, deviceID, algo) + if err != nil { + return err + } + } if keyJSON != nil { result = append(result, api.OneTimeKeys{ UserID: userID, diff --git a/userapi/storage/sqlite3/fallback_keys_table.go b/userapi/storage/sqlite3/fallback_keys_table.go new file mode 100644 index 000000000..2eb99813e --- /dev/null +++ b/userapi/storage/sqlite3/fallback_keys_table.go @@ -0,0 +1,132 @@ +// Copyright 2024 New Vector Ltd. +// Copyright 2017 Vector Creations Ltd +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +package sqlite3 + +import ( + "context" + "database/sql" + "encoding/json" + "time" + + "github.com/element-hq/dendrite/internal" + "github.com/element-hq/dendrite/internal/sqlutil" + "github.com/element-hq/dendrite/userapi/api" + "github.com/element-hq/dendrite/userapi/storage/tables" +) + +var fallbackKeysSchema = ` +-- Stores one-time public keys for users +CREATE TABLE IF NOT EXISTS keyserver_fallback_keys ( + user_id TEXT NOT NULL, + device_id TEXT NOT NULL, + key_id TEXT NOT NULL, + algorithm TEXT NOT NULL, + ts_added_secs BIGINT NOT NULL, + key_json TEXT NOT NULL, + used BOOLEAN NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS keyserver_fallback_keys_unique_idx ON keyserver_fallback_keys(user_id, device_id, algorithm); +CREATE INDEX IF NOT EXISTS keyserver_fallback_keys_idx ON keyserver_fallback_keys (user_id, device_id); +` + +const upsertFallbackKeysSQL = "" + + "INSERT INTO keyserver_fallback_keys (user_id, device_id, key_id, algorithm, ts_added_secs, key_json, used)" + + " VALUES ($1, $2, $3, $4, $5, $6, false)" + + " ON CONFLICT (user_id, device_id, algorithm)" + + " DO UPDATE SET key_id = $3, key_json = $6, used = false" + +const selectFallbackUnusedAlgorithmsSQL = "" + + "SELECT algorithm FROM keyserver_fallback_keys WHERE user_id = $1 AND device_id = $2 AND used = false" + +const selectFallbackKeysByAlgorithmSQL = "" + + "SELECT key_id, key_json FROM keyserver_fallback_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 ORDER BY used ASC LIMIT 1" + +const deleteFallbackKeysSQL = "" + + "DELETE FROM keyserver_fallback_keys WHERE user_id = $1 AND device_id = $2" + +const updateFallbackKeyUsedSQL = "" + + "UPDATE keyserver_fallback_keys SET used=true WHERE user_id = $1 AND device_id = $2 AND key_id = $3 AND algorithm = $4" + +type fallbackKeysStatements struct { + db *sql.DB + upsertKeysStmt *sql.Stmt + selectUnusedAlgorithmsStmt *sql.Stmt + selectKeyByAlgorithmStmt *sql.Stmt + deleteFallbackKeysStmt *sql.Stmt + updateFallbackKeyUsedStmt *sql.Stmt +} + +func NewSqliteFallbackKeysTable(db *sql.DB) (tables.FallbackKeys, error) { + s := &fallbackKeysStatements{ + db: db, + } + _, err := db.Exec(fallbackKeysSchema) + if err != nil { + return nil, err + } + return s, sqlutil.StatementList{ + {&s.upsertKeysStmt, upsertFallbackKeysSQL}, + {&s.selectUnusedAlgorithmsStmt, selectFallbackUnusedAlgorithmsSQL}, + {&s.selectKeyByAlgorithmStmt, selectFallbackKeysByAlgorithmSQL}, + {&s.deleteFallbackKeysStmt, deleteFallbackKeysSQL}, + {&s.updateFallbackKeyUsedStmt, updateFallbackKeyUsedSQL}, + }.Prepare(db) +} + +func (s *fallbackKeysStatements) SelectUnusedFallbackKeyAlgorithms(ctx context.Context, userID, deviceID string) ([]string, error) { + rows, err := s.selectUnusedAlgorithmsStmt.QueryContext(ctx, userID, deviceID) + if err != nil { + return nil, err + } + defer internal.CloseAndLogIfError(ctx, rows, "selectKeysCountStmt: rows.close() failed") + algos := []string{} + for rows.Next() { + var algorithm string + if err = rows.Scan(&algorithm); err != nil { + return nil, err + } + algos = append(algos, algorithm) + } + return algos, rows.Err() +} + +func (s *fallbackKeysStatements) InsertFallbackKeys(ctx context.Context, txn *sql.Tx, keys api.FallbackKeys) ([]string, error) { + now := time.Now().Unix() + for keyIDWithAlgo, keyJSON := range keys.KeyJSON { + algo, keyID := keys.Split(keyIDWithAlgo) + _, err := sqlutil.TxStmt(txn, s.upsertKeysStmt).ExecContext( + ctx, keys.UserID, keys.DeviceID, keyID, algo, now, string(keyJSON), + ) + if err != nil { + return nil, err + } + } + return s.SelectUnusedFallbackKeyAlgorithms(ctx, keys.UserID, keys.DeviceID) +} + +func (s *fallbackKeysStatements) DeleteFallbackKeys(ctx context.Context, txn *sql.Tx, userID, deviceID string) error { + _, err := sqlutil.TxStmt(txn, s.deleteFallbackKeysStmt).ExecContext(ctx, userID, deviceID) + return err +} + +func (s *fallbackKeysStatements) SelectAndUpdateFallbackKey( + ctx context.Context, txn *sql.Tx, userID, deviceID, algorithm string, +) (map[string]json.RawMessage, error) { + var keyID string + var keyJSON string + err := sqlutil.TxStmtContext(ctx, txn, s.selectKeyByAlgorithmStmt).QueryRowContext(ctx, userID, deviceID, algorithm).Scan(&keyID, &keyJSON) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + _, err = sqlutil.TxStmtContext(ctx, txn, s.updateFallbackKeyUsedStmt).ExecContext(ctx, userID, deviceID, algorithm, keyID) + return map[string]json.RawMessage{ + algorithm + ":" + keyID: json.RawMessage(keyJSON), + }, err +} diff --git a/userapi/storage/sqlite3/one_time_keys_table.go b/userapi/storage/sqlite3/one_time_keys_table.go index 0ef4639e6..bb2df83b6 100644 --- a/userapi/storage/sqlite3/one_time_keys_table.go +++ b/userapi/storage/sqlite3/one_time_keys_table.go @@ -52,7 +52,7 @@ const deleteOneTimeKeySQL = "" + "DELETE FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 AND key_id = $4" const selectKeyByAlgorithmSQL = "" + - "SELECT key_id, key_json FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 LIMIT 1" + "SELECT key_id, key_json FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2 AND algorithm = $3 ORDER BY ts_added_secs ASC LIMIT 1" const deleteOneTimeKeysSQL = "" + "DELETE FROM keyserver_one_time_keys WHERE user_id = $1 AND device_id = $2" diff --git a/userapi/storage/sqlite3/storage.go b/userapi/storage/sqlite3/storage.go index c57cc153e..6d906191f 100644 --- a/userapi/storage/sqlite3/storage.go +++ b/userapi/storage/sqlite3/storage.go @@ -138,6 +138,10 @@ func NewKeyDatabase(conMan *sqlutil.Connections, dbProperties *config.DatabaseOp if err != nil { return nil, err } + fk, err := NewSqliteFallbackKeysTable(db) + if err != nil { + return nil, err + } dk, err := NewSqliteDeviceKeysTable(db) if err != nil { return nil, err @@ -161,6 +165,7 @@ func NewKeyDatabase(conMan *sqlutil.Connections, dbProperties *config.DatabaseOp return &shared.KeyDatabase{ OneTimeKeysTable: otk, + FallbackKeysTable: fk, DeviceKeysTable: dk, KeyChangesTable: kc, StaleDeviceListsTable: sdl, diff --git a/userapi/storage/storage_test.go b/userapi/storage/storage_test.go index 68198b379..189c1dd8b 100644 --- a/userapi/storage/storage_test.go +++ b/userapi/storage/storage_test.go @@ -809,3 +809,42 @@ func TestOneTimeKeys(t *testing.T) { } }) } + +func TestFallbackKeys(t *testing.T) { + test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) { + db, clean := mustCreateKeyDatabase(t, dbType) + defer clean() + userID := "@alice:localhost" + deviceID := "alice_device" + fk := api.FallbackKeys{ + UserID: userID, + DeviceID: deviceID, + KeyJSON: map[string]json.RawMessage{"curve25519:KEY1": []byte(`{"key":"v1"}`)}, + } + + _, err := db.StoreFallbackKeys(ctx, fk) + MustNotError(t, err) + + unused, err := db.UnusedFallbackKeyAlgorithms(ctx, userID, deviceID) + MustNotError(t, err) + if c := len(unused); c != 1 { + t.Fatalf("Expected 1 unused key algorithm, got %d", c) + } + if unused[0] != "curve25519" { + t.Fatalf("Expected unused key algorithm to be 'curve25519', got '%s'", unused[0]) + } + + // No other one-time keys have been uploaded so we expect to get the fallback key instead. + claimed, err := db.ClaimKeys(ctx, map[string]map[string]string{userID: {deviceID: "curve25519"}}) + MustNotError(t, err) + + switch { + case claimed[0].UserID != fk.UserID: + t.Fatalf("Claimed user ID ID doesn't match, got %q, want %q", claimed[0].UserID, fk.DeviceID) + case claimed[0].DeviceID != fk.DeviceID: + t.Fatalf("Claimed device ID doesn't match, got %q, want %q", claimed[0].DeviceID, fk.DeviceID) + case claimed[0].KeyJSON["curve25519:KEY1"] == nil: + t.Fatalf("Claimed key JSON for curve25519:KEY1 not found") + } + }) +} diff --git a/userapi/storage/tables/interface.go b/userapi/storage/tables/interface.go index 7d4cfbae4..44f31a5c5 100644 --- a/userapi/storage/tables/interface.go +++ b/userapi/storage/tables/interface.go @@ -170,6 +170,13 @@ type DeviceKeys interface { DeleteAllDeviceKeys(ctx context.Context, txn *sql.Tx, userID string) error } +type FallbackKeys interface { + SelectUnusedFallbackKeyAlgorithms(ctx context.Context, userID, deviceID string) ([]string, error) + InsertFallbackKeys(ctx context.Context, txn *sql.Tx, keys api.FallbackKeys) ([]string, error) + DeleteFallbackKeys(ctx context.Context, txn *sql.Tx, userID, deviceID string) error + SelectAndUpdateFallbackKey(ctx context.Context, txn *sql.Tx, userID, deviceID, algorithm string) (map[string]json.RawMessage, error) +} + type KeyChanges interface { InsertKeyChange(ctx context.Context, userID string) (int64, error) // SelectKeyChanges returns the set (de-duplicated) of users who have changed their keys between the two offsets.