Compare commits
1 Commits
emmanuel-f
...
erikj/join
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13da1dca0a |
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,4 +9,5 @@
|
||||
- End with either a period (.) or an exclamation mark (!).
|
||||
- Start with a capital letter.
|
||||
- Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
|
||||
* [ ] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))
|
||||
* [ ] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct
|
||||
(run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
run: docker buildx inspect
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
|
||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
- name: Build and push all platforms
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||
with:
|
||||
push: true
|
||||
labels: |
|
||||
|
||||
2
.github/workflows/docs-pr-netlify.yaml
vendored
2
.github/workflows/docs-pr-netlify.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
||||
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
||||
- name: 📥 Download artifact
|
||||
uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 # v9
|
||||
uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
with:
|
||||
workflow: docs-pr.yaml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
4
.github/workflows/docs-pr.yaml
vendored
4
.github/workflows/docs-pr.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
mdbook-version: '0.4.17'
|
||||
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
cp book/welcome_and_overview.html book/index.html
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: book
|
||||
path: book
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
run: echo 'window.SYNAPSE_VERSION = "${{ needs.pre.outputs.branch-version }}";' > ./docs/website_files/version.js
|
||||
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
||||
2
.github/workflows/fix_lint.yaml
vendored
2
.github/workflows/fix_lint.yaml
vendored
@@ -44,6 +44,6 @@ jobs:
|
||||
- run: cargo fmt
|
||||
continue-on-error: true
|
||||
|
||||
- uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0
|
||||
- uses: stefanzweifel/git-auto-commit-action@e348103e9026cc0eee72ae06630dbe30c8bf7a79 # v5.1.0
|
||||
with:
|
||||
commit_message: "Attempt to fix linting"
|
||||
|
||||
6
.github/workflows/latest_deps.yml
vendored
6
.github/workflows/latest_deps.yml
vendored
@@ -86,7 +86,7 @@ jobs:
|
||||
-e POSTGRES_PASSWORD=postgres \
|
||||
-e POSTGRES_INITDB_ARGS="--lc-collate C --lc-ctype C --encoding UTF8" \
|
||||
postgres:${{ matrix.postgres-version }}
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- run: pip install .[all,test]
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
if: ${{ always() }}
|
||||
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
|
||||
- name: Upload SyTest logs
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: Sytest Logs - ${{ job.status }} - (${{ join(matrix.*, ', ') }})
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
- name: Prepare Complement's Prerequisites
|
||||
run: synapse/.ci/scripts/setup_complement_prerequisites.sh
|
||||
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
cache-dependency-path: complement/go.sum
|
||||
go-version-file: complement/go.mod
|
||||
|
||||
2
.github/workflows/poetry_lockfile.yaml
vendored
2
.github/workflows/poetry_lockfile.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: pip install tomli
|
||||
|
||||
20
.github/workflows/release-artifacts.yml
vendored
20
.github/workflows/release-artifacts.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- id: set-distros
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
install: true
|
||||
|
||||
- name: Set up docker layer caching
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Set up python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
echo "ARTIFACT_NAME=${DISTRO#*:}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload debs as artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: debs-${{ steps.artifact-name.outputs.ARTIFACT_NAME }}
|
||||
path: debs/*
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
# setup-python@v4 doesn't impose a default python version. Need to use 3.x
|
||||
# here, because `python` on osx points to Python 2.7.
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CIBW_ENVIRONMENT_PASS_LINUX: CARGO_NET_GIT_FETCH_WITH_CLI
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: Wheel-${{ matrix.os }}-${{ matrix.arch }}
|
||||
path: ./wheelhouse/*.whl
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
- name: Build sdist
|
||||
run: python -m build --sdist
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: Sdist
|
||||
path: dist/*.tar.gz
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all workflow run artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
uses: actions/download-artifact@b14cf4c92620c250e1c074ab0a5800e37df86765 # v4.2.0
|
||||
- name: Build a tarball for the debs
|
||||
# We need to merge all the debs uploads into one folder, then compress
|
||||
# that.
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
tar -cvJf debs.tar.xz debs
|
||||
- name: Attach to release
|
||||
# Pinned to work around https://github.com/softprops/action-gh-release/issues/445
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v0.1.15
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
20
.github/workflows/tests.yml
vendored
20
.github/workflows/tests.yml
vendored
@@ -102,7 +102,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- run: "pip install 'click==8.1.1' 'GitPython>=3.1.20'"
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- run: .ci/scripts/check_lockfile.py
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
# Cribbed from
|
||||
# https://github.com/AustinScola/mypy-cache-github-action/blob/85ea4f2972abed39b33bd02c36e341b28ca59213/src/restore.ts#L10-L17
|
||||
- name: Restore/persist mypy's cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
|
||||
with:
|
||||
path: |
|
||||
.mypy_cache
|
||||
@@ -192,7 +192,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- run: "pip install 'towncrier>=18.6.0rc1'"
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
if: ${{ needs.changes.outputs.linting_readme == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- run: "pip install rstcheck"
|
||||
@@ -327,7 +327,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- id: get-matrix
|
||||
@@ -414,7 +414,7 @@ jobs:
|
||||
sudo apt-get -qq install build-essential libffi-dev python3-dev \
|
||||
libxml2-dev libxslt-dev xmlsec1 zlib1g-dev libjpeg-dev libwebp-dev
|
||||
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
@@ -529,7 +529,7 @@ jobs:
|
||||
if: ${{ always() }}
|
||||
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
|
||||
- name: Upload SyTest logs
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: Sytest Logs - ${{ job.status }} - (${{ join(matrix.job.*, ', ') }})
|
||||
@@ -627,7 +627,7 @@ jobs:
|
||||
PGPASSWORD: postgres
|
||||
PGDATABASE: postgres
|
||||
- name: "Upload schema differences"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
if: ${{ failure() && !cancelled() && steps.run_tester_script.outcome == 'failure' }}
|
||||
with:
|
||||
name: Schema dumps
|
||||
@@ -669,7 +669,7 @@ jobs:
|
||||
- name: Prepare Complement's Prerequisites
|
||||
run: synapse/.ci/scripts/setup_complement_prerequisites.sh
|
||||
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
cache-dependency-path: complement/go.sum
|
||||
go-version-file: complement/go.mod
|
||||
|
||||
2
.github/workflows/triage_labelled.yml
vendored
2
.github/workflows/triage_labelled.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
if: >
|
||||
contains(github.event.issue.labels.*.name, 'X-Needs-Info')
|
||||
steps:
|
||||
- uses: actions/add-to-project@5b1a254a3546aef88e0a7724a77a623fa2e47c36 # main (v1.0.2 + 10 commits)
|
||||
- uses: actions/add-to-project@f5473ace9aeee8b97717b281e26980aa5097023f # main (v1.0.2 + 10 commits)
|
||||
id: add_project
|
||||
with:
|
||||
project-url: "https://github.com/orgs/matrix-org/projects/67"
|
||||
|
||||
4
.github/workflows/twisted_trunk.yml
vendored
4
.github/workflows/twisted_trunk.yml
vendored
@@ -138,7 +138,7 @@ jobs:
|
||||
if: ${{ always() }}
|
||||
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
|
||||
- name: Upload SyTest logs
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: Sytest Logs - ${{ job.status }} - (${{ join(matrix.*, ', ') }})
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
- name: Prepare Complement's Prerequisites
|
||||
run: synapse/.ci/scripts/setup_complement_prerequisites.sh
|
||||
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
|
||||
with:
|
||||
cache-dependency-path: complement/go.sum
|
||||
go-version-file: complement/go.mod
|
||||
|
||||
191
CHANGES.md
191
CHANGES.md
@@ -1,194 +1,3 @@
|
||||
# Synapse 1.130.0 (2025-05-20)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fix startup being blocked on creating a new index that was introduced in v1.130.0rc1. ([\#18439](https://github.com/element-hq/synapse/issues/18439))
|
||||
- Fix the ordering of local messages in rooms that were affected by [GHSA-v56r-hwv5-mxg6](https://github.com/advisories/GHSA-v56r-hwv5-mxg6). ([\#18447](https://github.com/element-hq/synapse/issues/18447))
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.130.0rc1 (2025-05-13)
|
||||
|
||||
### Features
|
||||
|
||||
- Add an Admin API endpoint `GET /_synapse/admin/v1/scheduled_tasks` to fetch scheduled tasks. ([\#18214](https://github.com/element-hq/synapse/issues/18214))
|
||||
- Add config option `user_directory.exclude_remote_users` which, when enabled, excludes remote users from user directory search results. ([\#18300](https://github.com/element-hq/synapse/issues/18300))
|
||||
- Add support for handling `GET /devices/` on workers. ([\#18355](https://github.com/element-hq/synapse/issues/18355))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fix a longstanding bug where Synapse would immediately retry a failing push endpoint when a new event is received, ignoring any backoff timers. ([\#18363](https://github.com/element-hq/synapse/issues/18363))
|
||||
- Pass leave from remote invite rejection down Sliding Sync. ([\#18375](https://github.com/element-hq/synapse/issues/18375))
|
||||
|
||||
### Updates to the Docker image
|
||||
|
||||
- In `configure_workers_and_start.py`, use the same absolute path of Python in the interpreter shebang, and invoke child Python processes with `sys.executable`. ([\#18291](https://github.com/element-hq/synapse/issues/18291))
|
||||
- Optimize the build of the workers image. ([\#18292](https://github.com/element-hq/synapse/issues/18292))
|
||||
- In `start_for_complement.sh`, replace some external program calls with shell builtins. ([\#18293](https://github.com/element-hq/synapse/issues/18293))
|
||||
- When generating container scripts from templates, don't add a leading newline so that their shebangs may be handled correctly. ([\#18295](https://github.com/element-hq/synapse/issues/18295))
|
||||
|
||||
### Improved Documentation
|
||||
|
||||
- Improve formatting of the README file. ([\#18218](https://github.com/element-hq/synapse/issues/18218))
|
||||
- Add documentation for configuring [Pocket ID](https://github.com/pocket-id/pocket-id) as an OIDC provider. ([\#18237](https://github.com/element-hq/synapse/issues/18237))
|
||||
- Fix typo in docs about the `push` config option. Contributed by @HarHarLinks. ([\#18320](https://github.com/element-hq/synapse/issues/18320))
|
||||
- Add `/_matrix/federation/v1/version` to list of federation endpoints that can be handled by workers. ([\#18377](https://github.com/element-hq/synapse/issues/18377))
|
||||
- Add an Admin API endpoint `GET /_synapse/admin/v1/scheduled_tasks` to fetch scheduled tasks. ([\#18384](https://github.com/element-hq/synapse/issues/18384))
|
||||
|
||||
### Internal Changes
|
||||
|
||||
- Return specific error code when adding an email address / phone number to account is not supported ([MSC4178](https://github.com/matrix-org/matrix-spec-proposals/pull/4178)). ([\#17578](https://github.com/element-hq/synapse/issues/17578))
|
||||
- Stop auto-provisionning missing users & devices when delegating auth to Matrix Authentication Service. Requires MAS 0.13.0 or later. ([\#18181](https://github.com/element-hq/synapse/issues/18181))
|
||||
- Apply file hashing and existing quarantines to media downloaded for URL previews. ([\#18297](https://github.com/element-hq/synapse/issues/18297))
|
||||
- Allow a few admin APIs used by matrix-authentication-service to run on workers. ([\#18313](https://github.com/element-hq/synapse/issues/18313))
|
||||
- Apply `should_drop_federated_event` to federation invites. ([\#18330](https://github.com/element-hq/synapse/issues/18330))
|
||||
- Allow `/rooms/` admin API to be run on workers. ([\#18360](https://github.com/element-hq/synapse/issues/18360))
|
||||
- Minor performance improvements to the notifier. ([\#18367](https://github.com/element-hq/synapse/issues/18367))
|
||||
- Slight performance increase when using the ratelimiter. ([\#18369](https://github.com/element-hq/synapse/issues/18369))
|
||||
- Don't validate the `at_hash` (access token hash) field in OIDC ID Tokens if we don't end up actually using the OIDC Access Token. ([\#18374](https://github.com/element-hq/synapse/issues/18374), [\#18385](https://github.com/element-hq/synapse/issues/18385))
|
||||
- Fixed test failures when using authlib 1.5.2. ([\#18390](https://github.com/element-hq/synapse/issues/18390))
|
||||
- Refactor [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) Simplified Sliding Sync room list tests to cover both new and fallback logic paths. ([\#18399](https://github.com/element-hq/synapse/issues/18399))
|
||||
|
||||
|
||||
|
||||
### Updates to locked dependencies
|
||||
|
||||
* Bump actions/add-to-project from 280af8ae1f83a494cfad2cb10f02f6d13529caa9 to 5b1a254a3546aef88e0a7724a77a623fa2e47c36. ([\#18365](https://github.com/element-hq/synapse/issues/18365))
|
||||
* Bump actions/download-artifact from 4.2.1 to 4.3.0. ([\#18364](https://github.com/element-hq/synapse/issues/18364))
|
||||
* Bump actions/setup-go from 5.4.0 to 5.5.0. ([\#18426](https://github.com/element-hq/synapse/issues/18426))
|
||||
* Bump anyhow from 1.0.97 to 1.0.98. ([\#18336](https://github.com/element-hq/synapse/issues/18336))
|
||||
* Bump packaging from 24.2 to 25.0. ([\#18393](https://github.com/element-hq/synapse/issues/18393))
|
||||
* Bump pillow from 11.1.0 to 11.2.1. ([\#18429](https://github.com/element-hq/synapse/issues/18429))
|
||||
* Bump pydantic from 2.10.3 to 2.11.4. ([\#18394](https://github.com/element-hq/synapse/issues/18394))
|
||||
* Bump pyo3-log from 0.12.2 to 0.12.3. ([\#18317](https://github.com/element-hq/synapse/issues/18317))
|
||||
* Bump pyopenssl from 24.3.0 to 25.0.0. ([\#18315](https://github.com/element-hq/synapse/issues/18315))
|
||||
* Bump sha2 from 0.10.8 to 0.10.9. ([\#18395](https://github.com/element-hq/synapse/issues/18395))
|
||||
* Bump sigstore/cosign-installer from 3.8.1 to 3.8.2. ([\#18366](https://github.com/element-hq/synapse/issues/18366))
|
||||
* Bump softprops/action-gh-release from 1 to 2. ([\#18264](https://github.com/element-hq/synapse/issues/18264))
|
||||
* Bump stefanzweifel/git-auto-commit-action from 5.1.0 to 5.2.0. ([\#18354](https://github.com/element-hq/synapse/issues/18354))
|
||||
* Bump txredisapi from 1.4.10 to 1.4.11. ([\#18392](https://github.com/element-hq/synapse/issues/18392))
|
||||
* Bump types-jsonschema from 4.23.0.20240813 to 4.23.0.20241208. ([\#18305](https://github.com/element-hq/synapse/issues/18305))
|
||||
* Bump types-psycopg2 from 2.9.21.20250121 to 2.9.21.20250318. ([\#18316](https://github.com/element-hq/synapse/issues/18316))
|
||||
|
||||
# Synapse 1.129.0 (2025-05-06)
|
||||
|
||||
No significant changes since 1.129.0rc2.
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.129.0rc2 (2025-04-30)
|
||||
|
||||
Synapse 1.129.0rc1 was never formally released due to regressions discovered during the release process. 1.129.0rc2 fixes those regressions by reverting the affected PRs.
|
||||
|
||||
### Internal Changes
|
||||
|
||||
- Revert the slow background update introduced by [\#18068](https://github.com/element-hq/synapse/issues/18068) in v1.128.0. ([\#18372](https://github.com/element-hq/synapse/issues/18372))
|
||||
- Revert "Add total event, unencrypted message, and e2ee event counts to stats reporting", added in v1.129.0rc1. ([\#18373](https://github.com/element-hq/synapse/issues/18373))
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.129.0rc1 (2025-04-15)
|
||||
|
||||
### Features
|
||||
|
||||
- Add `passthrough_authorization_parameters` in OIDC configuration to allow passing parameters to the authorization grant URL. ([\#18232](https://github.com/element-hq/synapse/issues/18232))
|
||||
- Add `total_event_count`, `total_message_count`, and `total_e2ee_event_count` fields to the homeserver usage statistics. ([\#18260](https://github.com/element-hq/synapse/issues/18260))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fix `force_tracing_for_users` config when using delegated auth. ([\#18334](https://github.com/element-hq/synapse/issues/18334))
|
||||
- Fix the token introspection cache logging access tokens when MAS integration is in use. ([\#18335](https://github.com/element-hq/synapse/issues/18335))
|
||||
- Stop caching introspection failures when delegating auth to MAS. ([\#18339](https://github.com/element-hq/synapse/issues/18339))
|
||||
- Fix `ExternalIDReuse` exception after migrating to MAS on workers with a high traffic. ([\#18342](https://github.com/element-hq/synapse/issues/18342))
|
||||
- Fix minor performance regression caused by tracking of room participation. Regressed in v1.128.0. ([\#18345](https://github.com/element-hq/synapse/issues/18345))
|
||||
|
||||
### Updates to the Docker image
|
||||
|
||||
- Optimize the build of the complement-synapse image. ([\#18294](https://github.com/element-hq/synapse/issues/18294))
|
||||
|
||||
### Internal Changes
|
||||
|
||||
- Disable statement timeout during room purge. ([\#18133](https://github.com/element-hq/synapse/issues/18133))
|
||||
- Add cache to storage functions used to auth requests when using delegated auth. ([\#18337](https://github.com/element-hq/synapse/issues/18337))
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.128.0 (2025-04-08)
|
||||
|
||||
No significant changes since 1.128.0rc1.
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.128.0rc1 (2025-04-01)
|
||||
|
||||
### Features
|
||||
|
||||
- Add an access token introspection cache to make Matrix Authentication Service integration ([MSC3861](https://github.com/matrix-org/matrix-doc/pull/3861)) more efficient. ([\#18231](https://github.com/element-hq/synapse/issues/18231))
|
||||
- Add background job to clear unreferenced state groups. ([\#18254](https://github.com/element-hq/synapse/issues/18254))
|
||||
- Hashes of media files are now tracked by Synapse. Media quarantines will now apply to all files with the same hash. ([\#18277](https://github.com/element-hq/synapse/issues/18277), [\#18302](https://github.com/element-hq/synapse/issues/18302), [\#18296](https://github.com/element-hq/synapse/issues/18296))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Add index to sliding sync ([MSC4186](https://github.com/matrix-org/matrix-doc/pull/4186)) membership snapshot table, to fix a performance issue. ([\#18074](https://github.com/element-hq/synapse/issues/18074))
|
||||
|
||||
### Updates to the Docker image
|
||||
|
||||
- Specify the architecture of installed packages via an APT config option, which is more reliable than appending package names with `:{arch}`. ([\#18271](https://github.com/element-hq/synapse/issues/18271))
|
||||
- Always specify base image debian versions with a build argument. ([\#18272](https://github.com/element-hq/synapse/issues/18272))
|
||||
- Allow passing arguments to `start_for_complement.sh` (to be sent to `configure_workers_and_start.py`). ([\#18273](https://github.com/element-hq/synapse/issues/18273))
|
||||
- Make some improvements to the `prefix-log` script in the workers image. ([\#18274](https://github.com/element-hq/synapse/issues/18274))
|
||||
- Use `uv pip` to install `supervisor` in the worker image. ([\#18275](https://github.com/element-hq/synapse/issues/18275))
|
||||
- Avoid needing to download & use `rsync` in a build layer. ([\#18287](https://github.com/element-hq/synapse/issues/18287))
|
||||
|
||||
### Improved Documentation
|
||||
|
||||
- Fix how to obtain access token and change naming from riot to element ([\#18225](https://github.com/element-hq/synapse/issues/18225))
|
||||
- Correct a small typo in the SSO mapping providers documentation. ([\#18276](https://github.com/element-hq/synapse/issues/18276))
|
||||
- Add docs for how to clear out the Poetry wheel cache. ([\#18283](https://github.com/element-hq/synapse/issues/18283))
|
||||
|
||||
### Internal Changes
|
||||
|
||||
- Add a column `participant` to `room_memberships` table. ([\#18068](https://github.com/element-hq/synapse/issues/18068))
|
||||
- Update Poetry to 2.1.1, including updating the lock file version. ([\#18251](https://github.com/element-hq/synapse/issues/18251))
|
||||
- Pin GitHub Actions dependencies by commit hash. ([\#18255](https://github.com/element-hq/synapse/issues/18255))
|
||||
- Add DB delta to remove the old state group deletion job. ([\#18284](https://github.com/element-hq/synapse/issues/18284))
|
||||
|
||||
|
||||
|
||||
### Updates to locked dependencies
|
||||
|
||||
* Bump actions/add-to-project from f5473ace9aeee8b97717b281e26980aa5097023f to 280af8ae1f83a494cfad2cb10f02f6d13529caa9. ([\#18303](https://github.com/element-hq/synapse/issues/18303))
|
||||
* Bump actions/cache from 4.2.2 to 4.2.3. ([\#18266](https://github.com/element-hq/synapse/issues/18266))
|
||||
* Bump actions/download-artifact from 4.2.0 to 4.2.1. ([\#18268](https://github.com/element-hq/synapse/issues/18268))
|
||||
* Bump actions/setup-python from 5.4.0 to 5.5.0. ([\#18298](https://github.com/element-hq/synapse/issues/18298))
|
||||
* Bump actions/upload-artifact from 4.6.1 to 4.6.2. ([\#18304](https://github.com/element-hq/synapse/issues/18304))
|
||||
* Bump authlib from 1.4.1 to 1.5.1. ([\#18306](https://github.com/element-hq/synapse/issues/18306))
|
||||
* Bump dawidd6/action-download-artifact from 8 to 9. ([\#18204](https://github.com/element-hq/synapse/issues/18204))
|
||||
* Bump jinja2 from 3.1.5 to 3.1.6. ([\#18223](https://github.com/element-hq/synapse/issues/18223))
|
||||
* Bump log from 0.4.26 to 0.4.27. ([\#18267](https://github.com/element-hq/synapse/issues/18267))
|
||||
* Bump phonenumbers from 8.13.50 to 9.0.2. ([\#18299](https://github.com/element-hq/synapse/issues/18299))
|
||||
* Bump pygithub from 2.5.0 to 2.6.1. ([\#18243](https://github.com/element-hq/synapse/issues/18243))
|
||||
* Bump pyo3-log from 0.12.1 to 0.12.2. ([\#18269](https://github.com/element-hq/synapse/issues/18269))
|
||||
|
||||
# Synapse 1.127.1 (2025-03-26)
|
||||
|
||||
## Security
|
||||
- Fix [CVE-2025-30355](https://www.cve.org/CVERecord?id=CVE-2025-30355) / [GHSA-v56r-hwv5-mxg6](https://github.com/element-hq/synapse/security/advisories/GHSA-v56r-hwv5-mxg6). **High severity vulnerability affecting federation. The vulnerability has been exploited in the wild.**
|
||||
|
||||
|
||||
|
||||
# Synapse 1.127.0 (2025-03-25)
|
||||
|
||||
No significant changes since 1.127.0rc1.
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.127.0rc1 (2025-03-18)
|
||||
|
||||
### Features
|
||||
|
||||
44
Cargo.lock
generated
44
Cargo.lock
generated
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@@ -223,9 +223,9 @@ checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -277,9 +277,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.24.2"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
|
||||
checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -296,9 +296,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.24.2"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
|
||||
checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
@@ -306,9 +306,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.24.2"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
|
||||
checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
@@ -316,9 +316,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-log"
|
||||
version = "0.12.4"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45192e5e4a4d2505587e27806c7b710c231c40c56f3bfc19535d0bb25df52264"
|
||||
checksum = "be5bb22b77965a7b5394e9aae9897a0607b51df5167561ffc3b02643b4200bc7"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"log",
|
||||
@@ -327,9 +327,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.24.2"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
|
||||
checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
@@ -339,9 +339,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.24.2"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
|
||||
checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -352,9 +352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pythonize"
|
||||
version = "0.24.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5bcac0d0b71821f0d69e42654f1e15e5c94b85196446c4de9588951a2117e7b"
|
||||
checksum = "91a6ee7a084f913f98d70cdc3ebec07e852b735ae3059a1500db2661265da9ff"
|
||||
dependencies = [
|
||||
"pyo3",
|
||||
"serde",
|
||||
@@ -480,9 +480,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@@ -532,9 +532,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.2"
|
||||
version = "0.12.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
|
||||
checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
|
||||
12
README.rst
12
README.rst
@@ -253,17 +253,15 @@ Alongside all that, join our developer community on Matrix:
|
||||
Copyright and Licensing
|
||||
=======================
|
||||
|
||||
| Copyright 2014-2017 OpenMarket Ltd
|
||||
| Copyright 2017 Vector Creations Ltd
|
||||
| Copyright 2017-2025 New Vector Ltd
|
||||
|
|
||||
Copyright 2014-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.
|
||||
|
||||
|
||||
|
||||
1
changelog.d/18068.misc
Normal file
1
changelog.d/18068.misc
Normal file
@@ -0,0 +1 @@
|
||||
Add a column `participant` to `room_memberships` table.
|
||||
1
changelog.d/18074.bugfix
Normal file
1
changelog.d/18074.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Add index to sliding sync membership snapshot table, to fix a performance issue.
|
||||
@@ -1 +0,0 @@
|
||||
Add `msc4263_limit_key_queries_to_users_who_share_rooms` config option as per [MSC4263](https://github.com/matrix-org/matrix-spec-proposals/pull/4263).
|
||||
1
changelog.d/18251.misc
Normal file
1
changelog.d/18251.misc
Normal file
@@ -0,0 +1 @@
|
||||
Update Poetry to 2.1.1, including updating the lock file version.
|
||||
@@ -1 +0,0 @@
|
||||
Mark dehydrated devices in the [List All User Devices Admin API](https://element-hq.github.io/synapse/latest/admin_api/user_admin_api.html#list-all-devices).
|
||||
1
changelog.d/18254.feature
Normal file
1
changelog.d/18254.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add background job to clear unreferenced state groups.
|
||||
1
changelog.d/18255.misc
Normal file
1
changelog.d/18255.misc
Normal file
@@ -0,0 +1 @@
|
||||
Pin GitHub Actions dependencies by commit hash.
|
||||
@@ -1 +0,0 @@
|
||||
Add option to allow registrations that begin with `_`. Contributed by `_` (@hex5f).
|
||||
@@ -1 +0,0 @@
|
||||
Update `room_list_publication_rules` docs to consider defaults that changed in v1.126.0. Contributed by @HarHarLinks.
|
||||
@@ -1 +0,0 @@
|
||||
Include room ID in room deletion status response.
|
||||
@@ -1 +0,0 @@
|
||||
Fix a memory leak in `_NotifierUserStream`.
|
||||
@@ -1 +0,0 @@
|
||||
Add support for calling Policy Servers ([MSC4284](https://github.com/matrix-org/matrix-spec-proposals/pull/4284)) to mark events as spam.
|
||||
@@ -1 +0,0 @@
|
||||
Prevent race-condition in `_maybe_retry_device_resync` entrance.
|
||||
@@ -1 +0,0 @@
|
||||
Fix a couple type annotations in the `RootConfig`/`Config`.
|
||||
@@ -1 +0,0 @@
|
||||
Explicitly enable PyPy builds in `cibuildwheel`s config to avoid it being disabled on a future upgrade to `cibuildwheel` v3.
|
||||
@@ -1 +0,0 @@
|
||||
Update the PR review template to remove an erroneous line break from the final bullet point.
|
||||
@@ -1 +0,0 @@
|
||||
Explain why we `flush_buffer()` for Python `print(...)` output.
|
||||
@@ -1 +0,0 @@
|
||||
Fix the `tests.handlers.test_worker_lock.WorkerLockTestCase.test_lock_contention` test which could spuriously time out on RISC-V architectures due to performance differences.
|
||||
@@ -1 +0,0 @@
|
||||
Fix admin redaction endpoint not redacting encrypted messages.
|
||||
@@ -1 +0,0 @@
|
||||
Add lint to ensure we don't add a `CREATE/DROP INDEX` in a schema delta.
|
||||
@@ -1 +0,0 @@
|
||||
Add advice for upgrading between major PostgreSQL versions to the database documentation.
|
||||
@@ -1 +0,0 @@
|
||||
Bump ruff from 0.7.3 to 0.11.10.
|
||||
@@ -1 +0,0 @@
|
||||
Allow checking only for the existence of a field in an SSO provider's response, rather than requiring the value(s) to check.
|
||||
@@ -1 +0,0 @@
|
||||
Bump tornado from 6.4.2 to 6.5.0.
|
||||
@@ -1 +0,0 @@
|
||||
Bump pyo3 from 0.23.5 to 0.24.2.
|
||||
@@ -1 +0,0 @@
|
||||
Add unit tests for homeserver usage statistics.
|
||||
@@ -1 +0,0 @@
|
||||
Don't move invited users to new room when shutting down room.
|
||||
@@ -1 +0,0 @@
|
||||
Migrate `assertEquals` (deprecated) to `assertEqual` in the tests. Contributed by @emmanuel-ferdman.
|
||||
53
debian/changelog
vendored
53
debian/changelog
vendored
@@ -1,57 +1,8 @@
|
||||
matrix-synapse-py3 (1.130.0) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.130.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 20 May 2025 08:34:13 -0600
|
||||
|
||||
matrix-synapse-py3 (1.130.0~rc1) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.130.0rc1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 13 May 2025 10:44:04 +0100
|
||||
|
||||
matrix-synapse-py3 (1.129.0) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.129.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 06 May 2025 12:22:11 +0100
|
||||
|
||||
matrix-synapse-py3 (1.129.0~rc2) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.129.0rc2.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 30 Apr 2025 13:13:16 +0000
|
||||
|
||||
matrix-synapse-py3 (1.129.0~rc1) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.129.0rc1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 15 Apr 2025 10:47:43 -0600
|
||||
|
||||
matrix-synapse-py3 (1.128.0) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.128.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 08 Apr 2025 14:09:54 +0100
|
||||
|
||||
matrix-synapse-py3 (1.128.0~rc1) stable; urgency=medium
|
||||
matrix-synapse-py3 (1.127.0~rc1+nmu1) UNRELEASED; urgency=medium
|
||||
|
||||
* Update Poetry to 2.1.1.
|
||||
* New synapse release 1.128.0rc1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 01 Apr 2025 14:35:33 +0000
|
||||
|
||||
matrix-synapse-py3 (1.127.1) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.127.1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 26 Mar 2025 21:07:31 +0000
|
||||
|
||||
matrix-synapse-py3 (1.127.0) stable; urgency=medium
|
||||
|
||||
* New Synapse release 1.127.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 25 Mar 2025 12:04:15 +0000
|
||||
-- Synapse Packaging team <packages@matrix.org> Wed, 19 Mar 2025 17:38:49 +0000
|
||||
|
||||
matrix-synapse-py3 (1.127.0~rc1) stable; urgency=medium
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ RUN \
|
||||
--mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update -qq && \
|
||||
apt-get install -y --no-install-recommends rsync && \
|
||||
apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances --no-pre-depends \
|
||||
curl \
|
||||
gosu \
|
||||
@@ -147,10 +148,14 @@ RUN \
|
||||
for arch in arm64 amd64; do \
|
||||
mkdir -p /tmp/debs-${arch} && \
|
||||
cd /tmp/debs-${arch} && \
|
||||
apt-get -o APT::Architecture="${arch}" download $(cat /tmp/pkg-list); \
|
||||
apt-get download $(sed "s/$/:${arch}/" /tmp/pkg-list); \
|
||||
done
|
||||
|
||||
# Extract the debs for each architecture
|
||||
# On the runtime image, /lib is a symlink to /usr/lib, so we need to copy the
|
||||
# libraries to the right place, else the `COPY` won't work.
|
||||
# On amd64, we'll also have a /lib64 folder with ld-linux-x86-64.so.2, which is
|
||||
# already present in the runtime image.
|
||||
RUN \
|
||||
for arch in arm64 amd64; do \
|
||||
mkdir -p /install-${arch}/var/lib/dpkg/status.d/ && \
|
||||
@@ -160,6 +165,8 @@ RUN \
|
||||
dpkg --ctrl-tarfile $deb | tar -Ox ./control > /install-${arch}/var/lib/dpkg/status.d/${package_name}; \
|
||||
dpkg --extract $deb /install-${arch}; \
|
||||
done; \
|
||||
rsync -avr /install-${arch}/lib/ /install-${arch}/usr/lib; \
|
||||
rm -rf /install-${arch}/lib /install-${arch}/lib64; \
|
||||
done
|
||||
|
||||
|
||||
@@ -176,14 +183,7 @@ LABEL org.opencontainers.image.documentation='https://github.com/element-hq/syna
|
||||
LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
|
||||
LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later'
|
||||
|
||||
# On the runtime image, /lib is a symlink to /usr/lib, so we need to copy the
|
||||
# libraries to the right place, else the `COPY` won't work.
|
||||
# On amd64, we'll also have a /lib64 folder with ld-linux-x86-64.so.2, which is
|
||||
# already present in the runtime image.
|
||||
COPY --from=runtime-deps /install-${TARGETARCH}/lib /usr/lib
|
||||
COPY --from=runtime-deps /install-${TARGETARCH}/etc /etc
|
||||
COPY --from=runtime-deps /install-${TARGETARCH}/usr /usr
|
||||
COPY --from=runtime-deps /install-${TARGETARCH}/var /var
|
||||
COPY --from=runtime-deps /install-${TARGETARCH} /
|
||||
COPY --from=builder /install /usr/local
|
||||
COPY ./docker/start.py /start.py
|
||||
COPY ./docker/conf /conf
|
||||
|
||||
@@ -2,38 +2,18 @@
|
||||
|
||||
ARG SYNAPSE_VERSION=latest
|
||||
ARG FROM=matrixdotorg/synapse:$SYNAPSE_VERSION
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
ARG PYTHON_VERSION=3.12
|
||||
|
||||
# first of all, we create a base image with dependencies which we can copy into the
|
||||
# first of all, we create a base image with an nginx which we can copy into the
|
||||
# target image. For repeated rebuilds, this is much faster than apt installing
|
||||
# each time.
|
||||
|
||||
FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS deps_base
|
||||
|
||||
# Tell apt to keep downloaded package files, as we're using cache mounts.
|
||||
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||
|
||||
FROM docker.io/library/debian:bookworm-slim AS deps_base
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update -qq && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -yqq --no-install-recommends \
|
||||
nginx-light
|
||||
|
||||
RUN \
|
||||
# remove default page
|
||||
rm /etc/nginx/sites-enabled/default && \
|
||||
# have nginx log to stderr/out
|
||||
ln -sf /dev/stdout /var/log/nginx/access.log && \
|
||||
ln -sf /dev/stderr /var/log/nginx/error.log
|
||||
|
||||
# --link-mode=copy silences a warning as uv isn't able to do hardlinks between its cache
|
||||
# (mounted as --mount=type=cache) and the target directory.
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --link-mode=copy --prefix="/uv/usr/local" supervisor~=4.2
|
||||
|
||||
RUN mkdir -p /uv/etc/supervisor/conf.d
|
||||
redis-server nginx-light
|
||||
|
||||
# Similarly, a base to copy the redis server from.
|
||||
#
|
||||
@@ -41,21 +21,31 @@ FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS deps_base
|
||||
# which makes it much easier to copy (but we need to make sure we use an image
|
||||
# based on the same debian version as the synapse image, to make sure we get
|
||||
# the expected version of libc.
|
||||
FROM docker.io/library/redis:7-${DEBIAN_VERSION} AS redis_base
|
||||
FROM docker.io/library/redis:7-bookworm AS redis_base
|
||||
|
||||
# now build the final image, based on the the regular Synapse docker image
|
||||
FROM $FROM
|
||||
|
||||
# Copy over dependencies
|
||||
# Install supervisord with pip instead of apt, to avoid installing a second
|
||||
# copy of python.
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
pip install supervisor~=4.2
|
||||
RUN mkdir -p /etc/supervisor/conf.d
|
||||
|
||||
# Copy over redis and nginx
|
||||
COPY --from=redis_base /usr/local/bin/redis-server /usr/local/bin
|
||||
COPY --from=deps_base /uv /
|
||||
|
||||
COPY --from=deps_base /usr/sbin/nginx /usr/sbin
|
||||
COPY --from=deps_base /usr/share/nginx /usr/share/nginx
|
||||
COPY --from=deps_base /usr/lib/nginx /usr/lib/nginx
|
||||
COPY --from=deps_base /etc/nginx /etc/nginx
|
||||
COPY --from=deps_base /var/log/nginx /var/log/nginx
|
||||
# chown to allow non-root user to write to http-*-temp-path dirs
|
||||
COPY --from=deps_base --chown=www-data:root /var/lib/nginx /var/lib/nginx
|
||||
RUN rm /etc/nginx/sites-enabled/default
|
||||
RUN mkdir /var/log/nginx /var/lib/nginx
|
||||
RUN chown www-data /var/lib/nginx
|
||||
|
||||
# have nginx log to stderr/out
|
||||
RUN ln -sf /dev/stdout /var/log/nginx/access.log
|
||||
RUN ln -sf /dev/stderr /var/log/nginx/error.log
|
||||
|
||||
# Copy Synapse worker, nginx and supervisord configuration template files
|
||||
COPY ./docker/conf-workers/* /conf/
|
||||
@@ -74,4 +64,4 @@ FROM $FROM
|
||||
# Replace the healthcheck with one which checks *all* the workers. The script
|
||||
# is generated by configure_workers_and_start.py.
|
||||
HEALTHCHECK --start-period=5s --interval=15s --timeout=5s \
|
||||
CMD ["/healthcheck.sh"]
|
||||
CMD /bin/sh /healthcheck.sh
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
ARG SYNAPSE_VERSION=latest
|
||||
# This is an intermediate image, to be built locally (not pulled from a registry).
|
||||
ARG FROM=matrixdotorg/synapse-workers:$SYNAPSE_VERSION
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
|
||||
FROM docker.io/library/postgres:13-${DEBIAN_VERSION} AS postgres_base
|
||||
|
||||
FROM $FROM
|
||||
# First of all, we copy postgres server from the official postgres image,
|
||||
@@ -23,9 +20,9 @@ FROM $FROM
|
||||
# the same debian version as Synapse's docker image (so the versions of the
|
||||
# shared libraries match).
|
||||
RUN adduser --system --uid 999 postgres --home /var/lib/postgresql
|
||||
COPY --from=postgres_base /usr/lib/postgresql /usr/lib/postgresql
|
||||
COPY --from=postgres_base /usr/share/postgresql /usr/share/postgresql
|
||||
COPY --from=postgres_base --chown=postgres /var/run/postgresql /var/run/postgresql
|
||||
COPY --from=docker.io/library/postgres:13-bookworm /usr/lib/postgresql /usr/lib/postgresql
|
||||
COPY --from=docker.io/library/postgres:13-bookworm /usr/share/postgresql /usr/share/postgresql
|
||||
RUN mkdir /var/run/postgresql && chown postgres /var/run/postgresql
|
||||
ENV PATH="${PATH}:/usr/lib/postgresql/13/bin"
|
||||
ENV PGDATA=/var/lib/postgresql/data
|
||||
|
||||
@@ -58,4 +55,4 @@ ENTRYPOINT ["/start_for_complement.sh"]
|
||||
|
||||
# Update the healthcheck to have a shorter check interval
|
||||
HEALTHCHECK --start-period=5s --interval=1s --timeout=1s \
|
||||
CMD ["/healthcheck.sh"]
|
||||
CMD /bin/sh /healthcheck.sh
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
set -e
|
||||
|
||||
echo "Complement Synapse launcher"
|
||||
echo " Args: $*"
|
||||
echo " Args: $@"
|
||||
echo " Env: SYNAPSE_COMPLEMENT_DATABASE=$SYNAPSE_COMPLEMENT_DATABASE SYNAPSE_COMPLEMENT_USE_WORKERS=$SYNAPSE_COMPLEMENT_USE_WORKERS SYNAPSE_COMPLEMENT_USE_ASYNCIO_REACTOR=$SYNAPSE_COMPLEMENT_USE_ASYNCIO_REACTOR"
|
||||
|
||||
function log {
|
||||
d=$(printf '%(%Y-%m-%d %H:%M:%S)T,%.3s\n' ${EPOCHREALTIME/./ })
|
||||
echo "$d $*"
|
||||
d=$(date +"%Y-%m-%d %H:%M:%S,%3N")
|
||||
echo "$d $@"
|
||||
}
|
||||
|
||||
# Set the server name of the homeserver
|
||||
@@ -103,11 +103,12 @@ fi
|
||||
# Note that both the key and certificate are in PEM format (not DER).
|
||||
|
||||
# First generate a configuration file to set up a Subject Alternative Name.
|
||||
echo "\
|
||||
cat > /conf/server.tls.conf <<EOF
|
||||
.include /etc/ssl/openssl.cnf
|
||||
|
||||
[SAN]
|
||||
subjectAltName=DNS:${SERVER_NAME}" > /conf/server.tls.conf
|
||||
subjectAltName=DNS:${SERVER_NAME}
|
||||
EOF
|
||||
|
||||
# Generate an RSA key
|
||||
openssl genrsa -out /conf/server.tls.key 2048
|
||||
@@ -122,12 +123,12 @@ openssl x509 -req -in /conf/server.tls.csr \
|
||||
-out /conf/server.tls.crt -extfile /conf/server.tls.conf -extensions SAN
|
||||
|
||||
# Assert that we have a Subject Alternative Name in the certificate.
|
||||
# (the test will exit with 1 here if there isn't a SAN in the certificate.)
|
||||
[[ $(openssl x509 -in /conf/server.tls.crt -noout -text) == *DNS:* ]]
|
||||
# (grep will exit with 1 here if there isn't a SAN in the certificate.)
|
||||
openssl x509 -in /conf/server.tls.crt -noout -text | grep DNS:
|
||||
|
||||
export SYNAPSE_TLS_CERT=/conf/server.tls.crt
|
||||
export SYNAPSE_TLS_KEY=/conf/server.tls.key
|
||||
|
||||
# Run the script that writes the necessary config files and starts supervisord, which in turn
|
||||
# starts everything else
|
||||
exec /configure_workers_and_start.py "$@"
|
||||
exec /configure_workers_and_start.py
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/local/bin/python
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
@@ -202,7 +202,6 @@ WORKERS_CONFIG: Dict[str, Dict[str, Any]] = {
|
||||
"app": "synapse.app.generic_worker",
|
||||
"listener_resources": ["federation"],
|
||||
"endpoint_patterns": [
|
||||
"^/_matrix/federation/v1/version$",
|
||||
"^/_matrix/federation/(v1|v2)/event/",
|
||||
"^/_matrix/federation/(v1|v2)/state/",
|
||||
"^/_matrix/federation/(v1|v2)/state_ids/",
|
||||
@@ -352,11 +351,6 @@ def error(txt: str) -> NoReturn:
|
||||
|
||||
|
||||
def flush_buffers() -> None:
|
||||
"""
|
||||
Python's `print()` buffers output by default, typically waiting until ~8KB
|
||||
accumulates. This method can be used to flush the buffers so we can see the output
|
||||
of any print statements so far.
|
||||
"""
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
@@ -382,11 +376,9 @@ def convert(src: str, dst: str, **template_vars: object) -> None:
|
||||
#
|
||||
# We use append mode in case the files have already been written to by something else
|
||||
# (for instance, as part of the instructions in a dockerfile).
|
||||
exists = os.path.isfile(dst)
|
||||
with open(dst, "a") as outfile:
|
||||
# In case the existing file doesn't end with a newline
|
||||
if exists:
|
||||
outfile.write("\n")
|
||||
outfile.write("\n")
|
||||
|
||||
outfile.write(rendered)
|
||||
|
||||
@@ -612,7 +604,7 @@ def generate_base_homeserver_config() -> None:
|
||||
# start.py already does this for us, so just call that.
|
||||
# note that this script is copied in in the official, monolith dockerfile
|
||||
os.environ["SYNAPSE_HTTP_PORT"] = str(MAIN_PROCESS_HTTP_LISTENER_PORT)
|
||||
subprocess.run([sys.executable, "/start.py", "migrate_config"], check=True)
|
||||
subprocess.run(["/usr/local/bin/python", "/start.py", "migrate_config"], check=True)
|
||||
|
||||
|
||||
def parse_worker_types(
|
||||
@@ -1006,7 +998,6 @@ def generate_worker_files(
|
||||
"/healthcheck.sh",
|
||||
healthcheck_urls=healthcheck_urls,
|
||||
)
|
||||
os.chmod("/healthcheck.sh", 0o755)
|
||||
|
||||
# Ensure the logging directory exists
|
||||
log_dir = data_dir + "/logs"
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
# '-W interactive' is a `mawk` extension which disables buffering on stdout and sets line-buffered reads on
|
||||
# stdin. The effect is that the output is flushed after each line, rather than being batched, which helps reduce
|
||||
# confusion due to to interleaving of the different processes.
|
||||
prefixer() {
|
||||
mawk -W interactive '{printf("%s | %s\n", ENVIRON["SUPERVISOR_PROCESS_NAME"], $0); fflush() }'
|
||||
}
|
||||
exec 1> >(prefixer)
|
||||
exec 2> >(prefixer >&2)
|
||||
exec 1> >(awk -W interactive '{print "'"${SUPERVISOR_PROCESS_NAME}"' | "$0 }' >&1)
|
||||
exec 2> >(awk -W interactive '{print "'"${SUPERVISOR_PROCESS_NAME}"' | "$0 }' >&2)
|
||||
exec "$@"
|
||||
|
||||
@@ -22,11 +22,6 @@ def error(txt: str) -> NoReturn:
|
||||
|
||||
|
||||
def flush_buffers() -> None:
|
||||
"""
|
||||
Python's `print()` buffers output by default, typically waiting until ~8KB
|
||||
accumulates. This method can be used to flush the buffers so we can see the output
|
||||
of any print statements so far.
|
||||
"""
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
@@ -46,14 +46,6 @@ to any local media, and any locally-cached copies of remote media.
|
||||
|
||||
The media file itself (and any thumbnails) is not deleted from the server.
|
||||
|
||||
Since Synapse 1.128.0, hashes of uploaded media are tracked. If this media
|
||||
is quarantined, Synapse will:
|
||||
|
||||
- Quarantine any media with a matching hash that has already been uploaded.
|
||||
- Quarantine any future media.
|
||||
- Quarantine any existing cached remote media.
|
||||
- Quarantine any future remote media.
|
||||
|
||||
## Quarantining media by ID
|
||||
|
||||
This API quarantines a single piece of local or remote media.
|
||||
|
||||
@@ -794,7 +794,6 @@ A response body like the following is returned:
|
||||
"results": [
|
||||
{
|
||||
"delete_id": "delete_id1",
|
||||
"room_id": "!roomid:example.com",
|
||||
"status": "failed",
|
||||
"error": "error message",
|
||||
"shutdown_room": {
|
||||
@@ -805,7 +804,6 @@ A response body like the following is returned:
|
||||
}
|
||||
}, {
|
||||
"delete_id": "delete_id2",
|
||||
"room_id": "!roomid:example.com",
|
||||
"status": "purging",
|
||||
"shutdown_room": {
|
||||
"kicked_users": [
|
||||
@@ -844,8 +842,6 @@ A response body like the following is returned:
|
||||
```json
|
||||
{
|
||||
"status": "purging",
|
||||
"delete_id": "bHkCNQpHqOaFhPtK",
|
||||
"room_id": "!roomid:example.com",
|
||||
"shutdown_room": {
|
||||
"kicked_users": [
|
||||
"@foobar:example.com"
|
||||
@@ -873,8 +869,7 @@ The following fields are returned in the JSON response body:
|
||||
- `results` - An array of objects, each containing information about one task.
|
||||
This field is omitted from the result when you query by `delete_id`.
|
||||
Task objects contain the following fields:
|
||||
- `delete_id` - The ID for this purge
|
||||
- `room_id` - The ID of the room being deleted
|
||||
- `delete_id` - The ID for this purge if you query by `room_id`.
|
||||
- `status` - The status will be one of:
|
||||
- `shutting_down` - The process is removing users from the room.
|
||||
- `purging` - The process is purging the room and event data from database.
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Show scheduled tasks
|
||||
|
||||
This API returns information about scheduled tasks.
|
||||
|
||||
To use it, you will need to authenticate by providing an `access_token`
|
||||
for a server admin: see [Admin API](../usage/administration/admin_api/).
|
||||
|
||||
The api is:
|
||||
```
|
||||
GET /_synapse/admin/v1/scheduled_tasks
|
||||
```
|
||||
|
||||
It returns a JSON body like the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"scheduled_tasks": [
|
||||
{
|
||||
"id": "GSA124oegf1",
|
||||
"action": "shutdown_room",
|
||||
"status": "complete",
|
||||
"timestamp_ms": 23423523,
|
||||
"resource_id": "!roomid",
|
||||
"result": "some result",
|
||||
"error": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Query parameters:**
|
||||
|
||||
* `action_name`: string - Is optional. Returns only the scheduled tasks with the given action name.
|
||||
* `resource_id`: string - Is optional. Returns only the scheduled tasks with the given resource id.
|
||||
* `status`: string - Is optional. Returns only the scheduled tasks matching the given status, one of
|
||||
- "scheduled" - Task is scheduled but not active
|
||||
- "active" - Task is active and probably running, and if not will be run on next scheduler loop run
|
||||
- "complete" - Task has completed successfully
|
||||
- "failed" - Task is over and either returned a failed status, or had an exception
|
||||
|
||||
* `max_timestamp`: int - Is optional. Returns only the scheduled tasks with a timestamp inferior to the specified one.
|
||||
|
||||
**Response**
|
||||
|
||||
The following fields are returned in the JSON response body along with a `200` HTTP status code:
|
||||
|
||||
* `id`: string - ID of scheduled task.
|
||||
* `action`: string - The name of the scheduled task's action.
|
||||
* `status`: string - The status of the scheduled task.
|
||||
* `timestamp_ms`: integer - The timestamp (in milliseconds since the unix epoch) of the given task - If the status is "scheduled" then this represents when it should be launched.
|
||||
Otherwise it represents the last time this task got a change of state.
|
||||
* `resource_id`: Optional string - The resource id of the scheduled task, if it possesses one
|
||||
* `result`: Optional Json - Any result of the scheduled task, if given
|
||||
* `error`: Optional string - If the task has the status "failed", the error associated with this failure
|
||||
@@ -954,8 +954,7 @@ A response body like the following is returned:
|
||||
"last_seen_ip": "1.2.3.4",
|
||||
"last_seen_user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0",
|
||||
"last_seen_ts": 1474491775024,
|
||||
"user_id": "<user_id>",
|
||||
"dehydrated": false
|
||||
"user_id": "<user_id>"
|
||||
},
|
||||
{
|
||||
"device_id": "AUIECTSRND",
|
||||
@@ -963,8 +962,7 @@ A response body like the following is returned:
|
||||
"last_seen_ip": "1.2.3.5",
|
||||
"last_seen_user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0",
|
||||
"last_seen_ts": 1474491775025,
|
||||
"user_id": "<user_id>",
|
||||
"dehydrated": false
|
||||
"user_id": "<user_id>"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
@@ -994,7 +992,6 @@ The following fields are returned in the JSON response body:
|
||||
- `last_seen_ts` - The timestamp (in milliseconds since the unix epoch) when this
|
||||
devices was last seen. (May be a few minutes out of date, for efficiency reasons).
|
||||
- `user_id` - Owner of device.
|
||||
- `dehydrated` - Whether the device is a dehydrated device.
|
||||
|
||||
- `total` - Total number of user's devices.
|
||||
|
||||
|
||||
@@ -150,28 +150,6 @@ $ poetry shell
|
||||
$ poetry install --extras all
|
||||
```
|
||||
|
||||
If you want to go even further and remove the Poetry caches:
|
||||
|
||||
```shell
|
||||
# Find your Poetry cache directory
|
||||
# Docs: https://github.com/python-poetry/poetry/blob/main/docs/configuration.md#cache-directory
|
||||
$ poetry config cache-dir
|
||||
|
||||
# Remove packages from all cached repositories
|
||||
$ poetry cache clear --all .
|
||||
|
||||
# Go completely nuclear and clear out everything Poetry cache related
|
||||
# including the wheel artifacts which is not covered by the above command
|
||||
# (see https://github.com/python-poetry/poetry/issues/10304)
|
||||
#
|
||||
# This is necessary in order to rebuild or fetch new wheels. For example, if you update
|
||||
# the `icu` library in on your system, you will need to rebuild the PyICU Python package
|
||||
# in order to incorporate the correct dynamically linked library locations otherwise you
|
||||
# will run into errors like: `ImportError: libicui18n.so.75: cannot open shared object file: No such file or directory`
|
||||
$ rm -rf $(poetry config cache-dir)
|
||||
```
|
||||
|
||||
|
||||
## ...run a command in the `poetry` virtualenv?
|
||||
|
||||
Use `poetry run cmd args` when you need the python virtualenv context.
|
||||
|
||||
@@ -353,8 +353,6 @@ callback returns `False`, Synapse falls through to the next one. The value of th
|
||||
callback that does not return `False` will be used. If this happens, Synapse will not call
|
||||
any of the subsequent implementations of this callback.
|
||||
|
||||
Note that this check is applied to federation invites as of Synapse v1.130.0.
|
||||
|
||||
|
||||
### `check_login_for_spam`
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ such as [Github][github-idp].
|
||||
[auth0]: https://auth0.com/
|
||||
[authentik]: https://goauthentik.io/
|
||||
[lemonldap]: https://lemonldap-ng.org/
|
||||
[pocket-id]: https://pocket-id.org/
|
||||
[okta]: https://www.okta.com/
|
||||
[dex-idp]: https://github.com/dexidp/dex
|
||||
[keycloak-idp]: https://www.keycloak.org/docs/latest/server_admin/#sso-protocols
|
||||
@@ -625,32 +624,6 @@ oidc_providers:
|
||||
|
||||
Note that the fields `client_id` and `client_secret` are taken from the CURL response above.
|
||||
|
||||
### Pocket ID
|
||||
|
||||
[Pocket ID][pocket-id] is a simple OIDC provider that allows users to authenticate with their passkeys.
|
||||
1. Go to `OIDC Clients`
|
||||
2. Click on `Add OIDC Client`
|
||||
3. Add a name, for example `Synapse`
|
||||
4. Add `"https://auth.example.org/_synapse/client/oidc/callback` to `Callback URLs` # Replace `auth.example.org` with your domain
|
||||
5. Click on `Save`
|
||||
6. Note down your `Client ID` and `Client secret`, these will be used later
|
||||
|
||||
Synapse config:
|
||||
|
||||
```yaml
|
||||
oidc_providers:
|
||||
- idp_id: pocket_id
|
||||
idp_name: Pocket ID
|
||||
issuer: "https://auth.example.org/" # Replace with your domain
|
||||
client_id: "your-client-id" # Replace with the "Client ID" you noted down before
|
||||
client_secret: "your-client-secret" # Replace with the "Client secret" you noted down before
|
||||
scopes: ["openid", "profile"]
|
||||
user_mapping_provider:
|
||||
config:
|
||||
localpart_template: "{{ user.preferred_username }}"
|
||||
display_name_template: "{{ user.name }}"
|
||||
```
|
||||
|
||||
### Shibboleth with OIDC Plugin
|
||||
|
||||
[Shibboleth](https://www.shibboleth.net/) is an open Standard IdP solution widely used by Universities.
|
||||
|
||||
@@ -100,14 +100,6 @@ database:
|
||||
keepalives_count: 3
|
||||
```
|
||||
|
||||
## Postgresql major version upgrades
|
||||
|
||||
Postgres uses separate directories for database locations between major versions (typically `/var/lib/postgresql/<version>/main`).
|
||||
|
||||
Therefore, it is recommended to stop Synapse and other services (MAS, etc) before upgrading Postgres major versions.
|
||||
|
||||
It is also strongly recommended to [back up](./usage/administration/backups.md#database) your database beforehand to ensure no data loss arising from a failed upgrade.
|
||||
|
||||
## Backups
|
||||
|
||||
Don't forget to [back up](./usage/administration/backups.md#database) your database!
|
||||
|
||||
@@ -10,7 +10,7 @@ As an example, a SSO service may return the email address
|
||||
to turn that into a displayname when creating a Matrix user for this individual.
|
||||
It may choose `John Smith`, or `Smith, John [Example.com]` or any number of
|
||||
variations. As each Synapse configuration may want something different, this is
|
||||
where SSO mapping providers come into play.
|
||||
where SAML mapping providers come into play.
|
||||
|
||||
SSO mapping providers are currently supported for OpenID and SAML SSO
|
||||
configurations. Please see the details below for how to implement your own.
|
||||
|
||||
@@ -117,16 +117,6 @@ each upgrade are complete before moving on to the next upgrade, to avoid
|
||||
stacking them up. You can monitor the currently running background updates with
|
||||
[the Admin API](usage/administration/admin_api/background_updates.html#status).
|
||||
|
||||
# Upgrading to v1.130.0
|
||||
|
||||
## Documented endpoint which can be delegated to a federation worker
|
||||
|
||||
The endpoint `^/_matrix/federation/v1/version$` can be delegated to a federation
|
||||
worker. This is not new behaviour, but had not been documented yet. The
|
||||
[list of delegatable endpoints](workers.md#synapseappgeneric_worker) has
|
||||
been updated to include it. Make sure to check your reverse proxy rules if you
|
||||
are using workers.
|
||||
|
||||
# Upgrading to v1.126.0
|
||||
|
||||
## Room list publication rules change
|
||||
|
||||
@@ -160,7 +160,7 @@ Using the following curl command:
|
||||
```console
|
||||
curl -H 'Authorization: Bearer <access-token>' -X DELETE https://matrix.org/_matrix/client/r0/directory/room/<room-alias>
|
||||
```
|
||||
`<access-token>` - can be obtained in element by looking in All settings, clicking Help & About and down the bottom is:
|
||||
`<access-token>` - can be obtained in riot by looking in the riot settings, down the bottom is:
|
||||
Access Token:\<click to reveal\>
|
||||
|
||||
`<room-alias>` - the room alias, eg. #my_room:matrix.org this possibly needs to be URL encoded also, for example %23my_room%3Amatrix.org
|
||||
|
||||
@@ -30,7 +30,7 @@ The following statistics are sent to the configured reporting endpoint:
|
||||
| `python_version` | string | The Python version number in use (e.g "3.7.1"). Taken from `sys.version_info`. |
|
||||
| `total_users` | int | The number of registered users on the homeserver. |
|
||||
| `total_nonbridged_users` | int | The number of users, excluding those created by an Application Service. |
|
||||
| `daily_user_type_native` | int | The number of native, non-guest users created in the last 24 hours. |
|
||||
| `daily_user_type_native` | int | The number of native users created in the last 24 hours. |
|
||||
| `daily_user_type_guest` | int | The number of guest users created in the last 24 hours. |
|
||||
| `daily_user_type_bridged` | int | The number of users created by Application Services in the last 24 hours. |
|
||||
| `total_room_count` | int | The total number of rooms present on the homeserver. |
|
||||
@@ -50,8 +50,8 @@ The following statistics are sent to the configured reporting endpoint:
|
||||
| `cache_factor` | int | The configured [`global factor`](../../configuration/config_documentation.md#caching) value for caching. |
|
||||
| `event_cache_size` | int | The configured [`event_cache_size`](../../configuration/config_documentation.md#caching) value for caching. |
|
||||
| `database_engine` | string | The database engine that is in use. Either "psycopg2" meaning PostgreSQL is in use, or "sqlite3" for SQLite3. |
|
||||
| `database_server_version` | string | The version of the database server. Examples being "10.10" for PostgreSQL server version 10.0, and "3.38.5" for SQLite 3.38.5 installed on the system. |
|
||||
| `log_level` | string | The log level in use. Examples are "INFO", "WARNING", "ERROR", "DEBUG", etc. |
|
||||
| `database_server_version` | string | The version of the database server. Examples being "10.10" for PostgreSQL server version 10.0, and "3.38.5" for SQLite 3.38.5 installed on the system. |
|
||||
| `log_level` | string | The log level in use. Examples are "INFO", "WARNING", "ERROR", "DEBUG", etc. |
|
||||
|
||||
|
||||
[^1]: Native matrix users and guests are always counted. If the
|
||||
|
||||
@@ -2887,20 +2887,6 @@ Example configuration:
|
||||
inhibit_user_in_use_error: true
|
||||
```
|
||||
---
|
||||
### `allow_underscore_prefixed_registration`
|
||||
|
||||
Whether users are allowed to register with a underscore-prefixed localpart.
|
||||
By default, AppServices use prefixes like `_example` to namespace their
|
||||
associated ghost users. If turned on, this may result in clashes or confusion.
|
||||
Useful when provisioning users from an external identity provider.
|
||||
|
||||
Defaults to false.
|
||||
|
||||
Example configuration:
|
||||
```yaml
|
||||
allow_underscore_prefixed_registration: false
|
||||
```
|
||||
---
|
||||
## User session management
|
||||
---
|
||||
### `session_lifetime`
|
||||
@@ -3686,9 +3672,6 @@ Options for each entry include:
|
||||
* `additional_authorization_parameters`: String to string dictionary that will be passed as
|
||||
additional parameters to the authorization grant URL.
|
||||
|
||||
* `passthrough_authorization_parameters`: List of parameters that will be passed through from the redirect endpoint
|
||||
to the authorization grant URL.
|
||||
|
||||
* `allow_existing_users`: set to true to allow a user logging in via OIDC to
|
||||
match a pre-existing account instead of failing. This could be used if
|
||||
switching from password logins to OIDC. Defaults to false.
|
||||
@@ -3782,23 +3765,17 @@ match particular values in the OIDC userinfo. The requirements can be listed und
|
||||
```yaml
|
||||
attribute_requirements:
|
||||
- attribute: family_name
|
||||
one_of: ["Stephensson", "Smith"]
|
||||
value: "Stephensson"
|
||||
- attribute: groups
|
||||
value: "admin"
|
||||
# If `value` or `one_of` are not specified, the attribute only needs
|
||||
# to exist, regardless of value.
|
||||
- attribute: picture
|
||||
```
|
||||
|
||||
`attribute` is a required field, while `value` and `one_of` are optional.
|
||||
|
||||
All of the listed attributes must match for the login to be permitted. Additional attributes can be added to
|
||||
userinfo by expanding the `scopes` section of the OIDC config to retrieve
|
||||
additional information from the OIDC provider.
|
||||
|
||||
If the OIDC claim is a list, then the attribute must match any value in the list.
|
||||
Otherwise, it must exactly match the value of the claim. Using the example
|
||||
above, the `family_name` claim MUST be either "Stephensson" or "Smith", but the `groups`
|
||||
above, the `family_name` claim MUST be "Stephensson", but the `groups`
|
||||
claim MUST contain "admin".
|
||||
|
||||
Example configuration:
|
||||
@@ -3821,7 +3798,6 @@ oidc_providers:
|
||||
jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
||||
additional_authorization_parameters:
|
||||
acr_values: 2fa
|
||||
passthrough_authorization_parameters: ["login_hint"]
|
||||
skip_verification: true
|
||||
enable_registration: true
|
||||
user_mapping_provider:
|
||||
@@ -4038,7 +4014,7 @@ This option has a number of sub-options. They are as follows:
|
||||
* `include_content`: Clients requesting push notifications can either have the body of
|
||||
the message sent in the notification poke along with other details
|
||||
like the sender, or just the event ID and room ID (`event_id_only`).
|
||||
If clients choose to have the body sent, this option controls whether the
|
||||
If clients choose the to have the body sent, this option controls whether the
|
||||
notification request includes the content of the event (other details
|
||||
like the sender are still included). If `event_id_only` is enabled, it
|
||||
has no effect.
|
||||
@@ -4115,7 +4091,6 @@ This option has the following sub-options:
|
||||
* `prefer_local_users`: Defines whether to prefer local users in search query results.
|
||||
If set to true, local users are more likely to appear above remote users when searching the
|
||||
user directory. Defaults to false.
|
||||
* `exclude_remote_users`: If set to true, the search will only return local users. Defaults to false.
|
||||
* `show_locked_users`: Defines whether to show locked users in search query results. Defaults to false.
|
||||
|
||||
Example configuration:
|
||||
@@ -4124,7 +4099,6 @@ user_directory:
|
||||
enabled: false
|
||||
search_all_users: true
|
||||
prefer_local_users: true
|
||||
exclude_remote_users: false
|
||||
show_locked_users: true
|
||||
```
|
||||
---
|
||||
@@ -4351,11 +4325,23 @@ room list by default_
|
||||
Example configuration:
|
||||
|
||||
```yaml
|
||||
# No rule list specified. No one may publish any room to the public list, except server admins.
|
||||
# No rule list specified. Anyone may publish any room to the public list.
|
||||
# This is the default behaviour.
|
||||
room_list_publication_rules:
|
||||
```
|
||||
|
||||
```yaml
|
||||
# A list of one rule which allows everything.
|
||||
# This has the same effect as the previous example.
|
||||
room_list_publication_rules:
|
||||
- "action": "allow"
|
||||
```
|
||||
|
||||
```yaml
|
||||
# An empty list of rules. No-one may publish to the room list.
|
||||
room_list_publication_rules: []
|
||||
```
|
||||
|
||||
```yaml
|
||||
# A list of one rule which denies everything.
|
||||
# This has the same effect as the previous example.
|
||||
@@ -4363,19 +4349,6 @@ room_list_publication_rules:
|
||||
- "action": "deny"
|
||||
```
|
||||
|
||||
```yaml
|
||||
# An empty list of rules.
|
||||
# This has the same effect as the previous example.
|
||||
room_list_publication_rules: []
|
||||
```
|
||||
|
||||
```yaml
|
||||
# A list of one rule which allows everything.
|
||||
# This was the default behaviour pre v1.126.0.
|
||||
room_list_publication_rules:
|
||||
- "action": "allow"
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Prevent a specific user from publishing rooms.
|
||||
# Allow other users to publish anything.
|
||||
|
||||
@@ -200,7 +200,6 @@ information.
|
||||
^/_matrix/client/(api/v1|r0|v3)/rooms/[^/]+/initialSync$
|
||||
|
||||
# Federation requests
|
||||
^/_matrix/federation/v1/version$
|
||||
^/_matrix/federation/v1/event/
|
||||
^/_matrix/federation/v1/state/
|
||||
^/_matrix/federation/v1/state_ids/
|
||||
@@ -250,7 +249,6 @@ information.
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/directory/room/.*$
|
||||
^/_matrix/client/(r0|v3|unstable)/capabilities$
|
||||
^/_matrix/client/(r0|v3|unstable)/notifications$
|
||||
^/_synapse/admin/v1/rooms/
|
||||
|
||||
# Encryption requests
|
||||
^/_matrix/client/(r0|v3|unstable)/keys/query$
|
||||
@@ -282,7 +280,6 @@ Additionally, the following REST endpoints can be handled for GET requests:
|
||||
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/pushrules/
|
||||
^/_matrix/client/unstable/org.matrix.msc4140/delayed_events
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/devices/
|
||||
|
||||
# Account data requests
|
||||
^/_matrix/client/(r0|v3|unstable)/.*/tags
|
||||
@@ -323,15 +320,6 @@ For multiple workers not handling the SSO endpoints properly, see
|
||||
[#7530](https://github.com/matrix-org/synapse/issues/7530) and
|
||||
[#9427](https://github.com/matrix-org/synapse/issues/9427).
|
||||
|
||||
Additionally, when MSC3861 is enabled (`experimental_features.msc3861.enabled`
|
||||
set to `true`), the following endpoints can be handled by the worker:
|
||||
|
||||
^/_synapse/admin/v2/users/[^/]+$
|
||||
^/_synapse/admin/v1/username_available$
|
||||
^/_synapse/admin/v1/users/[^/]+/_allow_cross_signing_replacement_without_uia$
|
||||
# Only the GET method:
|
||||
^/_synapse/admin/v1/users/[^/]+/devices$
|
||||
|
||||
Note that a [HTTP listener](usage/configuration/config_documentation.md#listeners)
|
||||
with `client` and `federation` `resources` must be configured in the
|
||||
[`worker_listeners`](usage/configuration/config_documentation.md#worker_listeners)
|
||||
|
||||
868
poetry.lock
generated
868
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
114
policy_server.py
Normal file
114
policy_server.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import secrets
|
||||
import ssl
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from aiohttp import web
|
||||
from signedjson.key import decode_signing_key_base64
|
||||
from signedjson.types import SigningKey
|
||||
|
||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
|
||||
from synapse.crypto.event_signing import compute_event_signature
|
||||
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
JOIN_FLOW_PAGE = """
|
||||
<html>
|
||||
<body>
|
||||
<a href="/accept?redirect_url={redirect_url}" target="_self">Accept policy and join room</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
SIGNING_KEY = decode_signing_key_base64(
|
||||
"ed25519", "p_afG2", "E+EmxfcqLYjlS20I5ZzjoYeN7oR9Qt/zitPGomU0hmA"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PolicyServer:
|
||||
server_name: str
|
||||
signing_key: SigningKey
|
||||
base_url: str
|
||||
token_store: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@routes.get("/")
|
||||
async def hello(request):
|
||||
return web.Response(text="Hello, world")
|
||||
|
||||
|
||||
@routes.post("/_matrix/federation/unstable/re.jki.join_policy/request_join")
|
||||
async def request_join(request: web.Request) -> web.Response:
|
||||
policy_server: PolicyServer = request.app["policy_server"]
|
||||
return web.json_response({"url": policy_server.base_url + "/join_flow"})
|
||||
|
||||
|
||||
@routes.post("/_matrix/federation/unstable/re.jki.join_policy/sign_join")
|
||||
async def sign_join(request: web.Request) -> web.Response:
|
||||
policy_server: PolicyServer = request.app["policy_server"]
|
||||
|
||||
json_body = await request.json()
|
||||
if json_body["token"] not in policy_server.token_store:
|
||||
return web.json_response({}, status=403)
|
||||
|
||||
room_version_id = json_body["room_version"]
|
||||
event_json = json_body["event"]
|
||||
|
||||
room_version = KNOWN_ROOM_VERSIONS[room_version_id]
|
||||
|
||||
signatures = compute_event_signature(
|
||||
room_version=room_version,
|
||||
event_dict=event_json,
|
||||
signature_name=policy_server.server_name,
|
||||
signing_key=policy_server.signing_key,
|
||||
)
|
||||
|
||||
return web.json_response({"signatures": signatures[policy_server.server_name]})
|
||||
|
||||
|
||||
@routes.get("/join_flow")
|
||||
async def join_flow(request: web.Request) -> web.Response:
|
||||
redirect_url = request.query["redirect_url"]
|
||||
return web.Response(
|
||||
text=JOIN_FLOW_PAGE.format(redirect_url=redirect_url), content_type="text/html"
|
||||
)
|
||||
|
||||
|
||||
@routes.get("/accept")
|
||||
async def accept(request: web.Request) -> web.Response:
|
||||
policy_server: PolicyServer = request.app["policy_server"]
|
||||
|
||||
redirect_url = request.query["redirect_url"]
|
||||
|
||||
token = secrets.token_hex(16)
|
||||
policy_server.token_store[token] = "user_id"
|
||||
|
||||
# TODO: Use less dodgy URL creation
|
||||
if "?" in redirect_url:
|
||||
redirect_url += f"&token={token}"
|
||||
else:
|
||||
redirect_url += f"?token={token}"
|
||||
|
||||
return web.Response(
|
||||
text="Done!",
|
||||
status=307,
|
||||
headers={"location": redirect_url},
|
||||
)
|
||||
|
||||
|
||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
context.load_cert_chain(
|
||||
certfile="/home/erikj/git/synapse/demo/8080/localhost:8080.tls.crt",
|
||||
keyfile="/home/erikj/git/synapse/demo/8080/localhost:8080.tls.key",
|
||||
)
|
||||
|
||||
|
||||
app = web.Application()
|
||||
app["policy_server"] = PolicyServer(
|
||||
server_name="localhost:8865",
|
||||
signing_key=SIGNING_KEY,
|
||||
base_url="https://localhost:8865",
|
||||
)
|
||||
app.add_routes(routes)
|
||||
web.run_app(app, port=8865, ssl_context=context)
|
||||
@@ -97,7 +97,7 @@ module-name = "synapse.synapse_rust"
|
||||
|
||||
[tool.poetry]
|
||||
name = "matrix-synapse"
|
||||
version = "1.130.0"
|
||||
version = "1.127.0rc1"
|
||||
description = "Homeserver for the Matrix decentralised comms protocol"
|
||||
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
@@ -320,7 +320,7 @@ all = [
|
||||
# failing on new releases. Keeping lower bounds loose here means that dependabot
|
||||
# can bump versions without having to update the content-hash in the lockfile.
|
||||
# This helps prevents merge conflicts when running a batch of dependabot updates.
|
||||
ruff = "0.11.11"
|
||||
ruff = "0.7.3"
|
||||
# Type checking only works with the pydantic.v1 compat module from pydantic v2
|
||||
pydantic = "^2"
|
||||
|
||||
@@ -385,9 +385,6 @@ build-backend = "poetry.core.masonry.api"
|
||||
# - PyPy on Aarch64 and musllinux on aarch64: too slow to build.
|
||||
# c.f. https://github.com/matrix-org/synapse/pull/14259
|
||||
skip = "cp36* cp37* cp38* pp37* pp38* *-musllinux_i686 pp*aarch64 *-musllinux_aarch64"
|
||||
# Enable non-default builds.
|
||||
# "pypy" used to be included by default up until cibuildwheel 3.
|
||||
enable = "pypy"
|
||||
|
||||
# We need a rust compiler.
|
||||
#
|
||||
|
||||
@@ -30,14 +30,14 @@ http = "1.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.17"
|
||||
mime = "0.3.17"
|
||||
pyo3 = { version = "0.24.2", features = [
|
||||
pyo3 = { version = "0.23.5", features = [
|
||||
"macros",
|
||||
"anyhow",
|
||||
"abi3",
|
||||
"abi3-py39",
|
||||
] }
|
||||
pyo3-log = "0.12.0"
|
||||
pythonize = "0.24.0"
|
||||
pythonize = "0.23.0"
|
||||
regex = "1.6.0"
|
||||
sha2 = "0.10.8"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Check that no schema deltas have been added to the wrong version.
|
||||
#
|
||||
# Also checks that schema deltas do not try and create or drop indices.
|
||||
|
||||
import re
|
||||
from typing import Any, Dict, List
|
||||
@@ -11,13 +9,6 @@ import click
|
||||
import git
|
||||
|
||||
SCHEMA_FILE_REGEX = re.compile(r"^synapse/storage/schema/(.*)/delta/(.*)/(.*)$")
|
||||
INDEX_CREATION_REGEX = re.compile(r"CREATE .*INDEX .*ON ([a-z_]+)", flags=re.IGNORECASE)
|
||||
INDEX_DELETION_REGEX = re.compile(r"DROP .*INDEX ([a-z_]+)", flags=re.IGNORECASE)
|
||||
TABLE_CREATION_REGEX = re.compile(r"CREATE .*TABLE ([a-z_]+)", flags=re.IGNORECASE)
|
||||
|
||||
# The base branch we want to check against. We use the main development branch
|
||||
# on the assumption that is what we are developing against.
|
||||
DEVELOP_BRANCH = "develop"
|
||||
|
||||
|
||||
@click.command()
|
||||
@@ -29,9 +20,6 @@ DEVELOP_BRANCH = "develop"
|
||||
help="Always output ANSI colours",
|
||||
)
|
||||
def main(force_colors: bool) -> None:
|
||||
# Return code. Set to non-zero when we encounter an error
|
||||
return_code = 0
|
||||
|
||||
click.secho(
|
||||
"+++ Checking schema deltas are in the right folder",
|
||||
fg="green",
|
||||
@@ -42,17 +30,17 @@ def main(force_colors: bool) -> None:
|
||||
click.secho("Updating repo...")
|
||||
|
||||
repo = git.Repo()
|
||||
repo.remote().fetch(refspec=DEVELOP_BRANCH)
|
||||
repo.remote().fetch()
|
||||
|
||||
click.secho("Getting current schema version...")
|
||||
|
||||
r = repo.git.show(f"origin/{DEVELOP_BRANCH}:synapse/storage/schema/__init__.py")
|
||||
r = repo.git.show("origin/develop:synapse/storage/schema/__init__.py")
|
||||
|
||||
locals: Dict[str, Any] = {}
|
||||
exec(r, locals)
|
||||
current_schema_version = locals["SCHEMA_VERSION"]
|
||||
|
||||
diffs: List[git.Diff] = repo.remote().refs[DEVELOP_BRANCH].commit.diff(None)
|
||||
diffs: List[git.Diff] = repo.remote().refs.develop.commit.diff(None)
|
||||
|
||||
# Get the schema version of the local file to check against current schema on develop
|
||||
with open("synapse/storage/schema/__init__.py") as file:
|
||||
@@ -65,7 +53,7 @@ def main(force_colors: bool) -> None:
|
||||
# local schema version must be +/-1 the current schema version on develop
|
||||
if abs(local_schema_version - current_schema_version) != 1:
|
||||
click.secho(
|
||||
f"The proposed schema version has diverged more than one version from {DEVELOP_BRANCH}, please fix!",
|
||||
"The proposed schema version has diverged more than one version from develop, please fix!",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
@@ -79,28 +67,21 @@ def main(force_colors: bool) -> None:
|
||||
click.secho(f"Current schema version: {current_schema_version}")
|
||||
|
||||
seen_deltas = False
|
||||
bad_delta_files = []
|
||||
changed_delta_files = []
|
||||
bad_files = []
|
||||
for diff in diffs:
|
||||
if diff.b_path is None:
|
||||
# We don't lint deleted files.
|
||||
if not diff.new_file or diff.b_path is None:
|
||||
continue
|
||||
|
||||
match = SCHEMA_FILE_REGEX.match(diff.b_path)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
changed_delta_files.append(diff.b_path)
|
||||
|
||||
if not diff.new_file:
|
||||
continue
|
||||
|
||||
seen_deltas = True
|
||||
|
||||
_, delta_version, _ = match.groups()
|
||||
|
||||
if delta_version != str(current_schema_version):
|
||||
bad_delta_files.append(diff.b_path)
|
||||
bad_files.append(diff.b_path)
|
||||
|
||||
if not seen_deltas:
|
||||
click.secho(
|
||||
@@ -111,91 +92,41 @@ def main(force_colors: bool) -> None:
|
||||
)
|
||||
return
|
||||
|
||||
if bad_delta_files:
|
||||
bad_delta_files.sort()
|
||||
|
||||
click.secho(
|
||||
"Found deltas in the wrong folder!",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
|
||||
for f in bad_delta_files:
|
||||
click.secho(
|
||||
f"\t{f}",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
|
||||
click.secho()
|
||||
click.secho(
|
||||
f"Please move these files to delta/{current_schema_version}/",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
|
||||
else:
|
||||
if not bad_files:
|
||||
click.secho(
|
||||
f"All deltas are in the correct folder: {current_schema_version}!",
|
||||
fg="green",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
return
|
||||
|
||||
# Make sure we process them in order. This sort works because deltas are numbered
|
||||
# and delta files are also numbered in order.
|
||||
changed_delta_files.sort()
|
||||
bad_files.sort()
|
||||
|
||||
# Now check that we're not trying to create or drop indices. If we want to
|
||||
# do that they should be in background updates. The exception is when we
|
||||
# create indices on tables we've just created.
|
||||
created_tables = set()
|
||||
for delta_file in changed_delta_files:
|
||||
with open(delta_file) as fd:
|
||||
delta_lines = fd.readlines()
|
||||
click.secho(
|
||||
"Found deltas in the wrong folder!",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
|
||||
for line in delta_lines:
|
||||
# Strip SQL comments
|
||||
line = line.split("--", maxsplit=1)[0]
|
||||
for f in bad_files:
|
||||
click.secho(
|
||||
f"\t{f}",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
|
||||
# Check and track any tables we create
|
||||
match = TABLE_CREATION_REGEX.search(line)
|
||||
if match:
|
||||
table_name = match.group(1)
|
||||
created_tables.add(table_name)
|
||||
click.secho()
|
||||
click.secho(
|
||||
f"Please move these files to delta/{current_schema_version}/",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
|
||||
# Check for dropping indices, these are always banned
|
||||
match = INDEX_DELETION_REGEX.search(line)
|
||||
if match:
|
||||
clause = match.group()
|
||||
|
||||
click.secho(
|
||||
f"Found delta with index deletion: '{clause}' in {delta_file}\nThese should be in background updates.",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
return_code = 1
|
||||
|
||||
# Check for index creation, which is only allowed for tables we've
|
||||
# created.
|
||||
match = INDEX_CREATION_REGEX.search(line)
|
||||
if match:
|
||||
clause = match.group()
|
||||
table_name = match.group(1)
|
||||
if table_name not in created_tables:
|
||||
click.secho(
|
||||
f"Found delta with index creation: '{clause}' in {delta_file}\nThese should be in background updates.",
|
||||
fg="red",
|
||||
bold=True,
|
||||
color=force_colors,
|
||||
)
|
||||
return_code = 1
|
||||
|
||||
click.get_current_context().exit(return_code)
|
||||
click.get_current_context().exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1065,7 +1065,7 @@ class Porter:
|
||||
|
||||
def get_sent_table_size(txn: LoggingTransaction) -> int:
|
||||
txn.execute(
|
||||
"SELECT count(*) FROM sent_transactions WHERE ts >= ?", (yesterday,)
|
||||
"SELECT count(*) FROM sent_transactions" " WHERE ts >= ?", (yesterday,)
|
||||
)
|
||||
result = txn.fetchone()
|
||||
assert result is not None
|
||||
|
||||
@@ -292,9 +292,9 @@ def main() -> None:
|
||||
for key in worker_config:
|
||||
if key == "worker_app": # But we allow worker_app
|
||||
continue
|
||||
assert not key.startswith("worker_"), (
|
||||
"Main process cannot use worker_* config"
|
||||
)
|
||||
assert not key.startswith(
|
||||
"worker_"
|
||||
), "Main process cannot use worker_* config"
|
||||
else:
|
||||
worker_pidfile = worker_config["worker_pid_file"]
|
||||
worker_cache_factor = worker_config.get("synctl_cache_factor")
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#
|
||||
#
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
@@ -39,16 +38,15 @@ from synapse.api.errors import (
|
||||
HttpResponseException,
|
||||
InvalidClientTokenError,
|
||||
OAuthInsufficientScopeError,
|
||||
StoreError,
|
||||
SynapseError,
|
||||
UnrecognizedRequestError,
|
||||
)
|
||||
from synapse.http.site import SynapseRequest
|
||||
from synapse.logging.context import make_deferred_yieldable
|
||||
from synapse.logging.opentracing import active_span, force_tracing, start_active_span
|
||||
from synapse.types import Requester, UserID, create_requester
|
||||
from synapse.util import json_decoder
|
||||
from synapse.util.caches.cached_call import RetryOnExceptionCachedCall
|
||||
from synapse.util.caches.response_cache import ResponseCache, ResponseCacheContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from synapse.rest.admin.experimental_features import ExperimentalFeature
|
||||
@@ -78,61 +76,6 @@ def scope_to_list(scope: str) -> List[str]:
|
||||
return scope.strip().split(" ")
|
||||
|
||||
|
||||
@dataclass
|
||||
class IntrospectionResult:
|
||||
_inner: IntrospectionToken
|
||||
|
||||
# when we retrieved this token,
|
||||
# in milliseconds since the Unix epoch
|
||||
retrieved_at_ms: int
|
||||
|
||||
def is_active(self, now_ms: int) -> bool:
|
||||
if not self._inner.get("active"):
|
||||
return False
|
||||
|
||||
expires_in = self._inner.get("expires_in")
|
||||
if expires_in is None:
|
||||
return True
|
||||
if not isinstance(expires_in, int):
|
||||
raise InvalidClientTokenError("token `expires_in` is not an int")
|
||||
|
||||
absolute_expiry_ms = expires_in * 1000 + self.retrieved_at_ms
|
||||
return now_ms < absolute_expiry_ms
|
||||
|
||||
def get_scope_list(self) -> List[str]:
|
||||
value = self._inner.get("scope")
|
||||
if not isinstance(value, str):
|
||||
return []
|
||||
return scope_to_list(value)
|
||||
|
||||
def get_sub(self) -> Optional[str]:
|
||||
value = self._inner.get("sub")
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
return value
|
||||
|
||||
def get_username(self) -> Optional[str]:
|
||||
value = self._inner.get("username")
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
return value
|
||||
|
||||
def get_name(self) -> Optional[str]:
|
||||
value = self._inner.get("name")
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
return value
|
||||
|
||||
def get_device_id(self) -> Optional[str]:
|
||||
value = self._inner.get("device_id")
|
||||
if value is not None and not isinstance(value, str):
|
||||
raise AuthError(
|
||||
500,
|
||||
"Invalid device ID in introspection result",
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class PrivateKeyJWTWithKid(PrivateKeyJWT): # type: ignore[misc]
|
||||
"""An implementation of the private_key_jwt client auth method that includes a kid header.
|
||||
|
||||
@@ -177,34 +120,6 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
self._http_client = hs.get_proxied_http_client()
|
||||
self._hostname = hs.hostname
|
||||
self._admin_token: Callable[[], Optional[str]] = self._config.admin_token
|
||||
self._force_tracing_for_users = hs.config.tracing.force_tracing_for_users
|
||||
|
||||
# # Token Introspection Cache
|
||||
# This remembers what users/devices are represented by which access tokens,
|
||||
# in order to reduce overall system load:
|
||||
# - on Synapse (as requests are relatively expensive)
|
||||
# - on the network
|
||||
# - on MAS
|
||||
#
|
||||
# Since there is no invalidation mechanism currently,
|
||||
# the entries expire after 2 minutes.
|
||||
# This does mean tokens can be treated as valid by Synapse
|
||||
# for longer than reality.
|
||||
#
|
||||
# Ideally, tokens should logically be invalidated in the following circumstances:
|
||||
# - If a session logout happens.
|
||||
# In this case, MAS will delete the device within Synapse
|
||||
# anyway and this is good enough as an invalidation.
|
||||
# - If the client refreshes their token in MAS.
|
||||
# In this case, the device still exists and it's not the end of the world for
|
||||
# the old access token to continue working for a short time.
|
||||
self._introspection_cache: ResponseCache[str] = ResponseCache(
|
||||
self._clock,
|
||||
"token_introspection",
|
||||
timeout_ms=120_000,
|
||||
# don't log because the keys are access tokens
|
||||
enable_logging=False,
|
||||
)
|
||||
|
||||
self._issuer_metadata = RetryOnExceptionCachedCall[OpenIDProviderMetadata](
|
||||
self._load_metadata
|
||||
@@ -278,9 +193,7 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
metadata = await self._issuer_metadata.get()
|
||||
return metadata.get("introspection_endpoint")
|
||||
|
||||
async def _introspect_token(
|
||||
self, token: str, cache_context: ResponseCacheContext[str]
|
||||
) -> IntrospectionResult:
|
||||
async def _introspect_token(self, token: str) -> IntrospectionToken:
|
||||
"""
|
||||
Send a token to the introspection endpoint and returns the introspection response
|
||||
|
||||
@@ -296,8 +209,6 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
Returns:
|
||||
The introspection response
|
||||
"""
|
||||
# By default, we shouldn't cache the result unless we know it's valid
|
||||
cache_context.should_cache = False
|
||||
introspection_endpoint = await self._introspection_endpoint()
|
||||
raw_headers: Dict[str, str] = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
@@ -355,11 +266,7 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
"The introspection endpoint returned an invalid JSON response."
|
||||
)
|
||||
|
||||
# We had a valid response, so we can cache it
|
||||
cache_context.should_cache = True
|
||||
return IntrospectionResult(
|
||||
IntrospectionToken(**resp), retrieved_at_ms=self._clock.time_msec()
|
||||
)
|
||||
return IntrospectionToken(**resp)
|
||||
|
||||
async def is_server_admin(self, requester: Requester) -> bool:
|
||||
return "urn:synapse:admin:*" in requester.scope
|
||||
@@ -370,55 +277,6 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
allow_guest: bool = False,
|
||||
allow_expired: bool = False,
|
||||
allow_locked: bool = False,
|
||||
) -> Requester:
|
||||
"""Get a registered user's ID.
|
||||
|
||||
Args:
|
||||
request: An HTTP request with an access_token query parameter.
|
||||
allow_guest: If False, will raise an AuthError if the user making the
|
||||
request is a guest.
|
||||
allow_expired: If True, allow the request through even if the account
|
||||
is expired, or session token lifetime has ended. Note that
|
||||
/login will deliver access tokens regardless of expiration.
|
||||
|
||||
Returns:
|
||||
Resolves to the requester
|
||||
Raises:
|
||||
InvalidClientCredentialsError if no user by that token exists or the token
|
||||
is invalid.
|
||||
AuthError if access is denied for the user in the access token
|
||||
"""
|
||||
parent_span = active_span()
|
||||
with start_active_span("get_user_by_req"):
|
||||
requester = await self._wrapped_get_user_by_req(
|
||||
request, allow_guest, allow_expired, allow_locked
|
||||
)
|
||||
|
||||
if parent_span:
|
||||
if requester.authenticated_entity in self._force_tracing_for_users:
|
||||
# request tracing is enabled for this user, so we need to force it
|
||||
# tracing on for the parent span (which will be the servlet span).
|
||||
#
|
||||
# It's too late for the get_user_by_req span to inherit the setting,
|
||||
# so we also force it on for that.
|
||||
force_tracing()
|
||||
force_tracing(parent_span)
|
||||
parent_span.set_tag(
|
||||
"authenticated_entity", requester.authenticated_entity
|
||||
)
|
||||
parent_span.set_tag("user_id", requester.user.to_string())
|
||||
if requester.device_id is not None:
|
||||
parent_span.set_tag("device_id", requester.device_id)
|
||||
if requester.app_service is not None:
|
||||
parent_span.set_tag("appservice_id", requester.app_service.id)
|
||||
return requester
|
||||
|
||||
async def _wrapped_get_user_by_req(
|
||||
self,
|
||||
request: SynapseRequest,
|
||||
allow_guest: bool = False,
|
||||
allow_expired: bool = False,
|
||||
allow_locked: bool = False,
|
||||
) -> Requester:
|
||||
access_token = self.get_access_token_from_request(request)
|
||||
|
||||
@@ -486,9 +344,7 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
)
|
||||
|
||||
try:
|
||||
introspection_result = await self._introspection_cache.wrap(
|
||||
token, self._introspect_token, token, cache_context=True
|
||||
)
|
||||
introspection_result = await self._introspect_token(token)
|
||||
except Exception:
|
||||
logger.exception("Failed to introspect token")
|
||||
raise SynapseError(503, "Unable to introspect the access token")
|
||||
@@ -497,11 +353,11 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
|
||||
# TODO: introspection verification should be more extensive, especially:
|
||||
# - verify the audience
|
||||
if not introspection_result.is_active(self._clock.time_msec()):
|
||||
if not introspection_result.get("active"):
|
||||
raise InvalidClientTokenError("Token is not active")
|
||||
|
||||
# Let's look at the scope
|
||||
scope: List[str] = introspection_result.get_scope_list()
|
||||
scope: List[str] = scope_to_list(introspection_result.get("scope", ""))
|
||||
|
||||
# Determine type of user based on presence of particular scopes
|
||||
has_user_scope = SCOPE_MATRIX_API in scope
|
||||
@@ -511,7 +367,7 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
raise InvalidClientTokenError("No scope in token granting user rights")
|
||||
|
||||
# Match via the sub claim
|
||||
sub = introspection_result.get_sub()
|
||||
sub: Optional[str] = introspection_result.get("sub")
|
||||
if sub is None:
|
||||
raise InvalidClientTokenError(
|
||||
"Invalid sub claim in the introspection result"
|
||||
@@ -524,20 +380,29 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
# If we could not find a user via the external_id, it either does not exist,
|
||||
# or the external_id was never recorded
|
||||
|
||||
username = introspection_result.get_username()
|
||||
if username is None:
|
||||
# TODO: claim mapping should be configurable
|
||||
username: Optional[str] = introspection_result.get("username")
|
||||
if username is None or not isinstance(username, str):
|
||||
raise AuthError(
|
||||
500,
|
||||
"Invalid username claim in the introspection result",
|
||||
)
|
||||
user_id = UserID(username, self._hostname)
|
||||
|
||||
# Try to find a user from the username claim
|
||||
# First try to find a user from the username claim
|
||||
user_info = await self.store.get_user_by_id(user_id=user_id.to_string())
|
||||
if user_info is None:
|
||||
raise AuthError(
|
||||
500,
|
||||
"User not found",
|
||||
# If the user does not exist, we should create it on the fly
|
||||
# TODO: we could use SCIM to provision users ahead of time and listen
|
||||
# for SCIM SET events if those ever become standard:
|
||||
# https://datatracker.ietf.org/doc/html/draft-hunt-scim-notify-00
|
||||
|
||||
# TODO: claim mapping should be configurable
|
||||
# If present, use the name claim as the displayname
|
||||
name: Optional[str] = introspection_result.get("name")
|
||||
|
||||
await self.store.register_user(
|
||||
user_id=user_id.to_string(), create_profile_with_displayname=name
|
||||
)
|
||||
|
||||
# And record the sub as external_id
|
||||
@@ -549,8 +414,15 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
|
||||
# MAS 0.15+ will give us the device ID as an explicit value for compatibility sessions
|
||||
# If present, we get it from here, if not we get it in thee scope
|
||||
device_id = introspection_result.get_device_id()
|
||||
if device_id is None:
|
||||
device_id = introspection_result.get("device_id")
|
||||
if device_id is not None:
|
||||
# We got the device ID explicitly, just sanity check that it's a string
|
||||
if not isinstance(device_id, str):
|
||||
raise AuthError(
|
||||
500,
|
||||
"Invalid device ID in introspection result",
|
||||
)
|
||||
else:
|
||||
# Find device_ids in scope
|
||||
# We only allow a single device_id in the scope, so we find them all in the
|
||||
# scope list, and raise if there are more than one. The OIDC server should be
|
||||
@@ -577,10 +449,17 @@ class MSC3861DelegatedAuth(BaseAuth):
|
||||
"Invalid device ID in introspection result",
|
||||
)
|
||||
|
||||
# Make sure the device exists
|
||||
await self.store.get_device(
|
||||
user_id=user_id.to_string(), device_id=device_id
|
||||
)
|
||||
# Create the device on the fly if it does not exist
|
||||
try:
|
||||
await self.store.get_device(
|
||||
user_id=user_id.to_string(), device_id=device_id
|
||||
)
|
||||
except StoreError:
|
||||
await self.store.store_device(
|
||||
user_id=user_id.to_string(),
|
||||
device_id=device_id,
|
||||
initial_device_display_name="OIDC-native client",
|
||||
)
|
||||
|
||||
# TODO: there is a few things missing in the requester here, which still need
|
||||
# to be figured out, like:
|
||||
|
||||
@@ -29,13 +29,8 @@ from typing import Final
|
||||
# the max size of a (canonical-json-encoded) event
|
||||
MAX_PDU_SIZE = 65536
|
||||
|
||||
# Max/min size of ints in canonical JSON
|
||||
CANONICALJSON_MAX_INT = (2**53) - 1
|
||||
CANONICALJSON_MIN_INT = -CANONICALJSON_MAX_INT
|
||||
|
||||
# the "depth" field on events is limited to the same as what
|
||||
# canonicaljson accepts
|
||||
MAX_DEPTH = CANONICALJSON_MAX_INT
|
||||
# the "depth" field on events is limited to 2**63 - 1
|
||||
MAX_DEPTH = 2**63 - 1
|
||||
|
||||
# the maximum length for a room alias is 255 characters
|
||||
MAX_ALIAS_LENGTH = 255
|
||||
|
||||
@@ -70,7 +70,6 @@ class Codes(str, Enum):
|
||||
THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
||||
THREEPID_DENIED = "M_THREEPID_DENIED"
|
||||
INVALID_USERNAME = "M_INVALID_USERNAME"
|
||||
THREEPID_MEDIUM_NOT_SUPPORTED = "M_THREEPID_MEDIUM_NOT_SUPPORTED"
|
||||
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||
CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||
CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#
|
||||
#
|
||||
|
||||
from typing import Dict, Hashable, Optional, Tuple
|
||||
from collections import OrderedDict
|
||||
from typing import Hashable, Optional, Tuple
|
||||
|
||||
from synapse.api.errors import LimitExceededError
|
||||
from synapse.config.ratelimiting import RatelimitSettings
|
||||
@@ -79,14 +80,12 @@ class Ratelimiter:
|
||||
self.store = store
|
||||
self._limiter_name = cfg.key
|
||||
|
||||
# A dictionary representing the token buckets tracked by this rate
|
||||
# An ordered dictionary representing the token buckets tracked by this rate
|
||||
# limiter. Each entry maps a key of arbitrary type to a tuple representing:
|
||||
# * The number of tokens currently in the bucket,
|
||||
# * The time point when the bucket was last completely empty, and
|
||||
# * The rate_hz (leak rate) of this particular bucket.
|
||||
self.actions: Dict[Hashable, Tuple[float, float, float]] = {}
|
||||
|
||||
self.clock.looping_call(self._prune_message_counts, 60 * 1000)
|
||||
self.actions: OrderedDict[Hashable, Tuple[float, float, float]] = OrderedDict()
|
||||
|
||||
def _get_key(
|
||||
self, requester: Optional[Requester], key: Optional[Hashable]
|
||||
@@ -170,6 +169,9 @@ class Ratelimiter:
|
||||
rate_hz = rate_hz if rate_hz is not None else self.rate_hz
|
||||
burst_count = burst_count if burst_count is not None else self.burst_count
|
||||
|
||||
# Remove any expired entries
|
||||
self._prune_message_counts(time_now_s)
|
||||
|
||||
# Check if there is an existing count entry for this key
|
||||
action_count, time_start, _ = self._get_action_counts(key, time_now_s)
|
||||
|
||||
@@ -244,12 +246,13 @@ class Ratelimiter:
|
||||
action_count, time_start, rate_hz = self._get_action_counts(key, time_now_s)
|
||||
self.actions[key] = (action_count + n_actions, time_start, rate_hz)
|
||||
|
||||
def _prune_message_counts(self) -> None:
|
||||
def _prune_message_counts(self, time_now_s: float) -> None:
|
||||
"""Remove message count entries that have not exceeded their defined
|
||||
rate_hz limit
|
||||
"""
|
||||
time_now_s = self.clock.time()
|
||||
|
||||
Args:
|
||||
time_now_s: The current time
|
||||
"""
|
||||
# We create a copy of the key list here as the dictionary is modified during
|
||||
# the loop
|
||||
for key in list(self.actions.keys()):
|
||||
|
||||
@@ -51,7 +51,8 @@ from synapse.http.server import JsonResource, OptionsResource
|
||||
from synapse.logging.context import LoggingContext
|
||||
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
|
||||
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
|
||||
from synapse.rest import ClientRestResource, admin
|
||||
from synapse.rest import ClientRestResource
|
||||
from synapse.rest.admin import register_servlets_for_media_repo
|
||||
from synapse.rest.health import HealthResource
|
||||
from synapse.rest.key.v2 import KeyResource
|
||||
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||
@@ -175,13 +176,8 @@ class GenericWorkerServer(HomeServer):
|
||||
def _listen_http(self, listener_config: ListenerConfig) -> None:
|
||||
assert listener_config.http_options is not None
|
||||
|
||||
# We always include an admin resource that we populate with servlets as needed
|
||||
admin_resource = JsonResource(self, canonical_json=False)
|
||||
resources: Dict[str, Resource] = {
|
||||
# We always include a health resource.
|
||||
"/health": HealthResource(),
|
||||
"/_synapse/admin": admin_resource,
|
||||
}
|
||||
# We always include a health resource.
|
||||
resources: Dict[str, Resource] = {"/health": HealthResource()}
|
||||
|
||||
for res in listener_config.http_options.resources:
|
||||
for name in res.names:
|
||||
@@ -194,7 +190,6 @@ class GenericWorkerServer(HomeServer):
|
||||
|
||||
resources.update(build_synapse_client_resource_tree(self))
|
||||
resources["/.well-known"] = well_known_resource(self)
|
||||
admin.register_servlets(self, admin_resource)
|
||||
|
||||
elif name == "federation":
|
||||
resources[FEDERATION_PREFIX] = TransportLayerServer(self)
|
||||
@@ -204,13 +199,15 @@ class GenericWorkerServer(HomeServer):
|
||||
|
||||
# We need to serve the admin servlets for media on the
|
||||
# worker.
|
||||
admin.register_servlets_for_media_repo(self, admin_resource)
|
||||
admin_resource = JsonResource(self, canonical_json=False)
|
||||
register_servlets_for_media_repo(self, admin_resource)
|
||||
|
||||
resources.update(
|
||||
{
|
||||
MEDIA_R0_PREFIX: media_repo,
|
||||
MEDIA_V3_PREFIX: media_repo,
|
||||
LEGACY_MEDIA_PREFIX: media_repo,
|
||||
"/_synapse/admin": admin_resource,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -287,7 +284,8 @@ class GenericWorkerServer(HomeServer):
|
||||
elif listener.type == "metrics":
|
||||
if not self.config.metrics.enable_metrics:
|
||||
logger.warning(
|
||||
"Metrics listener configured, but enable_metrics is not True!"
|
||||
"Metrics listener configured, but "
|
||||
"enable_metrics is not True!"
|
||||
)
|
||||
else:
|
||||
if isinstance(listener, TCPListenerConfig):
|
||||
|
||||
@@ -54,7 +54,6 @@ from synapse.config.server import ListenerConfig, TCPListenerConfig
|
||||
from synapse.federation.transport.server import TransportLayerServer
|
||||
from synapse.http.additional_resource import AdditionalResource
|
||||
from synapse.http.server import (
|
||||
JsonResource,
|
||||
OptionsResource,
|
||||
RootOptionsRedirectResource,
|
||||
StaticResource,
|
||||
@@ -62,7 +61,8 @@ from synapse.http.server import (
|
||||
from synapse.logging.context import LoggingContext
|
||||
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
|
||||
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
|
||||
from synapse.rest import ClientRestResource, admin
|
||||
from synapse.rest import ClientRestResource
|
||||
from synapse.rest.admin import AdminRestResource
|
||||
from synapse.rest.health import HealthResource
|
||||
from synapse.rest.key.v2 import KeyResource
|
||||
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||
@@ -180,14 +180,11 @@ class SynapseHomeServer(HomeServer):
|
||||
if compress:
|
||||
client_resource = gz_wrap(client_resource)
|
||||
|
||||
admin_resource = JsonResource(self, canonical_json=False)
|
||||
admin.register_servlets(self, admin_resource)
|
||||
|
||||
resources.update(
|
||||
{
|
||||
CLIENT_API_PREFIX: client_resource,
|
||||
"/.well-known": well_known_resource(self),
|
||||
"/_synapse/admin": admin_resource,
|
||||
"/_synapse/admin": AdminRestResource(self),
|
||||
**build_synapse_client_resource_tree(self),
|
||||
}
|
||||
)
|
||||
@@ -289,7 +286,8 @@ class SynapseHomeServer(HomeServer):
|
||||
elif listener.type == "metrics":
|
||||
if not self.config.metrics.enable_metrics:
|
||||
logger.warning(
|
||||
"Metrics listener configured, but enable_metrics is not True!"
|
||||
"Metrics listener configured, but "
|
||||
"enable_metrics is not True!"
|
||||
)
|
||||
else:
|
||||
if isinstance(listener, TCPListenerConfig):
|
||||
|
||||
@@ -34,22 +34,6 @@ if TYPE_CHECKING:
|
||||
|
||||
logger = logging.getLogger("synapse.app.homeserver")
|
||||
|
||||
ONE_MINUTE_SECONDS = 60
|
||||
ONE_HOUR_SECONDS = 60 * ONE_MINUTE_SECONDS
|
||||
|
||||
MILLISECONDS_PER_SECOND = 1000
|
||||
|
||||
INITIAL_DELAY_BEFORE_FIRST_PHONE_HOME_SECONDS = 5 * ONE_MINUTE_SECONDS
|
||||
"""
|
||||
We wait 5 minutes to send the first set of stats as the server can be quite busy the
|
||||
first few minutes
|
||||
"""
|
||||
|
||||
PHONE_HOME_INTERVAL_SECONDS = 3 * ONE_HOUR_SECONDS
|
||||
"""
|
||||
Phone home stats are sent every 3 hours
|
||||
"""
|
||||
|
||||
# Contains the list of processes we will be monitoring
|
||||
# currently either 0 or 1
|
||||
_stats_process: List[Tuple[int, "resource.struct_rusage"]] = []
|
||||
@@ -201,14 +185,12 @@ def start_phone_stats_home(hs: "HomeServer") -> None:
|
||||
# If you increase the loop period, the accuracy of user_daily_visits
|
||||
# table will decrease
|
||||
clock.looping_call(
|
||||
hs.get_datastores().main.generate_user_daily_visits,
|
||||
5 * ONE_MINUTE_SECONDS * MILLISECONDS_PER_SECOND,
|
||||
hs.get_datastores().main.generate_user_daily_visits, 5 * 60 * 1000
|
||||
)
|
||||
|
||||
# monthly active user limiting functionality
|
||||
clock.looping_call(
|
||||
hs.get_datastores().main.reap_monthly_active_users,
|
||||
ONE_HOUR_SECONDS * MILLISECONDS_PER_SECOND,
|
||||
hs.get_datastores().main.reap_monthly_active_users, 1000 * 60 * 60
|
||||
)
|
||||
hs.get_datastores().main.reap_monthly_active_users()
|
||||
|
||||
@@ -239,12 +221,7 @@ def start_phone_stats_home(hs: "HomeServer") -> None:
|
||||
|
||||
if hs.config.metrics.report_stats:
|
||||
logger.info("Scheduling stats reporting for 3 hour intervals")
|
||||
clock.looping_call(
|
||||
phone_stats_home,
|
||||
PHONE_HOME_INTERVAL_SECONDS * MILLISECONDS_PER_SECOND,
|
||||
hs,
|
||||
stats,
|
||||
)
|
||||
clock.looping_call(phone_stats_home, 3 * 60 * 60 * 1000, hs, stats)
|
||||
|
||||
# We need to defer this init for the cases that we daemonize
|
||||
# otherwise the process ID we get is that of the non-daemon process
|
||||
@@ -252,6 +229,4 @@ def start_phone_stats_home(hs: "HomeServer") -> None:
|
||||
|
||||
# We wait 5 minutes to send the first set of stats as the server can
|
||||
# be quite busy the first few minutes
|
||||
clock.call_later(
|
||||
INITIAL_DELAY_BEFORE_FIRST_PHONE_HOME_SECONDS, phone_stats_home, hs, stats
|
||||
)
|
||||
clock.call_later(5 * 60, phone_stats_home, hs, stats)
|
||||
|
||||
@@ -170,7 +170,7 @@ class Config:
|
||||
|
||||
section: ClassVar[str]
|
||||
|
||||
def __init__(self, root_config: "RootConfig"):
|
||||
def __init__(self, root_config: "RootConfig" = None):
|
||||
self.root = root_config
|
||||
|
||||
# Get the path to the default Synapse template directory
|
||||
@@ -445,7 +445,7 @@ class RootConfig:
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def invoke_all_static(cls, func_name: str, *args: Any, **kwargs: Any) -> None:
|
||||
def invoke_all_static(cls, func_name: str, *args: Any, **kwargs: any) -> None:
|
||||
"""
|
||||
Invoke a static function on config objects this RootConfig is
|
||||
configured to use.
|
||||
@@ -1047,7 +1047,7 @@ class RoutableShardedWorkerHandlingConfig(ShardedWorkerHandlingConfig):
|
||||
return self._get_instance(key)
|
||||
|
||||
|
||||
def read_file(file_path: Any, config_path: StrSequence) -> str:
|
||||
def read_file(file_path: Any, config_path: Iterable[str]) -> str:
|
||||
"""Check the given file exists, and read it into a string
|
||||
|
||||
If it does not, emit an error indicating the problem
|
||||
|
||||
@@ -179,7 +179,7 @@ class RootConfig:
|
||||
class Config:
|
||||
root: RootConfig
|
||||
default_template_dir: str
|
||||
def __init__(self, root_config: RootConfig = ...) -> None: ...
|
||||
def __init__(self, root_config: Optional[RootConfig] = ...) -> None: ...
|
||||
@staticmethod
|
||||
def parse_size(value: Union[str, int]) -> int: ...
|
||||
@staticmethod
|
||||
@@ -212,4 +212,4 @@ class ShardedWorkerHandlingConfig:
|
||||
class RoutableShardedWorkerHandlingConfig(ShardedWorkerHandlingConfig):
|
||||
def get_instance(self, key: str) -> str: ... # noqa: F811
|
||||
|
||||
def read_file(file_path: Any, config_path: StrSequence) -> str: ...
|
||||
def read_file(file_path: Any, config_path: Iterable[str]) -> str: ...
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import enum
|
||||
from functools import cache
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import TYPE_CHECKING, Any, Iterable, Optional
|
||||
|
||||
import attr
|
||||
import attr.validators
|
||||
@@ -29,7 +29,7 @@ import attr.validators
|
||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
|
||||
from synapse.config import ConfigError
|
||||
from synapse.config._base import Config, RootConfig, read_file
|
||||
from synapse.types import JsonDict, StrSequence
|
||||
from synapse.types import JsonDict
|
||||
|
||||
# Determine whether authlib is installed.
|
||||
try:
|
||||
@@ -45,7 +45,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
@cache
|
||||
def read_secret_from_file_once(file_path: Any, config_path: StrSequence) -> str:
|
||||
def read_secret_from_file_once(file_path: Any, config_path: Iterable[str]) -> str:
|
||||
"""Returns the memoized secret read from file."""
|
||||
return read_file(file_path, config_path).strip()
|
||||
|
||||
@@ -560,9 +560,3 @@ class ExperimentalConfig(Config):
|
||||
|
||||
# MSC4076: Add `disable_badge_count`` to pusher configuration
|
||||
self.msc4076_enabled: bool = experimental.get("msc4076_enabled", False)
|
||||
|
||||
# MSC4263: Preventing MXID enumeration via key queries
|
||||
self.msc4263_limit_key_queries_to_users_who_share_rooms = experimental.get(
|
||||
"msc4263_limit_key_queries_to_users_who_share_rooms",
|
||||
False,
|
||||
)
|
||||
|
||||
@@ -191,7 +191,7 @@ class KeyConfig(Config):
|
||||
if macaroon_secret_key:
|
||||
raise ConfigError(CONFLICTING_MACAROON_SECRET_KEY_OPTS_ERROR)
|
||||
macaroon_secret_key = read_file(
|
||||
macaroon_secret_key_path, ("macaroon_secret_key_path",)
|
||||
macaroon_secret_key_path, "macaroon_secret_key_path"
|
||||
).strip()
|
||||
if not macaroon_secret_key:
|
||||
macaroon_secret_key = self.root.registration.registration_shared_secret
|
||||
@@ -216,9 +216,7 @@ class KeyConfig(Config):
|
||||
if form_secret_path:
|
||||
if form_secret:
|
||||
raise ConfigError(CONFLICTING_FORM_SECRET_OPTS_ERROR)
|
||||
self.form_secret = read_file(
|
||||
form_secret_path, ("form_secret_path",)
|
||||
).strip()
|
||||
self.form_secret = read_file(form_secret_path, "form_secret_path").strip()
|
||||
else:
|
||||
self.form_secret = form_secret
|
||||
|
||||
|
||||
@@ -356,9 +356,6 @@ def _parse_oidc_config_dict(
|
||||
additional_authorization_parameters=oidc_config.get(
|
||||
"additional_authorization_parameters", {}
|
||||
),
|
||||
passthrough_authorization_parameters=oidc_config.get(
|
||||
"passthrough_authorization_parameters", []
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -504,6 +501,3 @@ class OidcProviderConfig:
|
||||
|
||||
# Additional parameters that will be passed to the authorization grant URL
|
||||
additional_authorization_parameters: Mapping[str, str]
|
||||
|
||||
# Allow query parameters to the redirect endpoint that will be passed to the authorization grant URL
|
||||
passthrough_authorization_parameters: Collection[str]
|
||||
|
||||
@@ -162,10 +162,6 @@ class RegistrationConfig(Config):
|
||||
"disable_msisdn_registration", False
|
||||
)
|
||||
|
||||
self.allow_underscore_prefixed_localpart = config.get(
|
||||
"allow_underscore_prefixed_localpart", False
|
||||
)
|
||||
|
||||
session_lifetime = config.get("session_lifetime")
|
||||
if session_lifetime is not None:
|
||||
session_lifetime = self.parse_duration(session_lifetime)
|
||||
|
||||
@@ -43,7 +43,8 @@ class SsoAttributeRequirement:
|
||||
"""Object describing a single requirement for SSO attributes."""
|
||||
|
||||
attribute: str
|
||||
# If neither `value` nor `one_of` is given, the attribute must simply exist.
|
||||
# If neither value nor one_of is given, the attribute must simply exist. This is
|
||||
# only true for CAS configs which use a different JSON schema than the one below.
|
||||
value: Optional[str] = None
|
||||
one_of: Optional[List[str]] = None
|
||||
|
||||
@@ -55,6 +56,10 @@ class SsoAttributeRequirement:
|
||||
"one_of": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"required": ["attribute"],
|
||||
"oneOf": [
|
||||
{"required": ["value"]},
|
||||
{"required": ["one_of"]},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -108,7 +108,8 @@ class TlsConfig(Config):
|
||||
# Raise an error if this option has been specified without any
|
||||
# corresponding certificates.
|
||||
raise ConfigError(
|
||||
"federation_custom_ca_list specified without any certificate files"
|
||||
"federation_custom_ca_list specified without "
|
||||
"any certificate files"
|
||||
)
|
||||
|
||||
certs = []
|
||||
|
||||
@@ -38,9 +38,6 @@ class UserDirectoryConfig(Config):
|
||||
self.user_directory_search_all_users = user_directory_config.get(
|
||||
"search_all_users", False
|
||||
)
|
||||
self.user_directory_exclude_remote_users = user_directory_config.get(
|
||||
"exclude_remote_users", False
|
||||
)
|
||||
self.user_directory_search_prefer_local_users = user_directory_config.get(
|
||||
"prefer_local_users", False
|
||||
)
|
||||
|
||||
@@ -263,7 +263,7 @@ class WorkerConfig(Config):
|
||||
if worker_replication_secret:
|
||||
raise ConfigError(CONFLICTING_WORKER_REPLICATION_SECRET_OPTS_ERROR)
|
||||
self.worker_replication_secret = read_file(
|
||||
worker_replication_secret_path, ("worker_replication_secret_path",)
|
||||
worker_replication_secret_path, "worker_replication_secret_path"
|
||||
).strip()
|
||||
else:
|
||||
self.worker_replication_secret = worker_replication_secret
|
||||
|
||||
@@ -552,11 +552,23 @@ def _is_membership_change_allowed(
|
||||
|
||||
key = (EventTypes.JoinRules, "")
|
||||
join_rule_event = auth_events.get(key)
|
||||
join_policy_server: Optional[str] = None
|
||||
if join_rule_event:
|
||||
join_rule = join_rule_event.content.get("join_rule", JoinRules.INVITE)
|
||||
join_policy_server = join_rule_event.content.get("re.jki.join_policy_server")
|
||||
else:
|
||||
join_rule = JoinRules.INVITE
|
||||
|
||||
if (
|
||||
join_policy_server
|
||||
and membership == Membership.JOIN
|
||||
and not (caller_in_room or caller_invited)
|
||||
):
|
||||
logger.info("Checking sigs")
|
||||
if not event.signatures.get(join_policy_server):
|
||||
raise AuthError(403, "Not signed by join policy server")
|
||||
caller_invited = True
|
||||
|
||||
user_level = get_user_power_level(event.user_id, auth_events)
|
||||
target_level = get_user_power_level(target_user_id, auth_events)
|
||||
|
||||
@@ -986,7 +998,8 @@ def _check_power_levels(
|
||||
if old_level == user_level:
|
||||
raise AuthError(
|
||||
403,
|
||||
"You don't have permission to remove ops level equal to your own",
|
||||
"You don't have permission to remove ops level equal "
|
||||
"to your own",
|
||||
)
|
||||
|
||||
# Check if the old and new levels are greater than the user level
|
||||
|
||||
@@ -40,8 +40,6 @@ import attr
|
||||
from canonicaljson import encode_canonical_json
|
||||
|
||||
from synapse.api.constants import (
|
||||
CANONICALJSON_MAX_INT,
|
||||
CANONICALJSON_MIN_INT,
|
||||
MAX_PDU_SIZE,
|
||||
EventContentFields,
|
||||
EventTypes,
|
||||
@@ -63,6 +61,9 @@ SPLIT_FIELD_REGEX = re.compile(r"\\*\.")
|
||||
# Find escaped characters, e.g. those with a \ in front of them.
|
||||
ESCAPE_SEQUENCE_PATTERN = re.compile(r"\\(.)")
|
||||
|
||||
CANONICALJSON_MAX_INT = (2**53) - 1
|
||||
CANONICALJSON_MIN_INT = -CANONICALJSON_MAX_INT
|
||||
|
||||
|
||||
# Module API callback that allows adding fields to the unsigned section of
|
||||
# events that are sent to clients.
|
||||
|
||||
@@ -86,7 +86,9 @@ class EventValidator:
|
||||
|
||||
# Depending on the room version, ensure the data is spec compliant JSON.
|
||||
if event.room_version.strict_canonicaljson:
|
||||
validate_canonicaljson(event.get_pdu_json())
|
||||
# Note that only the client controlled portion of the event is
|
||||
# checked, since we trust the portions of the event we created.
|
||||
validate_canonicaljson(event.content)
|
||||
|
||||
if event.type == EventTypes.Aliases:
|
||||
if "aliases" in event.content:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#
|
||||
#
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Awaitable, Callable, List, Optional, Sequence
|
||||
from typing import TYPE_CHECKING, Awaitable, Callable, Optional
|
||||
|
||||
from synapse.api.constants import MAX_DEPTH, EventContentFields, EventTypes, Membership
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
@@ -29,8 +29,6 @@ from synapse.crypto.event_signing import check_event_content_hash
|
||||
from synapse.crypto.keyring import Keyring
|
||||
from synapse.events import EventBase, make_event_from_dict
|
||||
from synapse.events.utils import prune_event, validate_canonicaljson
|
||||
from synapse.federation.units import filter_pdus_for_valid_depth
|
||||
from synapse.handlers.room_policy import RoomPolicyHandler
|
||||
from synapse.http.servlet import assert_params_in_dict
|
||||
from synapse.logging.opentracing import log_kv, trace
|
||||
from synapse.types import JsonDict, get_domain_from_id
|
||||
@@ -65,24 +63,6 @@ class FederationBase:
|
||||
self._clock = hs.get_clock()
|
||||
self._storage_controllers = hs.get_storage_controllers()
|
||||
|
||||
# We need to define this lazily otherwise we get a cyclic dependency.
|
||||
# self._policy_handler = hs.get_room_policy_handler()
|
||||
self._policy_handler: Optional[RoomPolicyHandler] = None
|
||||
|
||||
def _lazily_get_policy_handler(self) -> RoomPolicyHandler:
|
||||
"""Lazily get the room policy handler.
|
||||
|
||||
This is required to avoid an import cycle: RoomPolicyHandler requires a
|
||||
FederationClient, which requires a FederationBase, which requires a
|
||||
RoomPolicyHandler.
|
||||
|
||||
Returns:
|
||||
RoomPolicyHandler: The room policy handler.
|
||||
"""
|
||||
if self._policy_handler is None:
|
||||
self._policy_handler = self.hs.get_room_policy_handler()
|
||||
return self._policy_handler
|
||||
|
||||
@trace
|
||||
async def _check_sigs_and_hash(
|
||||
self,
|
||||
@@ -99,10 +79,6 @@ class FederationBase:
|
||||
Also runs the event through the spam checker; if it fails, redacts the event
|
||||
and flags it as soft-failed.
|
||||
|
||||
Also checks that the event is allowed by the policy server, if the room uses
|
||||
a policy server. If the event is not allowed, the event is flagged as
|
||||
soft-failed but not redacted.
|
||||
|
||||
Args:
|
||||
room_version: The room version of the PDU
|
||||
pdu: the event to be checked
|
||||
@@ -168,17 +144,6 @@ class FederationBase:
|
||||
)
|
||||
return redacted_event
|
||||
|
||||
policy_allowed = await self._lazily_get_policy_handler().is_event_allowed(pdu)
|
||||
if not policy_allowed:
|
||||
logger.warning(
|
||||
"Event not allowed by policy server, soft-failing %s", pdu.event_id
|
||||
)
|
||||
pdu.internal_metadata.soft_failed = True
|
||||
# Note: we don't redact the event so admins can inspect the event after the
|
||||
# fact. Other processes may redact the event, but that won't be applied to
|
||||
# the database copy of the event until the server's config requires it.
|
||||
return pdu
|
||||
|
||||
spam_check = await self._spam_checker_module_callbacks.check_event_for_spam(pdu)
|
||||
|
||||
if spam_check != self._spam_checker_module_callbacks.NOT_SPAM:
|
||||
@@ -302,15 +267,6 @@ def _is_invite_via_3pid(event: EventBase) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def parse_events_from_pdu_json(
|
||||
pdus_json: Sequence[JsonDict], room_version: RoomVersion
|
||||
) -> List[EventBase]:
|
||||
return [
|
||||
event_from_pdu_json(pdu_json, room_version)
|
||||
for pdu_json in filter_pdus_for_valid_depth(pdus_json)
|
||||
]
|
||||
|
||||
|
||||
def event_from_pdu_json(pdu_json: JsonDict, room_version: RoomVersion) -> EventBase:
|
||||
"""Construct an EventBase from an event json received over federation
|
||||
|
||||
|
||||
@@ -68,14 +68,12 @@ from synapse.federation.federation_base import (
|
||||
FederationBase,
|
||||
InvalidEventSignatureError,
|
||||
event_from_pdu_json,
|
||||
parse_events_from_pdu_json,
|
||||
)
|
||||
from synapse.federation.transport.client import SendJoinResponse
|
||||
from synapse.http.client import is_unknown_endpoint
|
||||
from synapse.http.types import QueryParams
|
||||
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, tag_args, trace
|
||||
from synapse.types import JsonDict, StrCollection, UserID, get_domain_from_id
|
||||
from synapse.types.handlers.policy_server import RECOMMENDATION_OK, RECOMMENDATION_SPAM
|
||||
from synapse.util.async_helpers import concurrently_execute
|
||||
from synapse.util.caches.expiringcache import ExpiringCache
|
||||
from synapse.util.retryutils import NotRetryingDestination
|
||||
@@ -351,7 +349,7 @@ class FederationClient(FederationBase):
|
||||
|
||||
room_version = await self.store.get_room_version(room_id)
|
||||
|
||||
pdus = parse_events_from_pdu_json(transaction_data_pdus, room_version)
|
||||
pdus = [event_from_pdu_json(p, room_version) for p in transaction_data_pdus]
|
||||
|
||||
# Check signatures and hash of pdus, removing any from the list that fail checks
|
||||
pdus[:] = await self._check_sigs_and_hash_for_pulled_events_and_fetch(
|
||||
@@ -395,7 +393,9 @@ class FederationClient(FederationBase):
|
||||
transaction_data,
|
||||
)
|
||||
|
||||
pdu_list = parse_events_from_pdu_json(transaction_data["pdus"], room_version)
|
||||
pdu_list: List[EventBase] = [
|
||||
event_from_pdu_json(p, room_version) for p in transaction_data["pdus"]
|
||||
]
|
||||
|
||||
if pdu_list and pdu_list[0]:
|
||||
pdu = pdu_list[0]
|
||||
@@ -422,62 +422,6 @@ class FederationClient(FederationBase):
|
||||
|
||||
return None
|
||||
|
||||
@trace
|
||||
@tag_args
|
||||
async def get_pdu_policy_recommendation(
|
||||
self, destination: str, pdu: EventBase, timeout: Optional[int] = None
|
||||
) -> str:
|
||||
"""Requests that the destination server (typically a policy server)
|
||||
check the event and return its recommendation on how to handle the
|
||||
event.
|
||||
|
||||
If the policy server could not be contacted or the policy server
|
||||
returned an unknown recommendation, this returns an OK recommendation.
|
||||
This type fixing behaviour is done because the typical caller will be
|
||||
in a critical call path and would generally interpret a `None` or similar
|
||||
response as "weird value; don't care; move on without taking action". We
|
||||
just frontload that logic here.
|
||||
|
||||
|
||||
Args:
|
||||
destination: The remote homeserver to ask (a policy server)
|
||||
pdu: The event to check
|
||||
timeout: How long to try (in ms) the destination for before
|
||||
giving up. None indicates no timeout.
|
||||
|
||||
Returns:
|
||||
The policy recommendation, or RECOMMENDATION_OK if the policy server was
|
||||
uncontactable or returned an unknown recommendation.
|
||||
"""
|
||||
|
||||
logger.debug(
|
||||
"get_pdu_policy_recommendation for event_id=%s from %s",
|
||||
pdu.event_id,
|
||||
destination,
|
||||
)
|
||||
|
||||
try:
|
||||
res = await self.transport_layer.get_policy_recommendation_for_pdu(
|
||||
destination, pdu, timeout=timeout
|
||||
)
|
||||
recommendation = res.get("recommendation")
|
||||
if not isinstance(recommendation, str):
|
||||
raise InvalidResponseError("recommendation is not a string")
|
||||
if recommendation not in (RECOMMENDATION_OK, RECOMMENDATION_SPAM):
|
||||
logger.warning(
|
||||
"get_pdu_policy_recommendation: unknown recommendation: %s",
|
||||
recommendation,
|
||||
)
|
||||
return RECOMMENDATION_OK
|
||||
return recommendation
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"get_pdu_policy_recommendation: server %s responded with error, assuming OK recommendation: %s",
|
||||
destination,
|
||||
e,
|
||||
)
|
||||
return RECOMMENDATION_OK
|
||||
|
||||
@trace
|
||||
@tag_args
|
||||
async def get_pdu(
|
||||
@@ -865,7 +809,7 @@ class FederationClient(FederationBase):
|
||||
|
||||
room_version = await self.store.get_room_version(room_id)
|
||||
|
||||
auth_chain = parse_events_from_pdu_json(res["auth_chain"], room_version)
|
||||
auth_chain = [event_from_pdu_json(p, room_version) for p in res["auth_chain"]]
|
||||
|
||||
signed_auth = await self._check_sigs_and_hash_for_pulled_events_and_fetch(
|
||||
destination, auth_chain, room_version=room_version
|
||||
@@ -1585,7 +1529,9 @@ class FederationClient(FederationBase):
|
||||
|
||||
room_version = await self.store.get_room_version(room_id)
|
||||
|
||||
events = parse_events_from_pdu_json(content.get("events", []), room_version)
|
||||
events = [
|
||||
event_from_pdu_json(e, room_version) for e in content.get("events", [])
|
||||
]
|
||||
|
||||
signed_events = await self._check_sigs_and_hash_for_pulled_events_and_fetch(
|
||||
destination, events, room_version=room_version
|
||||
@@ -2014,6 +1960,43 @@ class FederationClient(FederationBase):
|
||||
ip_address=ip_address,
|
||||
)
|
||||
|
||||
async def join_policy_server_get_url(
|
||||
self, policy_server: str, room_id: str, room_version: RoomVersion, user_id: str
|
||||
) -> Optional[str]:
|
||||
result = await self.transport_layer.join_policy_server_get_url(
|
||||
policy_server=policy_server,
|
||||
room_id=room_id,
|
||||
room_version=room_version,
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
url = result.get("url")
|
||||
if isinstance(url, str):
|
||||
return url
|
||||
return None
|
||||
|
||||
async def join_policy_server_sign_join(
|
||||
self,
|
||||
policy_server: str,
|
||||
room_id: str,
|
||||
user_id: str,
|
||||
token: str,
|
||||
room_version: RoomVersion,
|
||||
event: EventBase,
|
||||
) -> None:
|
||||
result = await self.transport_layer.join_policy_server_sign_join(
|
||||
policy_server=policy_server,
|
||||
room_id=room_id,
|
||||
user_id=user_id,
|
||||
token=token,
|
||||
room_version=room_version,
|
||||
event=event,
|
||||
)
|
||||
|
||||
signatures = result.get("signatures")
|
||||
if signatures:
|
||||
event.signatures[policy_server] = signatures
|
||||
|
||||
|
||||
@attr.s(frozen=True, slots=True, auto_attribs=True)
|
||||
class TimestampToEventResponse:
|
||||
|
||||
@@ -66,7 +66,7 @@ from synapse.federation.federation_base import (
|
||||
event_from_pdu_json,
|
||||
)
|
||||
from synapse.federation.persistence import TransactionActions
|
||||
from synapse.federation.units import Edu, Transaction, serialize_and_filter_pdus
|
||||
from synapse.federation.units import Edu, Transaction
|
||||
from synapse.handlers.worker_lock import NEW_EVENT_DURING_PURGE_LOCK_NAME
|
||||
from synapse.http.servlet import assert_params_in_dict
|
||||
from synapse.logging.context import (
|
||||
@@ -469,12 +469,7 @@ class FederationServer(FederationBase):
|
||||
logger.info("Ignoring PDU: %s", e)
|
||||
continue
|
||||
|
||||
try:
|
||||
event = event_from_pdu_json(p, room_version)
|
||||
except SynapseError as e:
|
||||
logger.info("Ignoring PDU for failing to deserialize: %s", e)
|
||||
continue
|
||||
|
||||
event = event_from_pdu_json(p, room_version)
|
||||
pdus_by_room.setdefault(room_id, []).append(event)
|
||||
|
||||
if event.origin_server_ts > newest_pdu_ts:
|
||||
@@ -641,8 +636,8 @@ class FederationServer(FederationBase):
|
||||
)
|
||||
|
||||
return {
|
||||
"pdus": serialize_and_filter_pdus(pdus),
|
||||
"auth_chain": serialize_and_filter_pdus(auth_chain),
|
||||
"pdus": [pdu.get_pdu_json() for pdu in pdus],
|
||||
"auth_chain": [pdu.get_pdu_json() for pdu in auth_chain],
|
||||
}
|
||||
|
||||
async def on_pdu_request(
|
||||
@@ -701,12 +696,6 @@ class FederationServer(FederationBase):
|
||||
pdu = event_from_pdu_json(content, room_version)
|
||||
origin_host, _ = parse_server_name(origin)
|
||||
await self.check_server_matches_acl(origin_host, pdu.room_id)
|
||||
if await self._spam_checker_module_callbacks.should_drop_federated_event(pdu):
|
||||
logger.info(
|
||||
"Federated event contains spam, dropping %s",
|
||||
pdu.event_id,
|
||||
)
|
||||
raise SynapseError(403, Codes.FORBIDDEN)
|
||||
try:
|
||||
pdu = await self._check_sigs_and_hash(room_version, pdu)
|
||||
except InvalidEventSignatureError as e:
|
||||
@@ -772,8 +761,8 @@ class FederationServer(FederationBase):
|
||||
event_json = event.get_pdu_json(time_now)
|
||||
resp = {
|
||||
"event": event_json,
|
||||
"state": serialize_and_filter_pdus(state_events, time_now),
|
||||
"auth_chain": serialize_and_filter_pdus(auth_chain_events, time_now),
|
||||
"state": [p.get_pdu_json(time_now) for p in state_events],
|
||||
"auth_chain": [p.get_pdu_json(time_now) for p in auth_chain_events],
|
||||
"members_omitted": caller_supports_partial_state,
|
||||
}
|
||||
|
||||
@@ -1016,7 +1005,7 @@ class FederationServer(FederationBase):
|
||||
|
||||
time_now = self._clock.time_msec()
|
||||
auth_pdus = await self.handler.on_event_auth(event_id)
|
||||
res = {"auth_chain": serialize_and_filter_pdus(auth_pdus, time_now)}
|
||||
res = {"auth_chain": [a.get_pdu_json(time_now) for a in auth_pdus]}
|
||||
return 200, res
|
||||
|
||||
async def on_query_client_keys(
|
||||
@@ -1101,7 +1090,7 @@ class FederationServer(FederationBase):
|
||||
|
||||
time_now = self._clock.time_msec()
|
||||
|
||||
return {"events": serialize_and_filter_pdus(missing_events, time_now)}
|
||||
return {"events": [ev.get_pdu_json(time_now) for ev in missing_events]}
|
||||
|
||||
async def on_openid_userinfo(self, token: str) -> Optional[str]:
|
||||
ts_now_ms = self._clock.time_msec()
|
||||
|
||||
@@ -143,33 +143,6 @@ class TransportLayerClient:
|
||||
destination, path=path, timeout=timeout, try_trailing_slash_on_400=True
|
||||
)
|
||||
|
||||
async def get_policy_recommendation_for_pdu(
|
||||
self, destination: str, event: EventBase, timeout: Optional[int] = None
|
||||
) -> JsonDict:
|
||||
"""Requests the policy recommendation for the given pdu from the given policy server.
|
||||
|
||||
Args:
|
||||
destination: The host name of the remote homeserver checking the event.
|
||||
event: The event to check.
|
||||
timeout: How long to try (in ms) the destination for before giving up.
|
||||
None indicates no timeout.
|
||||
|
||||
Returns:
|
||||
The full recommendation object from the remote server.
|
||||
"""
|
||||
logger.debug(
|
||||
"get_policy_recommendation_for_pdu dest=%s, event_id=%s",
|
||||
destination,
|
||||
event.event_id,
|
||||
)
|
||||
return await self.client.post_json(
|
||||
destination=destination,
|
||||
path=f"/_matrix/policy/unstable/org.matrix.msc4284/event/{event.event_id}/check",
|
||||
data=event.get_pdu_json(),
|
||||
ignore_backoff=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
async def backfill(
|
||||
self, destination: str, room_id: str, event_tuples: Collection[str], limit: int
|
||||
) -> Optional[Union[JsonDict, list]]:
|
||||
@@ -921,6 +894,46 @@ class TransportLayerClient:
|
||||
ip_address=ip_address,
|
||||
)
|
||||
|
||||
async def join_policy_server_get_url(
|
||||
self, policy_server: str, room_id: str, room_version: RoomVersion, user_id: str
|
||||
) -> JsonDict:
|
||||
path = _create_path(
|
||||
FEDERATION_UNSTABLE_PREFIX, "/re.jki.join_policy/request_join"
|
||||
)
|
||||
return await self.client.post_json(
|
||||
policy_server,
|
||||
path,
|
||||
data={
|
||||
"room_id": room_id,
|
||||
"room_version": room_version.identifier,
|
||||
"user_id": user_id,
|
||||
},
|
||||
ignore_backoff=True,
|
||||
)
|
||||
|
||||
async def join_policy_server_sign_join(
|
||||
self,
|
||||
policy_server: str,
|
||||
room_id: str,
|
||||
user_id: str,
|
||||
token: str,
|
||||
room_version: RoomVersion,
|
||||
event: EventBase,
|
||||
) -> JsonDict:
|
||||
path = _create_path(FEDERATION_UNSTABLE_PREFIX, "/re.jki.join_policy/sign_join")
|
||||
return await self.client.post_json(
|
||||
policy_server,
|
||||
path,
|
||||
data={
|
||||
"room_id": room_id,
|
||||
"user_id": user_id,
|
||||
"token": token,
|
||||
"room_version": room_version.identifier,
|
||||
"event": event.get_pdu_json(),
|
||||
},
|
||||
ignore_backoff=True,
|
||||
)
|
||||
|
||||
|
||||
def _create_path(federation_prefix: str, path: str, *args: str) -> str:
|
||||
"""
|
||||
|
||||
@@ -24,12 +24,10 @@ server protocol.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List, Optional
|
||||
|
||||
import attr
|
||||
|
||||
from synapse.api.constants import CANONICALJSON_MAX_INT, CANONICALJSON_MIN_INT
|
||||
from synapse.events import EventBase
|
||||
from synapse.types import JsonDict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -106,28 +104,8 @@ class Transaction:
|
||||
result = {
|
||||
"origin": self.origin,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"pdus": filter_pdus_for_valid_depth(self.pdus),
|
||||
"pdus": self.pdus,
|
||||
}
|
||||
if self.edus:
|
||||
result["edus"] = self.edus
|
||||
return result
|
||||
|
||||
|
||||
def filter_pdus_for_valid_depth(pdus: Sequence[JsonDict]) -> List[JsonDict]:
|
||||
filtered_pdus = []
|
||||
for pdu in pdus:
|
||||
# Drop PDUs that have a depth that is outside of the range allowed
|
||||
# by canonical json.
|
||||
if (
|
||||
"depth" in pdu
|
||||
and CANONICALJSON_MIN_INT <= pdu["depth"] <= CANONICALJSON_MAX_INT
|
||||
):
|
||||
filtered_pdus.append(pdu)
|
||||
|
||||
return filtered_pdus
|
||||
|
||||
|
||||
def serialize_and_filter_pdus(
|
||||
pdus: Sequence[EventBase], time_now: Optional[int] = None
|
||||
) -> List[JsonDict]:
|
||||
return filter_pdus_for_valid_depth([pdu.get_pdu_json(time_now) for pdu in pdus])
|
||||
|
||||
@@ -445,7 +445,7 @@ class AdminHandler:
|
||||
user_id,
|
||||
room,
|
||||
limit,
|
||||
["m.room.member", "m.room.message", "m.room.encrypted"],
|
||||
["m.room.member", "m.room.message"],
|
||||
)
|
||||
if not event_ids:
|
||||
# nothing to redact in this room
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#
|
||||
#
|
||||
import logging
|
||||
from threading import Lock
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
AbstractSet,
|
||||
@@ -164,8 +163,6 @@ class DeviceWorkerHandler:
|
||||
raise errors.NotFoundError()
|
||||
|
||||
ips = await self.store.get_last_client_ip_by_device(user_id, device_id)
|
||||
|
||||
device = dict(device)
|
||||
_update_device_from_client_ips(device, ips)
|
||||
|
||||
set_tag("device", str(device))
|
||||
@@ -1238,7 +1235,7 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
)
|
||||
|
||||
# Attempt to resync out of sync device lists every 30s.
|
||||
self._resync_retry_lock = Lock()
|
||||
self._resync_retry_in_progress = False
|
||||
self.clock.looping_call(
|
||||
run_as_background_process,
|
||||
30 * 1000,
|
||||
@@ -1420,10 +1417,13 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
"""Retry to resync device lists that are out of sync, except if another retry is
|
||||
in progress.
|
||||
"""
|
||||
# If the lock can not be acquired we want to always return immediately instead of blocking here
|
||||
if not self._resync_retry_lock.acquire(blocking=False):
|
||||
if self._resync_retry_in_progress:
|
||||
return
|
||||
|
||||
try:
|
||||
# Prevent another call of this function to retry resyncing device lists so
|
||||
# we don't send too many requests.
|
||||
self._resync_retry_in_progress = True
|
||||
# Get all of the users that need resyncing.
|
||||
need_resync = await self.store.get_user_ids_requiring_device_list_resync()
|
||||
|
||||
@@ -1464,7 +1464,8 @@ class DeviceListUpdater(DeviceListWorkerUpdater):
|
||||
e,
|
||||
)
|
||||
finally:
|
||||
self._resync_retry_lock.release()
|
||||
# Allow future calls to retry resyncinc out of sync device lists.
|
||||
self._resync_retry_in_progress = False
|
||||
|
||||
async def multi_user_device_resync(
|
||||
self, user_ids: List[str], mark_failed_as_stale: bool = True
|
||||
|
||||
@@ -158,37 +158,7 @@ class E2eKeysHandler:
|
||||
the number of in-flight queries at a time.
|
||||
"""
|
||||
async with self._query_devices_linearizer.queue((from_user_id, from_device_id)):
|
||||
|
||||
async def filter_device_key_query(
|
||||
query: Dict[str, List[str]],
|
||||
) -> Dict[str, List[str]]:
|
||||
if not self.config.experimental.msc4263_limit_key_queries_to_users_who_share_rooms:
|
||||
# Only ignore invalid user IDs, which is the same behaviour as if
|
||||
# the user existed but had no keys.
|
||||
return {
|
||||
user_id: v
|
||||
for user_id, v in query.items()
|
||||
if UserID.is_valid(user_id)
|
||||
}
|
||||
|
||||
# Strip invalid user IDs and user IDs the requesting user does not share rooms with.
|
||||
valid_user_ids = [
|
||||
user_id for user_id in query.keys() if UserID.is_valid(user_id)
|
||||
]
|
||||
allowed_user_ids = set(
|
||||
await self.store.do_users_share_a_room_joined_or_invited(
|
||||
from_user_id, valid_user_ids
|
||||
)
|
||||
)
|
||||
return {
|
||||
user_id: v
|
||||
for user_id, v in query.items()
|
||||
if user_id in allowed_user_ids
|
||||
}
|
||||
|
||||
device_keys_query: Dict[str, List[str]] = await filter_device_key_query(
|
||||
query_body.get("device_keys", {})
|
||||
)
|
||||
device_keys_query: Dict[str, List[str]] = query_body.get("device_keys", {})
|
||||
|
||||
# separate users by domain.
|
||||
# make a map from domain to user_id to device_ids
|
||||
@@ -196,6 +166,11 @@ class E2eKeysHandler:
|
||||
remote_queries = {}
|
||||
|
||||
for user_id, device_ids in device_keys_query.items():
|
||||
if not UserID.is_valid(user_id):
|
||||
# Ignore invalid user IDs, which is the same behaviour as if
|
||||
# the user existed but had no keys.
|
||||
continue
|
||||
|
||||
# we use UserID.from_string to catch invalid user ids
|
||||
if self.is_mine(UserID.from_string(user_id)):
|
||||
local_query[user_id] = device_ids
|
||||
@@ -1188,7 +1163,7 @@ class E2eKeysHandler:
|
||||
devices = devices[user_id]
|
||||
except SynapseError as e:
|
||||
failure = _exception_to_failure(e)
|
||||
failures[user_id] = dict.fromkeys(signatures.keys(), failure)
|
||||
failures[user_id] = {device: failure for device in signatures.keys()}
|
||||
return signature_list, failures
|
||||
|
||||
for device_id, device in signatures.items():
|
||||
@@ -1328,7 +1303,7 @@ class E2eKeysHandler:
|
||||
except SynapseError as e:
|
||||
failure = _exception_to_failure(e)
|
||||
for user, devicemap in signatures.items():
|
||||
failures[user] = dict.fromkeys(devicemap.keys(), failure)
|
||||
failures[user] = {device_id: failure for device_id in devicemap.keys()}
|
||||
return signature_list, failures
|
||||
|
||||
for target_user, devicemap in signatures.items():
|
||||
@@ -1369,7 +1344,9 @@ class E2eKeysHandler:
|
||||
# other devices were signed -- mark those as failures
|
||||
logger.debug("upload signature: too many devices specified")
|
||||
failure = _exception_to_failure(NotFoundError("Unknown device"))
|
||||
failures[target_user] = dict.fromkeys(other_devices, failure)
|
||||
failures[target_user] = {
|
||||
device: failure for device in other_devices
|
||||
}
|
||||
|
||||
if user_signing_key_id in master_key.get("signatures", {}).get(
|
||||
user_id, {}
|
||||
@@ -1390,7 +1367,9 @@ class E2eKeysHandler:
|
||||
except SynapseError as e:
|
||||
failure = _exception_to_failure(e)
|
||||
if device_id is None:
|
||||
failures[target_user] = dict.fromkeys(devicemap.keys(), failure)
|
||||
failures[target_user] = {
|
||||
device_id: failure for device_id in devicemap.keys()
|
||||
}
|
||||
else:
|
||||
failures.setdefault(target_user, {})[device_id] = failure
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user