mirror of
https://mau.dev/maunium/synapse.git
synced 2024-06-02 02:38:59 +02:00
Compare commits
150 commits
v1.105.0rc
...
master
Author | SHA1 | Date | |
---|---|---|---|
966a886b36 | |||
535e97da7b | |||
acfc71a2de | |||
f35bc08d39 | |||
048b912ac1 | |||
e0d420fbd1 | |||
f4ce030608 | |||
8b43cc89fa | |||
52af16c561 | |||
38f03a09ff | |||
c856ae4724 | |||
fe07995e69 | |||
52a649580f | |||
28a948f04f | |||
7cb3f8a979 | |||
fd12003441 | |||
5e892671a7 | |||
d2d48cce85 | |||
2359c64dec | |||
68dca8076f | |||
284d85dee3 | |||
fee7ccd51a | |||
ebe77381b0 | |||
0b91ccce47 | |||
ecf4e0674c | |||
7d82987b27 | |||
bd8d8865fb | |||
caf528477e | |||
f0c72d8e87 | |||
03a342b049 | |||
aa6345cb3b | |||
2b438df9b3 | |||
038b9ec59a | |||
59ac541310 | |||
a2e6f43f11 | |||
6f07fc4e00 | |||
4cf4a8281b | |||
ef7e040e54 | |||
393429d692 | |||
34a8652366 | |||
414ddcd457 | |||
4d408cb4dd | |||
212f150208 | |||
4c6e78fa14 | |||
1b155362ca | |||
522a40c4de | |||
dcd03d3b15 | |||
438bc23560 | |||
cf30cfe5d1 | |||
1726b49457 | |||
792cfe7ba6 | |||
c3682ff668 | |||
3e6ee8ff88 | |||
7c9ac01eb5 | |||
3818597751 | |||
3aadf43122 | |||
5b6a75935e | |||
c0ea2bf800 | |||
37558d5e4c | |||
0b358f8643 | |||
7254015665 | |||
1e10f437cf | |||
e84a493f41 | |||
07232e27a8 | |||
e26673fe97 | |||
7ab0f630da | |||
b548f7803a | |||
758aec6b34 | |||
c897ac63e9 | |||
38bc7a009d | |||
6a275828c8 | |||
6e373468a4 | |||
48ee17dc79 | |||
f6437ca1c4 | |||
02bda250f8 | |||
0fd6b269d3 | |||
ef1db42843 | |||
89fc579329 | |||
9c91873922 | |||
41fbe387d6 | |||
90cc9e5b29 | |||
516fd891ee | |||
0ef2315a99 | |||
59710437e4 | |||
9985aa6821 | |||
31742149d4 | |||
947e8a6cb0 | |||
0d4d00a07c | |||
3166445514 | |||
922656fc77 | |||
30c50e0240 | |||
48a90c697b | |||
47773232b0 | |||
2e92b718d5 | |||
646cb6ff24 | |||
f85f2a0455 | |||
0fe9e1f7da | |||
ae181233aa | |||
20c9e19519 | |||
55b0aa847a | |||
074ef4d75f | |||
301c9771c4 | |||
800a5b6ef3 | |||
8c667759ad | |||
14e9ab19be | |||
20c8991a94 | |||
dcae2b4ba4 | |||
98f57ea3f2 | |||
f5b6005559 | |||
47f3870894 | |||
6d64f1b2b8 | |||
1d47532310 | |||
09f0957b36 | |||
803f05f60c | |||
c8e0bed426 | |||
28f5ad07d3 | |||
f0d6f14047 | |||
3a196b3227 | |||
4d5f585dee | |||
fbb2573525 | |||
259442fa4c | |||
fe4719a268 | |||
3a30846bd0 | |||
15947bbd71 | |||
698ceabe2a | |||
67b2fad49e | |||
f2f54cb6af | |||
2ba175485f | |||
14c2066db6 | |||
15d050f5f4 | |||
aef880992a | |||
1cf18958a4 | |||
3a8e8c750c | |||
3568fb0874 | |||
9354d32fc9 | |||
0d0f138bbf | |||
0f5e09524d | |||
1b784b06d4 | |||
f4f711f28b | |||
de89885d15 | |||
3108b67232 | |||
b07561405c | |||
9eb9372eb4 | |||
ab635c80a7 | |||
5e7ff45534 | |||
0de822af4d | |||
83f9a6cdd5 | |||
78584d476c | |||
ce38046124 | |||
e95889bab3 |
|
@ -8,6 +8,7 @@
|
||||||
!README.rst
|
!README.rst
|
||||||
!pyproject.toml
|
!pyproject.toml
|
||||||
!poetry.lock
|
!poetry.lock
|
||||||
|
!requirements.txt
|
||||||
!Cargo.lock
|
!Cargo.lock
|
||||||
!Cargo.toml
|
!Cargo.toml
|
||||||
!build_rust.py
|
!build_rust.py
|
||||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
||||||
run: docker buildx inspect
|
run: docker buildx inspect
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@v3.4.0
|
uses: sigstore/cosign-installer@v3.5.0
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
4
.github/workflows/docs-pr.yaml
vendored
4
.github/workflows/docs-pr.yaml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup mdbook
|
- name: Setup mdbook
|
||||||
uses: peaceiris/actions-mdbook@adeb05db28a0c0004681db83893d56c0388ea9ea # v1.2.0
|
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
|
||||||
with:
|
with:
|
||||||
mdbook-version: '0.4.17'
|
mdbook-version: '0.4.17'
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup mdbook
|
- name: Setup mdbook
|
||||||
uses: peaceiris/actions-mdbook@adeb05db28a0c0004681db83893d56c0388ea9ea # v1.2.0
|
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
|
||||||
with:
|
with:
|
||||||
mdbook-version: '0.4.17'
|
mdbook-version: '0.4.17'
|
||||||
|
|
||||||
|
|
34
.github/workflows/docs.yaml
vendored
34
.github/workflows/docs.yaml
vendored
|
@ -56,7 +56,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup mdbook
|
- name: Setup mdbook
|
||||||
uses: peaceiris/actions-mdbook@adeb05db28a0c0004681db83893d56c0388ea9ea # v1.2.0
|
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
|
||||||
with:
|
with:
|
||||||
mdbook-version: '0.4.17'
|
mdbook-version: '0.4.17'
|
||||||
|
|
||||||
|
@ -80,38 +80,8 @@ jobs:
|
||||||
|
|
||||||
# Deploy to the target directory.
|
# Deploy to the target directory.
|
||||||
- name: Deploy to gh pages
|
- name: Deploy to gh pages
|
||||||
uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3
|
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: ./book
|
publish_dir: ./book
|
||||||
destination_dir: ./${{ needs.pre.outputs.branch-version }}
|
destination_dir: ./${{ needs.pre.outputs.branch-version }}
|
||||||
|
|
||||||
################################################################################
|
|
||||||
pages-devdocs:
|
|
||||||
name: GitHub Pages (developer docs)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- pre
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: "Set up Sphinx"
|
|
||||||
uses: matrix-org/setup-python-poetry@v1
|
|
||||||
with:
|
|
||||||
python-version: "3.x"
|
|
||||||
poetry-version: "1.3.2"
|
|
||||||
groups: "dev-docs"
|
|
||||||
extras: ""
|
|
||||||
|
|
||||||
- name: Build the documentation
|
|
||||||
run: |
|
|
||||||
cd dev-docs
|
|
||||||
poetry run make html
|
|
||||||
|
|
||||||
# Deploy to the target directory.
|
|
||||||
- name: Deploy to gh pages
|
|
||||||
uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
publish_dir: ./dev-docs/_build/html
|
|
||||||
destination_dir: ./dev-docs/${{ needs.pre.outputs.branch-version }}
|
|
||||||
|
|
18
.github/workflows/tests.yml
vendored
18
.github/workflows/tests.yml
vendored
|
@ -81,7 +81,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- uses: matrix-org/setup-python-poetry@v1
|
- uses: matrix-org/setup-python-poetry@v1
|
||||||
with:
|
with:
|
||||||
|
@ -148,7 +148,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Setup Poetry
|
- name: Setup Poetry
|
||||||
|
@ -208,7 +208,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- uses: matrix-org/setup-python-poetry@v1
|
- uses: matrix-org/setup-python-poetry@v1
|
||||||
with:
|
with:
|
||||||
|
@ -225,7 +225,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
@ -344,7 +344,7 @@ jobs:
|
||||||
postgres:${{ matrix.job.postgres-version }}
|
postgres:${{ matrix.job.postgres-version }}
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- uses: matrix-org/setup-python-poetry@v1
|
- uses: matrix-org/setup-python-poetry@v1
|
||||||
|
@ -386,7 +386,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
# There aren't wheels for some of the older deps, so we need to install
|
# There aren't wheels for some of the older deps, so we need to install
|
||||||
|
@ -498,7 +498,7 @@ jobs:
|
||||||
run: cat sytest-blacklist .ci/worker-blacklist > synapse-blacklist-with-workers
|
run: cat sytest-blacklist .ci/worker-blacklist > synapse-blacklist-with-workers
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Run SyTest
|
- name: Run SyTest
|
||||||
|
@ -642,7 +642,7 @@ jobs:
|
||||||
path: synapse
|
path: synapse
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Prepare Complement's Prerequisites
|
- name: Prepare Complement's Prerequisites
|
||||||
|
@ -674,7 +674,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.66.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- run: cargo test
|
- run: cargo test
|
||||||
|
|
19
.gitlab-ci.yml
Normal file
19
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
image: docker:stable
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
|
||||||
|
build amd64:
|
||||||
|
stage: build
|
||||||
|
tags:
|
||||||
|
- amd64
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
before_script:
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
script:
|
||||||
|
- synversion=$(cat pyproject.toml | grep '^version =' | sed -E 's/^version = "(.+)"$/\1/')
|
||||||
|
- docker build --tag $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$synversion .
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:latest
|
||||||
|
- docker push $CI_REGISTRY_IMAGE:$synversion
|
||||||
|
- docker rmi $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$synversion
|
188
CHANGES.md
188
CHANGES.md
|
@ -1,3 +1,191 @@
|
||||||
|
# Synapse 1.108.0 (2024-05-28)
|
||||||
|
|
||||||
|
No significant changes since 1.108.0rc1.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Synapse 1.108.0rc1 (2024-05-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add a feature that allows clients to query the configured federation whitelist. Disabled by default. ([\#16848](https://github.com/element-hq/synapse/issues/16848), [\#17199](https://github.com/element-hq/synapse/issues/17199))
|
||||||
|
- Add the ability to allow numeric user IDs with a specific prefix when in the CAS flow. Contributed by Aurélien Grimpard. ([\#17098](https://github.com/element-hq/synapse/issues/17098))
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- Fix bug where push rules would be empty in `/sync` for some accounts. Introduced in v1.93.0. ([\#17142](https://github.com/element-hq/synapse/issues/17142))
|
||||||
|
- Add support for optional whitespace around the Federation API's `Authorization` header's parameter commas. ([\#17145](https://github.com/element-hq/synapse/issues/17145))
|
||||||
|
- Fix bug where disabling room publication prevented public rooms being created on workers. ([\#17177](https://github.com/element-hq/synapse/issues/17177), [\#17184](https://github.com/element-hq/synapse/issues/17184))
|
||||||
|
|
||||||
|
### Improved Documentation
|
||||||
|
|
||||||
|
- Document [`/v1/make_knock`](https://spec.matrix.org/v1.10/server-server-api/#get_matrixfederationv1make_knockroomiduserid) and [`/v1/send_knock/`](https://spec.matrix.org/v1.10/server-server-api/#put_matrixfederationv1send_knockroomideventid) federation endpoints as worker-compatible. ([\#17058](https://github.com/element-hq/synapse/issues/17058))
|
||||||
|
- Update User Admin API with note about prefixing OIDC external_id providers. ([\#17139](https://github.com/element-hq/synapse/issues/17139))
|
||||||
|
- Clarify the state of the created room when using the `autocreate_auto_join_room_preset` config option. ([\#17150](https://github.com/element-hq/synapse/issues/17150))
|
||||||
|
- Update the Admin FAQ with the current libjemalloc version for latest Debian stable. Additionally update the name of the "push_rules" stream in the Workers documentation. ([\#17171](https://github.com/element-hq/synapse/issues/17171))
|
||||||
|
|
||||||
|
### Internal Changes
|
||||||
|
|
||||||
|
- Add note to reflect that [MSC3886](https://github.com/matrix-org/matrix-spec-proposals/pull/3886) is closed but will remain supported for some time. ([\#17151](https://github.com/element-hq/synapse/issues/17151))
|
||||||
|
- Update dependency PyO3 to 0.21. ([\#17162](https://github.com/element-hq/synapse/issues/17162))
|
||||||
|
- Fixes linter errors found in PR #17147. ([\#17166](https://github.com/element-hq/synapse/issues/17166))
|
||||||
|
- Bump black from 24.2.0 to 24.4.2. ([\#17170](https://github.com/element-hq/synapse/issues/17170))
|
||||||
|
- Cache literal sync filter validation for performance. ([\#17186](https://github.com/element-hq/synapse/issues/17186))
|
||||||
|
- Improve performance by fixing a reactor pause. ([\#17192](https://github.com/element-hq/synapse/issues/17192))
|
||||||
|
- Route `/make_knock` and `/send_knock` federation APIs to the federation reader worker in Complement test runs. ([\#17195](https://github.com/element-hq/synapse/issues/17195))
|
||||||
|
- Prepare sync handler to be able to return different sync responses (`SyncVersion`). ([\#17200](https://github.com/element-hq/synapse/issues/17200))
|
||||||
|
- Organize the sync cache key parameter outside of the sync config (separate concerns). ([\#17201](https://github.com/element-hq/synapse/issues/17201))
|
||||||
|
- Refactor `SyncResultBuilder` assembly to its own function. ([\#17202](https://github.com/element-hq/synapse/issues/17202))
|
||||||
|
- Rename to be obvious: `joined_rooms` -> `joined_room_ids`. ([\#17203](https://github.com/element-hq/synapse/issues/17203), [\#17208](https://github.com/element-hq/synapse/issues/17208))
|
||||||
|
- Add a short pause when rate-limiting a request. ([\#17210](https://github.com/element-hq/synapse/issues/17210))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Updates to locked dependencies
|
||||||
|
|
||||||
|
* Bump cryptography from 42.0.5 to 42.0.7. ([\#17180](https://github.com/element-hq/synapse/issues/17180))
|
||||||
|
* Bump gitpython from 3.1.41 to 3.1.43. ([\#17181](https://github.com/element-hq/synapse/issues/17181))
|
||||||
|
* Bump immutabledict from 4.1.0 to 4.2.0. ([\#17179](https://github.com/element-hq/synapse/issues/17179))
|
||||||
|
* Bump sentry-sdk from 1.40.3 to 2.1.1. ([\#17178](https://github.com/element-hq/synapse/issues/17178))
|
||||||
|
* Bump serde from 1.0.200 to 1.0.201. ([\#17183](https://github.com/element-hq/synapse/issues/17183))
|
||||||
|
* Bump serde_json from 1.0.116 to 1.0.117. ([\#17182](https://github.com/element-hq/synapse/issues/17182))
|
||||||
|
|
||||||
|
Synapse 1.107.0 (2024-05-14)
|
||||||
|
============================
|
||||||
|
|
||||||
|
No significant changes since 1.107.0rc1.
|
||||||
|
|
||||||
|
|
||||||
|
# Synapse 1.107.0rc1 (2024-05-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add preliminary support for [MSC3823: Account Suspension](https://github.com/matrix-org/matrix-spec-proposals/pull/3823). ([\#17051](https://github.com/element-hq/synapse/issues/17051))
|
||||||
|
- Declare support for [Matrix v1.10](https://matrix.org/blog/2024/03/22/matrix-v1.10-release/). Contributed by @clokep. ([\#17082](https://github.com/element-hq/synapse/issues/17082))
|
||||||
|
- Add support for [MSC4115: membership metadata on events](https://github.com/matrix-org/matrix-spec-proposals/pull/4115). ([\#17104](https://github.com/element-hq/synapse/issues/17104), [\#17137](https://github.com/element-hq/synapse/issues/17137))
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- Fixed search feature of Element Android on homesevers using SQLite by returning search terms as search highlights. ([\#17000](https://github.com/element-hq/synapse/issues/17000))
|
||||||
|
- Fixes a bug introduced in v1.52.0 where the `destination` query parameter for the [Destination Rooms Admin API](https://element-hq.github.io/synapse/v1.105/usage/administration/admin_api/federation.html#destination-rooms) failed to actually filter returned rooms. ([\#17077](https://github.com/element-hq/synapse/issues/17077))
|
||||||
|
- For MSC3266 room summaries, support queries at the recommended endpoint of `/_matrix/client/unstable/im.nheko.summary/summary/{roomIdOrAlias}`. The existing endpoint of `/_matrix/client/unstable/im.nheko.summary/rooms/{roomIdOrAlias}/summary` is deprecated. ([\#17078](https://github.com/element-hq/synapse/issues/17078))
|
||||||
|
- Apply user email & picture during OIDC registration if present & selected. ([\#17120](https://github.com/element-hq/synapse/issues/17120))
|
||||||
|
- Improve error message for cross signing reset with [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) enabled. ([\#17121](https://github.com/element-hq/synapse/issues/17121))
|
||||||
|
- Fix a bug which meant that to-device messages received over federation could be dropped when the server was under load or networking problems caused problems between Synapse processes or the database. ([\#17127](https://github.com/element-hq/synapse/issues/17127))
|
||||||
|
- Fix bug where `StreamChangeCache` would not respect configured cache factors. ([\#17152](https://github.com/element-hq/synapse/issues/17152))
|
||||||
|
|
||||||
|
### Updates to the Docker image
|
||||||
|
|
||||||
|
- Correct licensing metadata on Docker image. ([\#17141](https://github.com/element-hq/synapse/issues/17141))
|
||||||
|
|
||||||
|
### Improved Documentation
|
||||||
|
|
||||||
|
- Update the `event_cache_size` and `global_factor` configuration options' documentation. ([\#17071](https://github.com/element-hq/synapse/issues/17071))
|
||||||
|
- Remove broken sphinx docs. ([\#17073](https://github.com/element-hq/synapse/issues/17073), [\#17148](https://github.com/element-hq/synapse/issues/17148))
|
||||||
|
- Add RuntimeDirectory to example matrix-synapse.service systemd unit. ([\#17084](https://github.com/element-hq/synapse/issues/17084))
|
||||||
|
- Fix various small typos throughout the docs. ([\#17114](https://github.com/element-hq/synapse/issues/17114))
|
||||||
|
- Update enable_notifs configuration documentation. ([\#17116](https://github.com/element-hq/synapse/issues/17116))
|
||||||
|
- Update the Upgrade Notes with the latest minimum supported Rust version of 1.66.0. Contributed by @jahway603. ([\#17140](https://github.com/element-hq/synapse/issues/17140))
|
||||||
|
|
||||||
|
### Internal Changes
|
||||||
|
|
||||||
|
- Enable [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266) by default in the Synapse Complement image. ([\#17105](https://github.com/element-hq/synapse/issues/17105))
|
||||||
|
- Add optimisation to `StreamChangeCache.get_entities_changed(..)`. ([\#17130](https://github.com/element-hq/synapse/issues/17130))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Updates to locked dependencies
|
||||||
|
|
||||||
|
* Bump furo from 2024.1.29 to 2024.4.27. ([\#17133](https://github.com/element-hq/synapse/issues/17133))
|
||||||
|
* Bump idna from 3.6 to 3.7. ([\#17136](https://github.com/element-hq/synapse/issues/17136))
|
||||||
|
* Bump jsonschema from 4.21.1 to 4.22.0. ([\#17157](https://github.com/element-hq/synapse/issues/17157))
|
||||||
|
* Bump lxml from 5.1.0 to 5.2.1. ([\#17158](https://github.com/element-hq/synapse/issues/17158))
|
||||||
|
* Bump phonenumbers from 8.13.29 to 8.13.35. ([\#17106](https://github.com/element-hq/synapse/issues/17106))
|
||||||
|
- Bump pillow from 10.2.0 to 10.3.0. ([\#17146](https://github.com/element-hq/synapse/issues/17146))
|
||||||
|
* Bump pydantic from 2.6.4 to 2.7.0. ([\#17107](https://github.com/element-hq/synapse/issues/17107))
|
||||||
|
* Bump pydantic from 2.7.0 to 2.7.1. ([\#17160](https://github.com/element-hq/synapse/issues/17160))
|
||||||
|
* Bump pyicu from 2.12 to 2.13. ([\#17109](https://github.com/element-hq/synapse/issues/17109))
|
||||||
|
* Bump serde from 1.0.197 to 1.0.198. ([\#17111](https://github.com/element-hq/synapse/issues/17111))
|
||||||
|
* Bump serde from 1.0.198 to 1.0.199. ([\#17132](https://github.com/element-hq/synapse/issues/17132))
|
||||||
|
* Bump serde from 1.0.199 to 1.0.200. ([\#17161](https://github.com/element-hq/synapse/issues/17161))
|
||||||
|
* Bump serde_json from 1.0.115 to 1.0.116. ([\#17112](https://github.com/element-hq/synapse/issues/17112))
|
||||||
|
- Update `tornado` Python dependency from 6.2 to 6.4. ([\#17131](https://github.com/element-hq/synapse/issues/17131))
|
||||||
|
* Bump twisted from 23.10.0 to 24.3.0. ([\#17135](https://github.com/element-hq/synapse/issues/17135))
|
||||||
|
* Bump types-bleach from 6.1.0.1 to 6.1.0.20240331. ([\#17110](https://github.com/element-hq/synapse/issues/17110))
|
||||||
|
* Bump types-pillow from 10.2.0.20240415 to 10.2.0.20240423. ([\#17159](https://github.com/element-hq/synapse/issues/17159))
|
||||||
|
* Bump types-setuptools from 69.0.0.20240125 to 69.5.0.20240423. ([\#17134](https://github.com/element-hq/synapse/issues/17134))
|
||||||
|
|
||||||
|
# Synapse 1.106.0 (2024-04-30)
|
||||||
|
|
||||||
|
No significant changes since 1.106.0rc1.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Synapse 1.106.0rc1 (2024-04-25)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Send an email if the address is already bound to an user account. ([\#16819](https://github.com/element-hq/synapse/issues/16819))
|
||||||
|
- Implement the rendezvous mechanism described by [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/issues/4108). ([\#17056](https://github.com/element-hq/synapse/issues/17056))
|
||||||
|
- Support delegating the rendezvous mechanism described [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/issues/4108) to an external implementation. ([\#17086](https://github.com/element-hq/synapse/issues/17086))
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
- Add validation to ensure that the `limit` parameter on `/publicRooms` is non-negative. ([\#16920](https://github.com/element-hq/synapse/issues/16920))
|
||||||
|
- Return `400 M_NOT_JSON` upon receiving invalid JSON in query parameters across various client and admin endpoints, rather than an internal server error. ([\#16923](https://github.com/element-hq/synapse/issues/16923))
|
||||||
|
- Make the CSAPI endpoint `/keys/device_signing/upload` idempotent. ([\#16943](https://github.com/element-hq/synapse/issues/16943))
|
||||||
|
- Redact membership events if the user requested erasure upon deactivating. ([\#17076](https://github.com/element-hq/synapse/issues/17076))
|
||||||
|
|
||||||
|
### Improved Documentation
|
||||||
|
|
||||||
|
- Add a prompt in the contributing guide to manually configure icu4c. ([\#17069](https://github.com/element-hq/synapse/issues/17069))
|
||||||
|
- Clarify what part of message retention is still experimental. ([\#17099](https://github.com/element-hq/synapse/issues/17099))
|
||||||
|
|
||||||
|
### Internal Changes
|
||||||
|
|
||||||
|
- Use new receipts column to optimise receipt and push action SQL queries. Contributed by Nick @ Beeper (@fizzadar). ([\#17032](https://github.com/element-hq/synapse/issues/17032), [\#17096](https://github.com/element-hq/synapse/issues/17096))
|
||||||
|
- Fix mypy with latest Twisted release. ([\#17036](https://github.com/element-hq/synapse/issues/17036))
|
||||||
|
- Bump minimum supported Rust version to 1.66.0. ([\#17079](https://github.com/element-hq/synapse/issues/17079))
|
||||||
|
- Add helpers to transform Twisted requests to Rust http Requests/Responses. ([\#17081](https://github.com/element-hq/synapse/issues/17081))
|
||||||
|
- Fix type annotation for `visited_chains` after `mypy` upgrade. ([\#17125](https://github.com/element-hq/synapse/issues/17125))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Updates to locked dependencies
|
||||||
|
|
||||||
|
* Bump anyhow from 1.0.81 to 1.0.82. ([\#17095](https://github.com/element-hq/synapse/issues/17095))
|
||||||
|
* Bump peaceiris/actions-gh-pages from 3.9.3 to 4.0.0. ([\#17087](https://github.com/element-hq/synapse/issues/17087))
|
||||||
|
* Bump peaceiris/actions-mdbook from 1.2.0 to 2.0.0. ([\#17089](https://github.com/element-hq/synapse/issues/17089))
|
||||||
|
* Bump pyasn1-modules from 0.3.0 to 0.4.0. ([\#17093](https://github.com/element-hq/synapse/issues/17093))
|
||||||
|
* Bump pygithub from 2.2.0 to 2.3.0. ([\#17092](https://github.com/element-hq/synapse/issues/17092))
|
||||||
|
* Bump ruff from 0.3.5 to 0.3.7. ([\#17094](https://github.com/element-hq/synapse/issues/17094))
|
||||||
|
* Bump sigstore/cosign-installer from 3.4.0 to 3.5.0. ([\#17088](https://github.com/element-hq/synapse/issues/17088))
|
||||||
|
* Bump twine from 4.0.2 to 5.0.0. ([\#17091](https://github.com/element-hq/synapse/issues/17091))
|
||||||
|
* Bump types-pillow from 10.2.0.20240406 to 10.2.0.20240415. ([\#17090](https://github.com/element-hq/synapse/issues/17090))
|
||||||
|
|
||||||
|
# Synapse 1.105.1 (2024-04-23)
|
||||||
|
|
||||||
|
## Security advisory
|
||||||
|
|
||||||
|
The following issues are fixed in 1.105.1.
|
||||||
|
|
||||||
|
- [GHSA-3h7q-rfh9-xm4v](https://github.com/element-hq/synapse/security/advisories/GHSA-3h7q-rfh9-xm4v) / [CVE-2024-31208](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-31208) — High Severity
|
||||||
|
|
||||||
|
Weakness in auth chain indexing allows DoS from remote room members through disk fill and high CPU usage.
|
||||||
|
|
||||||
|
See the advisories for more details. If you have any questions, email security@element.io.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Synapse 1.105.0 (2024-04-16)
|
||||||
|
|
||||||
|
No significant changes since 1.105.0rc1.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Synapse 1.105.0rc1 (2024-04-11)
|
# Synapse 1.105.0rc1 (2024-04-11)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
471
Cargo.lock
generated
471
Cargo.lock
generated
|
@ -4,36 +4,42 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.0.2"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.81"
|
version = "1.0.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.5.1"
|
version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
|
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake2"
|
name = "blake2"
|
||||||
|
@ -46,19 +52,40 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.3"
|
version = "0.10.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -71,9 +98,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.5"
|
version = "0.10.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
@ -81,15 +108,58 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "fnv"
|
||||||
version = "0.14.6"
|
version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"headers-core",
|
||||||
|
"http",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"sha1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers-core"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -103,16 +173,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indoc"
|
name = "http"
|
||||||
version = "2.0.4"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.4"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
|
@ -122,15 +218,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.135"
|
version = "0.2.154"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
|
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
@ -144,30 +240,36 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.6.3"
|
version = "2.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.9.0"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "mime"
|
||||||
version = "1.15.0"
|
version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
|
@ -175,15 +277,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.3"
|
version = "0.9.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -193,19 +295,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
|
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "ppv-lite86"
|
||||||
version = "1.0.76"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3"
|
name = "pyo3"
|
||||||
version = "0.20.3"
|
version = "0.21.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233"
|
checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
@ -222,9 +330,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-build-config"
|
name = "pyo3-build-config"
|
||||||
version = "0.20.3"
|
version = "0.21.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7"
|
checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
|
@ -232,9 +340,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-ffi"
|
name = "pyo3-ffi"
|
||||||
version = "0.20.3"
|
version = "0.21.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa"
|
checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"pyo3-build-config",
|
"pyo3-build-config",
|
||||||
|
@ -242,9 +350,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-log"
|
name = "pyo3-log"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd"
|
checksum = "2af49834b8d2ecd555177e63b273b708dea75150abc6f5341d0a6e1a9623976c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"log",
|
"log",
|
||||||
|
@ -253,9 +361,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-macros"
|
name = "pyo3-macros"
|
||||||
version = "0.20.3"
|
version = "0.21.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158"
|
checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"pyo3-macros-backend",
|
"pyo3-macros-backend",
|
||||||
|
@ -265,9 +373,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyo3-macros-backend"
|
name = "pyo3-macros-backend"
|
||||||
version = "0.20.3"
|
version = "0.21.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185"
|
checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -278,9 +386,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pythonize"
|
name = "pythonize"
|
||||||
version = "0.20.0"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffd1c3ef39c725d63db5f9bc455461bafd80540cb7824c61afb823501921a850"
|
checksum = "9d0664248812c38cc55a4ed07f88e4df516ce82604b93b1ffdc041aa77a6cb3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pyo3",
|
"pyo3",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -288,18 +396,48 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.35"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "rand"
|
||||||
version = "0.2.16"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
@ -318,9 +456,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.4"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
|
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -329,36 +467,36 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.11"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.201"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.197"
|
version = "1.0.201"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -367,9 +505,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.115"
|
version = "1.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -377,22 +515,44 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "sha1"
|
||||||
version = "1.10.0"
|
version = "0.10.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.4.1"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.48"
|
version = "2.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -404,35 +564,53 @@ name = "synapse"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64",
|
||||||
"blake2",
|
"blake2",
|
||||||
|
"bytes",
|
||||||
|
"headers",
|
||||||
"hex",
|
"hex",
|
||||||
|
"http",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
"mime",
|
||||||
"pyo3",
|
"pyo3",
|
||||||
"pyo3-log",
|
"pyo3-log",
|
||||||
"pythonize",
|
"pythonize",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"ulid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.4"
|
version = "0.12.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1"
|
checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.15.0"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ulid"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34778c17965aa2a08913b57e1f34db9b4a63f5de31768b55bf20d2795f921259"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"rand",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.5"
|
version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unindent"
|
name = "unindent"
|
||||||
|
@ -447,44 +625,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "wasi"
|
||||||
version = "0.36.1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.36.1"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.36.1"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.36.1"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.36.1"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.36.1"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
61
Dockerfile
Normal file
61
Dockerfile
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
ARG PYTHON_VERSION=3.11
|
||||||
|
|
||||||
|
FROM docker.io/python:${PYTHON_VERSION}-slim as builder
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
libffi-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libpq-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libwebp-dev \
|
||||||
|
libxml++2.6-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
openssl \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV RUSTUP_HOME=/rust
|
||||||
|
ENV CARGO_HOME=/cargo
|
||||||
|
ENV PATH=/cargo/bin:/rust/bin:$PATH
|
||||||
|
RUN mkdir /rust /cargo
|
||||||
|
|
||||||
|
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable
|
||||||
|
|
||||||
|
COPY synapse /synapse/synapse/
|
||||||
|
COPY rust /synapse/rust/
|
||||||
|
COPY README.rst pyproject.toml requirements.txt build_rust.py /synapse/
|
||||||
|
|
||||||
|
RUN pip install --prefix="/install" --no-warn-script-location --ignore-installed \
|
||||||
|
--no-deps -r /synapse/requirements.txt \
|
||||||
|
&& pip install --prefix="/install" --no-warn-script-location \
|
||||||
|
--no-deps \
|
||||||
|
'git+https://github.com/maunium/synapse-simple-antispam#egg=synapse-simple-antispam' \
|
||||||
|
'git+https://github.com/devture/matrix-synapse-shared-secret-auth@2.0.3#egg=shared_secret_authenticator' \
|
||||||
|
&& pip install --prefix="/install" --no-warn-script-location \
|
||||||
|
--no-deps /synapse
|
||||||
|
|
||||||
|
FROM docker.io/python:${PYTHON_VERSION}-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
libjpeg62-turbo \
|
||||||
|
libpq5 \
|
||||||
|
libwebp7 \
|
||||||
|
xmlsec1 \
|
||||||
|
libjemalloc2 \
|
||||||
|
openssl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /install /usr/local
|
||||||
|
|
||||||
|
VOLUME ["/data"]
|
||||||
|
ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
|
||||||
|
|
||||||
|
ENTRYPOINT ["python3", "-m", "synapse.app.homeserver"]
|
||||||
|
CMD ["--keys-directory", "/data", "-c", "/data/homeserver.yaml"]
|
||||||
|
|
||||||
|
HEALTHCHECK --start-period=5s --interval=1m --timeout=5s \
|
||||||
|
CMD curl -fSs http://localhost:8008/health || exit 1
|
63
README.md
Normal file
63
README.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Maunium Synapse
|
||||||
|
This is a fork of [Synapse] to remove dumb limits and fix bugs that the
|
||||||
|
upstream devs don't want to fix.
|
||||||
|
|
||||||
|
The only official distribution is the docker image in the [GitLab container
|
||||||
|
registry], but you can also install from source ([upstream instructions]).
|
||||||
|
|
||||||
|
The master branch and `:latest` docker tag are upgraded to each upstream
|
||||||
|
release candidate very soon after release (usually within 10 minutes†). There
|
||||||
|
are also docker tags for each release, e.g. `:1.75.0`. If you don't want RCs,
|
||||||
|
use the specific release tags.
|
||||||
|
|
||||||
|
†If there are merge conflicts, the update may be delayed for up to a few days
|
||||||
|
after the full release.
|
||||||
|
|
||||||
|
[Synapse]: https://github.com/matrix-org/synapse
|
||||||
|
[GitLab container registry]: https://mau.dev/maunium/synapse/container_registry
|
||||||
|
[upstream instructions]: https://github.com/matrix-org/synapse/blob/develop/INSTALL.md#installing-from-source
|
||||||
|
|
||||||
|
## List of changes
|
||||||
|
* Default power level for room creator is 9001 instead of 100.
|
||||||
|
* Room creator can specify a custom room ID with the `room_id` param in the
|
||||||
|
request body. If the room ID is already in use, it will return `M_CONFLICT`.
|
||||||
|
* ~~URL previewer user agent includes `Bot` so Twitter previews work properly.~~
|
||||||
|
Upstreamed after over 2 years 🎉
|
||||||
|
* ~~Local event creation concurrency is disabled to avoid unnecessary state
|
||||||
|
resolution.~~ Upstreamed after over 3 years 🎉
|
||||||
|
* Register admin API can register invalid user IDs.
|
||||||
|
* Docker image with jemalloc enabled by default.
|
||||||
|
* Config option to allow specific users to send events without unnecessary
|
||||||
|
validation.
|
||||||
|
* Config option to allow specific users to receive events that are usually
|
||||||
|
filtered away (e.g. `org.matrix.dummy_event` and `m.room.aliases`).
|
||||||
|
* Config option to allow specific users to use timestamp massaging without
|
||||||
|
being appservice users.
|
||||||
|
* Removed bad pusher URL validation.
|
||||||
|
* webp images are thumbnailed to webp instead of jpeg to avoid losing
|
||||||
|
transparency.
|
||||||
|
* Media repo `Cache-Control` header says `immutable` and 1 year for all media
|
||||||
|
that exists, as media IDs in Matrix are immutable.
|
||||||
|
* Allowed sending custom data with read receipts.
|
||||||
|
|
||||||
|
You can view the full list of changes on the [meow-patchset] branch.
|
||||||
|
Additionally, historical patch sets are saved as `meow-patchset-vX` [tags].
|
||||||
|
|
||||||
|
[meow-patchset]: https://mau.dev/maunium/synapse/-/compare/patchset-base...meow-patchset
|
||||||
|
[tags]: https://mau.dev/maunium/synapse/-/tags?search=meow-patchset&sort=updated_desc
|
||||||
|
|
||||||
|
## Configuration reference
|
||||||
|
```yaml
|
||||||
|
meow:
|
||||||
|
# List of users who aren't subject to unnecessary validation in the C-S API.
|
||||||
|
validation_override:
|
||||||
|
- "@you:example.com"
|
||||||
|
# List of users who will get org.matrix.dummy_event and m.room.aliases events down /sync
|
||||||
|
filter_override:
|
||||||
|
- "@you:example.com"
|
||||||
|
# Whether or not the admin API should be able to register invalid user IDs.
|
||||||
|
admin_api_register_invalid: true
|
||||||
|
# List of users who can use timestamp massaging without being appservices
|
||||||
|
timestamp_override:
|
||||||
|
- "@you:example.com"
|
||||||
|
```
|
48
debian/changelog
vendored
48
debian/changelog
vendored
|
@ -1,3 +1,51 @@
|
||||||
|
matrix-synapse-py3 (1.108.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.108.0.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 28 May 2024 11:54:22 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.108.0~rc1) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.108.0rc1.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 21 May 2024 10:54:13 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.107.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.107.0.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 14 May 2024 14:15:34 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.107.0~rc1) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.107.0rc1.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 07 May 2024 16:26:26 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.106.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.106.0.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 30 Apr 2024 11:51:43 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.106.0~rc1) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.106.0rc1.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Thu, 25 Apr 2024 15:54:59 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.105.1) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.105.1.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 23 Apr 2024 15:56:18 +0100
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.105.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* New Synapse release 1.105.0.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Tue, 16 Apr 2024 15:53:23 +0100
|
||||||
|
|
||||||
matrix-synapse-py3 (1.105.0~rc1) stable; urgency=medium
|
matrix-synapse-py3 (1.105.0~rc1) stable; urgency=medium
|
||||||
|
|
||||||
* New Synapse release 1.105.0rc1.
|
* New Synapse release 1.105.0rc1.
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Minimal makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line, and also
|
|
||||||
# from the environment for the first two.
|
|
||||||
SPHINXOPTS ?=
|
|
||||||
SPHINXBUILD ?= sphinx-build
|
|
||||||
SOURCEDIR = .
|
|
||||||
BUILDDIR = _build
|
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
|
||||||
help:
|
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
||||||
|
|
||||||
.PHONY: help Makefile
|
|
||||||
|
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
||||||
%: Makefile
|
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
|
@ -1,50 +0,0 @@
|
||||||
# Configuration file for the Sphinx documentation builder.
|
|
||||||
#
|
|
||||||
# For the full list of built-in configuration values, see the documentation:
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
||||||
|
|
||||||
project = "Synapse development"
|
|
||||||
copyright = "2023, The Matrix.org Foundation C.I.C."
|
|
||||||
author = "The Synapse Maintainers and Community"
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
||||||
|
|
||||||
extensions = [
|
|
||||||
"autodoc2",
|
|
||||||
"myst_parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
templates_path = ["_templates"]
|
|
||||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Autodoc2 ----------------------------------------------------
|
|
||||||
|
|
||||||
autodoc2_docstring_parser_regexes = [
|
|
||||||
# this will render all docstrings as 'MyST' Markdown
|
|
||||||
(r".*", "myst"),
|
|
||||||
]
|
|
||||||
|
|
||||||
autodoc2_packages = [
|
|
||||||
{
|
|
||||||
"path": "../synapse",
|
|
||||||
# Don't render documentation for everything as a matter of course
|
|
||||||
"auto_mode": False,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for MyST (Markdown) ---------------------------------------------
|
|
||||||
|
|
||||||
# myst_heading_anchors = 2
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
||||||
|
|
||||||
html_theme = "furo"
|
|
||||||
html_static_path = ["_static"]
|
|
|
@ -1,22 +0,0 @@
|
||||||
.. Synapse Developer Documentation documentation master file, created by
|
|
||||||
sphinx-quickstart on Mon Mar 13 08:59:51 2023.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
Welcome to the Synapse Developer Documentation!
|
|
||||||
===========================================================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Contents:
|
|
||||||
|
|
||||||
modules/federation_sender
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
|
@ -1,5 +0,0 @@
|
||||||
Federation Sender
|
|
||||||
=================
|
|
||||||
|
|
||||||
```{autodoc2-docstring} synapse.federation.sender
|
|
||||||
```
|
|
|
@ -163,7 +163,7 @@ FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm
|
||||||
LABEL org.opencontainers.image.url='https://matrix.org/docs/projects/server/synapse'
|
LABEL org.opencontainers.image.url='https://matrix.org/docs/projects/server/synapse'
|
||||||
LABEL org.opencontainers.image.documentation='https://github.com/element-hq/synapse/blob/master/docker/README.md'
|
LABEL org.opencontainers.image.documentation='https://github.com/element-hq/synapse/blob/master/docker/README.md'
|
||||||
LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
|
LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
|
||||||
LABEL org.opencontainers.image.licenses='Apache-2.0'
|
LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later'
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
--mount=type=cache,target=/var/cache/apt,sharing=locked \
|
--mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
|
|
|
@ -92,8 +92,6 @@ allow_device_name_lookup_over_federation: true
|
||||||
## Experimental Features ##
|
## Experimental Features ##
|
||||||
|
|
||||||
experimental_features:
|
experimental_features:
|
||||||
# client-side support for partial state in /send_join responses
|
|
||||||
faster_joins: true
|
|
||||||
# Enable support for polls
|
# Enable support for polls
|
||||||
msc3381_polls_enabled: true
|
msc3381_polls_enabled: true
|
||||||
# Enable deleting device-specific notification settings stored in account data
|
# Enable deleting device-specific notification settings stored in account data
|
||||||
|
@ -102,6 +100,12 @@ experimental_features:
|
||||||
msc3391_enabled: true
|
msc3391_enabled: true
|
||||||
# Filtering /messages by relation type.
|
# Filtering /messages by relation type.
|
||||||
msc3874_enabled: true
|
msc3874_enabled: true
|
||||||
|
# no UIA for x-signing upload for the first time
|
||||||
|
msc3967_enabled: true
|
||||||
|
# Expose a room summary for public rooms
|
||||||
|
msc3266_enabled: true
|
||||||
|
|
||||||
|
msc4115_membership_on_events: true
|
||||||
|
|
||||||
server_notices:
|
server_notices:
|
||||||
system_mxid_localpart: _server
|
system_mxid_localpart: _server
|
||||||
|
|
|
@ -211,6 +211,8 @@ WORKERS_CONFIG: Dict[str, Dict[str, Any]] = {
|
||||||
"^/_matrix/federation/(v1|v2)/make_leave/",
|
"^/_matrix/federation/(v1|v2)/make_leave/",
|
||||||
"^/_matrix/federation/(v1|v2)/send_join/",
|
"^/_matrix/federation/(v1|v2)/send_join/",
|
||||||
"^/_matrix/federation/(v1|v2)/send_leave/",
|
"^/_matrix/federation/(v1|v2)/send_leave/",
|
||||||
|
"^/_matrix/federation/v1/make_knock/",
|
||||||
|
"^/_matrix/federation/v1/send_knock/",
|
||||||
"^/_matrix/federation/(v1|v2)/invite/",
|
"^/_matrix/federation/(v1|v2)/invite/",
|
||||||
"^/_matrix/federation/(v1|v2)/query_auth/",
|
"^/_matrix/federation/(v1|v2)/query_auth/",
|
||||||
"^/_matrix/federation/(v1|v2)/event_auth/",
|
"^/_matrix/federation/(v1|v2)/event_auth/",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Edit Room Membership API
|
# Edit Room Membership API
|
||||||
|
|
||||||
This API allows an administrator to join an user account with a given `user_id`
|
This API allows an administrator to join a user account with a given `user_id`
|
||||||
to a room with a given `room_id_or_alias`. You can only modify the membership of
|
to a room with a given `room_id_or_alias`. You can only modify the membership of
|
||||||
local users. The server administrator must be in the room and have permission to
|
local users. The server administrator must be in the room and have permission to
|
||||||
invite users.
|
invite users.
|
||||||
|
|
|
@ -141,8 +141,8 @@ Body parameters:
|
||||||
provider for SSO (Single sign-on). More details are in the configuration manual under the
|
provider for SSO (Single sign-on). More details are in the configuration manual under the
|
||||||
sections [sso](../usage/configuration/config_documentation.md#sso) and [oidc_providers](../usage/configuration/config_documentation.md#oidc_providers).
|
sections [sso](../usage/configuration/config_documentation.md#sso) and [oidc_providers](../usage/configuration/config_documentation.md#oidc_providers).
|
||||||
- `auth_provider` - **string**, required. The unique, internal ID of the external identity provider.
|
- `auth_provider` - **string**, required. The unique, internal ID of the external identity provider.
|
||||||
The same as `idp_id` from the homeserver configuration. Note that no error is raised if the
|
The same as `idp_id` from the homeserver configuration. If using OIDC, this value should be prefixed
|
||||||
provided value is not in the homeserver configuration.
|
with `oidc-`. Note that no error is raised if the provided value is not in the homeserver configuration.
|
||||||
- `external_id` - **string**, required. An identifier for the user in the external identity provider.
|
- `external_id` - **string**, required. An identifier for the user in the external identity provider.
|
||||||
When the user logs in to the identity provider, this must be the unique ID that they map to.
|
When the user logs in to the identity provider, this must be the unique ID that they map to.
|
||||||
- `admin` - **bool**, optional, defaults to `false`. Whether the user is a homeserver administrator,
|
- `admin` - **bool**, optional, defaults to `false`. Whether the user is a homeserver administrator,
|
||||||
|
|
|
@ -86,6 +86,8 @@ poetry install --extras all
|
||||||
This will install the runtime and developer dependencies for the project. Be sure to check
|
This will install the runtime and developer dependencies for the project. Be sure to check
|
||||||
that the `poetry install` step completed cleanly.
|
that the `poetry install` step completed cleanly.
|
||||||
|
|
||||||
|
For OSX users, be sure to set `PKG_CONFIG_PATH` to support `icu4c`. Run `brew info icu4c` for more details.
|
||||||
|
|
||||||
## Running Synapse via poetry
|
## Running Synapse via poetry
|
||||||
|
|
||||||
To start a local instance of Synapse in the locked poetry environment, create a config file:
|
To start a local instance of Synapse in the locked poetry environment, create a config file:
|
||||||
|
|
|
@ -7,8 +7,10 @@ follow the semantics described in
|
||||||
and allow server and room admins to configure how long messages should
|
and allow server and room admins to configure how long messages should
|
||||||
be kept in a homeserver's database before being purged from it.
|
be kept in a homeserver's database before being purged from it.
|
||||||
**Please note that, as this feature isn't part of the Matrix
|
**Please note that, as this feature isn't part of the Matrix
|
||||||
specification yet, this implementation is to be considered as
|
specification yet, the use of `m.room.retention` events for per-room
|
||||||
experimental.**
|
retention policies is to be considered as experimental. However, the use
|
||||||
|
of a default message retention policy is considered a stable feature
|
||||||
|
in Synapse.**
|
||||||
|
|
||||||
A message retention policy is mainly defined by its `max_lifetime`
|
A message retention policy is mainly defined by its `max_lifetime`
|
||||||
parameter, which defines how long a message can be kept around after
|
parameter, which defines how long a message can be kept around after
|
||||||
|
@ -49,8 +51,8 @@ clients.
|
||||||
|
|
||||||
## Server configuration
|
## Server configuration
|
||||||
|
|
||||||
Support for this feature can be enabled and configured by adding a the
|
Support for this feature can be enabled and configured by adding the
|
||||||
`retention` in the Synapse configuration file (see
|
`retention` option in the Synapse configuration file (see
|
||||||
[configuration manual](usage/configuration/config_documentation.md#retention)).
|
[configuration manual](usage/configuration/config_documentation.md#retention)).
|
||||||
|
|
||||||
To enable support for message retention policies, set the setting
|
To enable support for message retention policies, set the setting
|
||||||
|
@ -115,7 +117,7 @@ In this example, we define three jobs:
|
||||||
policy's `max_lifetime` is greater than a week.
|
policy's `max_lifetime` is greater than a week.
|
||||||
|
|
||||||
Note that this example is tailored to show different configurations and
|
Note that this example is tailored to show different configurations and
|
||||||
features slightly more jobs than it's probably necessary (in practice, a
|
features slightly more jobs than is probably necessary (in practice, a
|
||||||
server admin would probably consider it better to replace the two last
|
server admin would probably consider it better to replace the two last
|
||||||
jobs with one that runs once a day and handles rooms which
|
jobs with one that runs once a day and handles rooms which
|
||||||
policy's `max_lifetime` is greater than 3 days).
|
policy's `max_lifetime` is greater than 3 days).
|
||||||
|
|
|
@ -128,7 +128,7 @@ can read more about that [here](https://www.postgresql.org/docs/10/kernel-resour
|
||||||
### Overview
|
### Overview
|
||||||
|
|
||||||
The script `synapse_port_db` allows porting an existing synapse server
|
The script `synapse_port_db` allows porting an existing synapse server
|
||||||
backed by SQLite to using PostgreSQL. This is done in as a two phase
|
backed by SQLite to using PostgreSQL. This is done as a two phase
|
||||||
process:
|
process:
|
||||||
|
|
||||||
1. Copy the existing SQLite database to a separate location and run
|
1. Copy the existing SQLite database to a separate location and run
|
||||||
|
|
|
@ -259,9 +259,9 @@ users, etc.) to the developers via the `--report-stats` argument.
|
||||||
|
|
||||||
This command will generate you a config file that you can then customise, but it will
|
This command will generate you a config file that you can then customise, but it will
|
||||||
also generate a set of keys for you. These keys will allow your homeserver to
|
also generate a set of keys for you. These keys will allow your homeserver to
|
||||||
identify itself to other homeserver, so don't lose or delete them. It would be
|
identify itself to other homeservers, so don't lose or delete them. It would be
|
||||||
wise to back them up somewhere safe. (If, for whatever reason, you do need to
|
wise to back them up somewhere safe. (If, for whatever reason, you do need to
|
||||||
change your homeserver's keys, you may find that other homeserver have the
|
change your homeserver's keys, you may find that other homeservers have the
|
||||||
old key cached. If you update the signing key, you should change the name of the
|
old key cached. If you update the signing key, you should change the name of the
|
||||||
key in the `<server name>.signing.key` file (the second word) to something
|
key in the `<server name>.signing.key` file (the second word) to something
|
||||||
different. See the [spec](https://matrix.org/docs/spec/server_server/latest.html#retrieving-server-keys) for more information on key management).
|
different. See the [spec](https://matrix.org/docs/spec/server_server/latest.html#retrieving-server-keys) for more information on key management).
|
||||||
|
|
|
@ -98,6 +98,7 @@ A custom mapping provider must specify the following methods:
|
||||||
either accept this localpart or pick their own username. Otherwise this
|
either accept this localpart or pick their own username. Otherwise this
|
||||||
option has no effect. If omitted, defaults to `False`.
|
option has no effect. If omitted, defaults to `False`.
|
||||||
- `display_name`: An optional string, the display name for the user.
|
- `display_name`: An optional string, the display name for the user.
|
||||||
|
- `picture`: An optional string, the avatar url for the user.
|
||||||
- `emails`: A list of strings, the email address(es) to associate with
|
- `emails`: A list of strings, the email address(es) to associate with
|
||||||
this user. If omitted, defaults to an empty list.
|
this user. If omitted, defaults to an empty list.
|
||||||
* `async def get_extra_attributes(self, userinfo, token)`
|
* `async def get_extra_attributes(self, userinfo, token)`
|
||||||
|
|
|
@ -9,6 +9,7 @@ ReloadPropagatedFrom=matrix-synapse.target
|
||||||
Type=notify
|
Type=notify
|
||||||
NotifyAccess=main
|
NotifyAccess=main
|
||||||
User=matrix-synapse
|
User=matrix-synapse
|
||||||
|
RuntimeDirectory=synapse
|
||||||
WorkingDirectory=/var/lib/matrix-synapse
|
WorkingDirectory=/var/lib/matrix-synapse
|
||||||
EnvironmentFile=-/etc/default/matrix-synapse
|
EnvironmentFile=-/etc/default/matrix-synapse
|
||||||
ExecStartPre=/opt/venvs/matrix-synapse/bin/python -m synapse.app.homeserver --config-path=/etc/matrix-synapse/homeserver.yaml --config-path=/etc/matrix-synapse/conf.d/ --generate-keys
|
ExecStartPre=/opt/venvs/matrix-synapse/bin/python -m synapse.app.homeserver --config-path=/etc/matrix-synapse/homeserver.yaml --config-path=/etc/matrix-synapse/conf.d/ --generate-keys
|
||||||
|
|
|
@ -117,6 +117,14 @@ 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
|
stacking them up. You can monitor the currently running background updates with
|
||||||
[the Admin API](usage/administration/admin_api/background_updates.html#status).
|
[the Admin API](usage/administration/admin_api/background_updates.html#status).
|
||||||
|
|
||||||
|
# Upgrading to v1.106.0
|
||||||
|
|
||||||
|
## Minimum supported Rust version
|
||||||
|
The minimum supported Rust version has been increased from v1.65.0 to v1.66.0.
|
||||||
|
Users building from source will need to ensure their `rustc` version is up to
|
||||||
|
date.
|
||||||
|
|
||||||
|
|
||||||
# Upgrading to v1.100.0
|
# Upgrading to v1.100.0
|
||||||
|
|
||||||
## Minimum supported Rust version
|
## Minimum supported Rust version
|
||||||
|
|
|
@ -44,7 +44,7 @@ For each update:
|
||||||
|
|
||||||
## Enabled
|
## Enabled
|
||||||
|
|
||||||
This API allow pausing background updates.
|
This API allows pausing background updates.
|
||||||
|
|
||||||
Background updates should *not* be paused for significant periods of time, as
|
Background updates should *not* be paused for significant periods of time, as
|
||||||
this can affect the performance of Synapse.
|
this can affect the performance of Synapse.
|
||||||
|
|
|
@ -241,7 +241,7 @@ in memory constrained environments, or increased if performance starts to
|
||||||
degrade.
|
degrade.
|
||||||
|
|
||||||
However, degraded performance due to a low cache factor, common on
|
However, degraded performance due to a low cache factor, common on
|
||||||
machines with slow disks, often leads to explosions in memory use due
|
machines with slow disks, often leads to explosions in memory use due to
|
||||||
backlogged requests. In this case, reducing the cache factor will make
|
backlogged requests. In this case, reducing the cache factor will make
|
||||||
things worse. Instead, try increasing it drastically. 2.0 is a good
|
things worse. Instead, try increasing it drastically. 2.0 is a good
|
||||||
starting value.
|
starting value.
|
||||||
|
@ -250,10 +250,10 @@ Using [libjemalloc](https://jemalloc.net) can also yield a significant
|
||||||
improvement in overall memory use, and especially in terms of giving back
|
improvement in overall memory use, and especially in terms of giving back
|
||||||
RAM to the OS. To use it, the library must simply be put in the
|
RAM to the OS. To use it, the library must simply be put in the
|
||||||
LD_PRELOAD environment variable when launching Synapse. On Debian, this
|
LD_PRELOAD environment variable when launching Synapse. On Debian, this
|
||||||
can be done by installing the `libjemalloc1` package and adding this
|
can be done by installing the `libjemalloc2` package and adding this
|
||||||
line to `/etc/default/matrix-synapse`:
|
line to `/etc/default/matrix-synapse`:
|
||||||
|
|
||||||
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1
|
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
|
||||||
|
|
||||||
This made a significant difference on Python 2.7 - it's unclear how
|
This made a significant difference on Python 2.7 - it's unclear how
|
||||||
much of an improvement it provides on Python 3.x.
|
much of an improvement it provides on Python 3.x.
|
||||||
|
|
|
@ -676,8 +676,8 @@ This setting has the following sub-options:
|
||||||
trailing 's'.
|
trailing 's'.
|
||||||
* `app_name`: `app_name` defines the default value for '%(app)s' in `notif_from` and email
|
* `app_name`: `app_name` defines the default value for '%(app)s' in `notif_from` and email
|
||||||
subjects. It defaults to 'Matrix'.
|
subjects. It defaults to 'Matrix'.
|
||||||
* `enable_notifs`: Set to true to enable sending emails for messages that the user
|
* `enable_notifs`: Set to true to allow users to receive e-mail notifications. If this is not set,
|
||||||
has missed. Disabled by default.
|
users can configure e-mail notifications but will not receive them. Disabled by default.
|
||||||
* `notif_for_new_users`: Set to false to disable automatic subscription to email
|
* `notif_for_new_users`: Set to false to disable automatic subscription to email
|
||||||
notifications for new users. Enabled by default.
|
notifications for new users. Enabled by default.
|
||||||
* `notif_delay_before_mail`: The time to wait before emailing about a notification.
|
* `notif_delay_before_mail`: The time to wait before emailing about a notification.
|
||||||
|
@ -1232,6 +1232,31 @@ federation_domain_whitelist:
|
||||||
- syd.example.com
|
- syd.example.com
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
|
### `federation_whitelist_endpoint_enabled`
|
||||||
|
|
||||||
|
Enables an endpoint for fetching the federation whitelist config.
|
||||||
|
|
||||||
|
The request method and path is `GET /_synapse/client/v1/config/federation_whitelist`, and the
|
||||||
|
response format is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"whitelist_enabled": true, // Whether the federation whitelist is being enforced
|
||||||
|
"whitelist": [ // Which server names are allowed by the whitelist
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `whitelist_enabled` is `false` then the server is permitted to federate with all others.
|
||||||
|
|
||||||
|
The endpoint requires authentication.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```yaml
|
||||||
|
federation_whitelist_endpoint_enabled: true
|
||||||
|
```
|
||||||
|
---
|
||||||
### `federation_metrics_domains`
|
### `federation_metrics_domains`
|
||||||
|
|
||||||
Report prometheus metrics on the age of PDUs being sent to and received from
|
Report prometheus metrics on the age of PDUs being sent to and received from
|
||||||
|
@ -1317,6 +1342,12 @@ Options related to caching.
|
||||||
The number of events to cache in memory. Defaults to 10K. Like other caches,
|
The number of events to cache in memory. Defaults to 10K. Like other caches,
|
||||||
this is affected by `caches.global_factor` (see below).
|
this is affected by `caches.global_factor` (see below).
|
||||||
|
|
||||||
|
For example, the default is 10K and the global_factor default is 0.5.
|
||||||
|
|
||||||
|
Since 10K * 0.5 is 5K then the event cache size will be 5K.
|
||||||
|
|
||||||
|
The cache affected by this configuration is named as "*getEvent*".
|
||||||
|
|
||||||
Note that this option is not part of the `caches` section.
|
Note that this option is not part of the `caches` section.
|
||||||
|
|
||||||
Example configuration:
|
Example configuration:
|
||||||
|
@ -1342,6 +1373,8 @@ number of entries that can be stored.
|
||||||
|
|
||||||
Defaults to 0.5, which will halve the size of all caches.
|
Defaults to 0.5, which will halve the size of all caches.
|
||||||
|
|
||||||
|
Note that changing this value also affects the HTTP connection pool.
|
||||||
|
|
||||||
* `per_cache_factors`: A dictionary of cache name to cache factor for that individual
|
* `per_cache_factors`: A dictionary of cache name to cache factor for that individual
|
||||||
cache. Overrides the global cache factor for a given cache.
|
cache. Overrides the global cache factor for a given cache.
|
||||||
|
|
||||||
|
@ -2583,6 +2616,11 @@ Possible values for this option are:
|
||||||
* "trusted_private_chat": an invitation is required to join this room and the invitee is
|
* "trusted_private_chat": an invitation is required to join this room and the invitee is
|
||||||
assigned a power level of 100 upon joining the room.
|
assigned a power level of 100 upon joining the room.
|
||||||
|
|
||||||
|
Each preset will set up a room in the same manner as if it were provided as the `preset` parameter when
|
||||||
|
calling the
|
||||||
|
[`POST /_matrix/client/v3/createRoom`](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom)
|
||||||
|
Client-Server API endpoint.
|
||||||
|
|
||||||
If a value of "private_chat" or "trusted_private_chat" is used then
|
If a value of "private_chat" or "trusted_private_chat" is used then
|
||||||
`auto_join_mxid_localpart` must also be configured.
|
`auto_join_mxid_localpart` must also be configured.
|
||||||
|
|
||||||
|
@ -3520,6 +3558,15 @@ Has the following sub-options:
|
||||||
users. This allows the CAS SSO flow to be limited to sign in only, rather than
|
users. This allows the CAS SSO flow to be limited to sign in only, rather than
|
||||||
automatically registering users that have a valid SSO login but do not have
|
automatically registering users that have a valid SSO login but do not have
|
||||||
a pre-registered account. Defaults to true.
|
a pre-registered account. Defaults to true.
|
||||||
|
* `allow_numeric_ids`: set to 'true' allow numeric user IDs (default false).
|
||||||
|
This allows CAS SSO flow to provide user IDs composed of numbers only.
|
||||||
|
These identifiers will be prefixed by the letter "u" by default.
|
||||||
|
The prefix can be configured using the "numeric_ids_prefix" option.
|
||||||
|
Be careful to choose the prefix correctly to avoid any possible conflicts
|
||||||
|
(e.g. user 1234 becomes u1234 when a user u1234 already exists).
|
||||||
|
* `numeric_ids_prefix`: the prefix you wish to add in front of a numeric user ID
|
||||||
|
when the "allow_numeric_ids" option is set to "true".
|
||||||
|
By default, the prefix is the letter "u" and only alphanumeric characters are allowed.
|
||||||
|
|
||||||
*Added in Synapse 1.93.0.*
|
*Added in Synapse 1.93.0.*
|
||||||
|
|
||||||
|
@ -3534,6 +3581,8 @@ cas_config:
|
||||||
userGroup: "staff"
|
userGroup: "staff"
|
||||||
department: None
|
department: None
|
||||||
enable_registration: true
|
enable_registration: true
|
||||||
|
allow_numeric_ids: true
|
||||||
|
numeric_ids_prefix: "numericuser"
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
### `sso`
|
### `sso`
|
||||||
|
|
|
@ -86,9 +86,9 @@ The search term is then split into words:
|
||||||
* If unavailable, then runs of ASCII characters, numbers, underscores, and hyphens
|
* If unavailable, then runs of ASCII characters, numbers, underscores, and hyphens
|
||||||
are considered words.
|
are considered words.
|
||||||
|
|
||||||
The queries for PostgreSQL and SQLite are detailed below, by their overall goal
|
The queries for PostgreSQL and SQLite are detailed below, but their overall goal
|
||||||
is to find matching users, preferring users who are "real" (e.g. not bots,
|
is to find matching users, preferring users who are "real" (e.g. not bots,
|
||||||
not deactivated). It is assumed that real users will have an display name and
|
not deactivated). It is assumed that real users will have a display name and
|
||||||
avatar set.
|
avatar set.
|
||||||
|
|
||||||
### PostgreSQL
|
### PostgreSQL
|
||||||
|
|
|
@ -211,6 +211,8 @@ information.
|
||||||
^/_matrix/federation/v1/make_leave/
|
^/_matrix/federation/v1/make_leave/
|
||||||
^/_matrix/federation/(v1|v2)/send_join/
|
^/_matrix/federation/(v1|v2)/send_join/
|
||||||
^/_matrix/federation/(v1|v2)/send_leave/
|
^/_matrix/federation/(v1|v2)/send_leave/
|
||||||
|
^/_matrix/federation/v1/make_knock/
|
||||||
|
^/_matrix/federation/v1/send_knock/
|
||||||
^/_matrix/federation/(v1|v2)/invite/
|
^/_matrix/federation/(v1|v2)/invite/
|
||||||
^/_matrix/federation/v1/event_auth/
|
^/_matrix/federation/v1/event_auth/
|
||||||
^/_matrix/federation/v1/timestamp_to_event/
|
^/_matrix/federation/v1/timestamp_to_event/
|
||||||
|
@ -232,7 +234,7 @@ information.
|
||||||
^/_matrix/client/v1/rooms/.*/hierarchy$
|
^/_matrix/client/v1/rooms/.*/hierarchy$
|
||||||
^/_matrix/client/(v1|unstable)/rooms/.*/relations/
|
^/_matrix/client/(v1|unstable)/rooms/.*/relations/
|
||||||
^/_matrix/client/v1/rooms/.*/threads$
|
^/_matrix/client/v1/rooms/.*/threads$
|
||||||
^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary$
|
^/_matrix/client/unstable/im.nheko.summary/summary/.*$
|
||||||
^/_matrix/client/(r0|v3|unstable)/account/3pid$
|
^/_matrix/client/(r0|v3|unstable)/account/3pid$
|
||||||
^/_matrix/client/(r0|v3|unstable)/account/whoami$
|
^/_matrix/client/(r0|v3|unstable)/account/whoami$
|
||||||
^/_matrix/client/(r0|v3|unstable)/devices$
|
^/_matrix/client/(r0|v3|unstable)/devices$
|
||||||
|
@ -535,7 +537,7 @@ the stream writer for the `presence` stream:
|
||||||
##### The `push_rules` stream
|
##### The `push_rules` stream
|
||||||
|
|
||||||
The following endpoints should be routed directly to the worker configured as
|
The following endpoints should be routed directly to the worker configured as
|
||||||
the stream writer for the `push` stream:
|
the stream writer for the `push_rules` stream:
|
||||||
|
|
||||||
^/_matrix/client/(api/v1|r0|v3|unstable)/pushrules/
|
^/_matrix/client/(api/v1|r0|v3|unstable)/pushrules/
|
||||||
|
|
||||||
|
@ -634,7 +636,7 @@ worker application type.
|
||||||
|
|
||||||
#### Push Notifications
|
#### Push Notifications
|
||||||
|
|
||||||
You can designate generic worker to sending push notifications to
|
You can designate generic workers to send push notifications to
|
||||||
a [push gateway](https://spec.matrix.org/v1.5/push-gateway-api/) such as
|
a [push gateway](https://spec.matrix.org/v1.5/push-gateway-api/) such as
|
||||||
[sygnal](https://github.com/matrix-org/sygnal) and email.
|
[sygnal](https://github.com/matrix-org/sygnal) and email.
|
||||||
|
|
||||||
|
|
1207
poetry.lock
generated
1207
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -96,7 +96,7 @@ module-name = "synapse.synapse_rust"
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "matrix-synapse"
|
name = "matrix-synapse"
|
||||||
version = "1.105.0rc1"
|
version = "1.108.0"
|
||||||
description = "Homeserver for the Matrix decentralised comms protocol"
|
description = "Homeserver for the Matrix decentralised comms protocol"
|
||||||
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
|
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
@ -321,7 +321,7 @@ all = [
|
||||||
# This helps prevents merge conflicts when running a batch of dependabot updates.
|
# This helps prevents merge conflicts when running a batch of dependabot updates.
|
||||||
isort = ">=5.10.1"
|
isort = ">=5.10.1"
|
||||||
black = ">=22.7.0"
|
black = ">=22.7.0"
|
||||||
ruff = "0.3.5"
|
ruff = "0.3.7"
|
||||||
# Type checking only works with the pydantic.v1 compat module from pydantic v2
|
# Type checking only works with the pydantic.v1 compat module from pydantic v2
|
||||||
pydantic = "^2"
|
pydantic = "^2"
|
||||||
|
|
||||||
|
@ -364,17 +364,6 @@ towncrier = ">=18.6.0rc1"
|
||||||
tomli = ">=1.2.3"
|
tomli = ">=1.2.3"
|
||||||
|
|
||||||
|
|
||||||
# Dependencies for building the development documentation
|
|
||||||
[tool.poetry.group.dev-docs]
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[tool.poetry.group.dev-docs.dependencies]
|
|
||||||
sphinx = {version = "^6.1", python = "^3.8"}
|
|
||||||
sphinx-autodoc2 = {version = ">=0.4.2,<0.6.0", python = "^3.8"}
|
|
||||||
myst-parser = {version = "^1.0.0", python = "^3.8"}
|
|
||||||
furo = ">=2022.12.7,<2025.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
# The upper bounds here are defensive, intended to prevent situations like
|
# The upper bounds here are defensive, intended to prevent situations like
|
||||||
# https://github.com/matrix-org/synapse/issues/13849 and
|
# https://github.com/matrix-org/synapse/issues/13849 and
|
||||||
|
|
1182
requirements.txt
Normal file
1182
requirements.txt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@ name = "synapse"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.65.0"
|
rust-version = "1.66.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "synapse"
|
name = "synapse"
|
||||||
|
@ -23,19 +23,26 @@ name = "synapse.synapse_rust"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.63"
|
anyhow = "1.0.63"
|
||||||
|
base64 = "0.21.7"
|
||||||
|
bytes = "1.6.0"
|
||||||
|
headers = "0.4.0"
|
||||||
|
http = "1.1.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
pyo3 = { version = "0.20.0", features = [
|
mime = "0.3.17"
|
||||||
|
pyo3 = { version = "0.21.0", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"abi3",
|
"abi3",
|
||||||
"abi3-py38",
|
"abi3-py38",
|
||||||
] }
|
] }
|
||||||
pyo3-log = "0.9.0"
|
pyo3-log = "0.10.0"
|
||||||
pythonize = "0.20.0"
|
pythonize = "0.21.0"
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
|
sha2 = "0.10.8"
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
serde_json = "1.0.85"
|
serde_json = "1.0.85"
|
||||||
|
ulid = "1.1.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
extension-module = ["pyo3/extension-module"]
|
extension-module = ["pyo3/extension-module"]
|
||||||
|
|
|
@ -25,21 +25,21 @@ use std::net::Ipv4Addr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use pyo3::prelude::*;
|
use pyo3::{prelude::*, pybacked::PyBackedStr};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::push::utils::{glob_to_regex, GlobMatchType};
|
use crate::push::utils::{glob_to_regex, GlobMatchType};
|
||||||
|
|
||||||
/// Called when registering modules with python.
|
/// Called when registering modules with python.
|
||||||
pub fn register_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
let child_module = PyModule::new(py, "acl")?;
|
let child_module = PyModule::new_bound(py, "acl")?;
|
||||||
child_module.add_class::<ServerAclEvaluator>()?;
|
child_module.add_class::<ServerAclEvaluator>()?;
|
||||||
|
|
||||||
m.add_submodule(child_module)?;
|
m.add_submodule(&child_module)?;
|
||||||
|
|
||||||
// We need to manually add the module to sys.modules to make `from
|
// We need to manually add the module to sys.modules to make `from
|
||||||
// synapse.synapse_rust import acl` work.
|
// synapse.synapse_rust import acl` work.
|
||||||
py.import("sys")?
|
py.import_bound("sys")?
|
||||||
.getattr("modules")?
|
.getattr("modules")?
|
||||||
.set_item("synapse.synapse_rust.acl", child_module)?;
|
.set_item("synapse.synapse_rust.acl", child_module)?;
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ impl ServerAclEvaluator {
|
||||||
#[new]
|
#[new]
|
||||||
pub fn py_new(
|
pub fn py_new(
|
||||||
allow_ip_literals: bool,
|
allow_ip_literals: bool,
|
||||||
allow: Vec<&str>,
|
allow: Vec<PyBackedStr>,
|
||||||
deny: Vec<&str>,
|
deny: Vec<PyBackedStr>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let allow = allow
|
let allow = allow
|
||||||
.iter()
|
.iter()
|
||||||
|
|
60
rust/src/errors.rs
Normal file
60
rust/src/errors.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 New Vector, Ltd
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it 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.
|
||||||
|
*
|
||||||
|
* See the GNU Affero General Public License for more details:
|
||||||
|
* <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![allow(clippy::new_ret_no_self)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use http::{HeaderMap, StatusCode};
|
||||||
|
use pyo3::{exceptions::PyValueError, import_exception};
|
||||||
|
|
||||||
|
import_exception!(synapse.api.errors, SynapseError);
|
||||||
|
|
||||||
|
impl SynapseError {
|
||||||
|
pub fn new(
|
||||||
|
code: StatusCode,
|
||||||
|
message: String,
|
||||||
|
errcode: &'static str,
|
||||||
|
additional_fields: Option<HashMap<String, String>>,
|
||||||
|
headers: Option<HeaderMap>,
|
||||||
|
) -> pyo3::PyErr {
|
||||||
|
// Transform the HeaderMap into a HashMap<String, String>
|
||||||
|
let headers = if let Some(headers) = headers {
|
||||||
|
let mut map = HashMap::with_capacity(headers.len());
|
||||||
|
for (key, value) in headers.iter() {
|
||||||
|
let Ok(value) = value.to_str() else {
|
||||||
|
// This should never happen, but we don't want to panic in case it does
|
||||||
|
return PyValueError::new_err(
|
||||||
|
"Could not construct SynapseError: header value is not valid ASCII",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
map.insert(key.as_str().to_owned(), value.to_owned());
|
||||||
|
}
|
||||||
|
Some(map)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
SynapseError::new_err((code.as_u16(), message, errcode, additional_fields, headers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import_exception!(synapse.api.errors, NotFoundError);
|
||||||
|
|
||||||
|
impl NotFoundError {
|
||||||
|
pub fn new() -> pyo3::PyErr {
|
||||||
|
NotFoundError::new_err(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,10 @@
|
||||||
|
|
||||||
//! Implements the internal metadata class attached to events.
|
//! Implements the internal metadata class attached to events.
|
||||||
//!
|
//!
|
||||||
//! The internal metadata is a bit like a `TypedDict`, in that it is stored as a
|
//! The internal metadata is a bit like a `TypedDict`, in that most of
|
||||||
//! JSON dict in the DB. Most events have zero, or only a few, of these keys
|
//! it is stored as a JSON dict in the DB (the exceptions being `outlier`
|
||||||
|
//! and `stream_ordering` which have their own columns in the database).
|
||||||
|
//! Most events have zero, or only a few, of these keys
|
||||||
//! set. Therefore, since we care more about memory size than performance here,
|
//! set. Therefore, since we care more about memory size than performance here,
|
||||||
//! we store these fields in a mapping.
|
//! we store these fields in a mapping.
|
||||||
//!
|
//!
|
||||||
|
@ -36,9 +38,10 @@ use anyhow::Context;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::PyAttributeError,
|
exceptions::PyAttributeError,
|
||||||
|
pybacked::PyBackedStr,
|
||||||
pyclass, pymethods,
|
pyclass, pymethods,
|
||||||
types::{PyDict, PyString},
|
types::{PyAnyMethods, PyDict, PyDictMethods, PyString},
|
||||||
IntoPy, PyAny, PyObject, PyResult, Python,
|
Bound, IntoPy, PyAny, PyObject, PyResult, Python,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Definitions of the various fields of the internal metadata.
|
/// Definitions of the various fields of the internal metadata.
|
||||||
|
@ -57,7 +60,7 @@ enum EventInternalMetadataData {
|
||||||
|
|
||||||
impl EventInternalMetadataData {
|
impl EventInternalMetadataData {
|
||||||
/// Convert the field to its name and python object.
|
/// Convert the field to its name and python object.
|
||||||
fn to_python_pair<'a>(&self, py: Python<'a>) -> (&'a PyString, PyObject) {
|
fn to_python_pair<'a>(&self, py: Python<'a>) -> (&'a Bound<'a, PyString>, PyObject) {
|
||||||
match self {
|
match self {
|
||||||
EventInternalMetadataData::OutOfBandMembership(o) => {
|
EventInternalMetadataData::OutOfBandMembership(o) => {
|
||||||
(pyo3::intern!(py, "out_of_band_membership"), o.into_py(py))
|
(pyo3::intern!(py, "out_of_band_membership"), o.into_py(py))
|
||||||
|
@ -88,10 +91,13 @@ impl EventInternalMetadataData {
|
||||||
/// Converts from python key/values to the field.
|
/// Converts from python key/values to the field.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the key is a valid but unrecognized string.
|
/// Returns `None` if the key is a valid but unrecognized string.
|
||||||
fn from_python_pair(key: &PyAny, value: &PyAny) -> PyResult<Option<Self>> {
|
fn from_python_pair(
|
||||||
let key_str: &str = key.extract()?;
|
key: &Bound<'_, PyAny>,
|
||||||
|
value: &Bound<'_, PyAny>,
|
||||||
|
) -> PyResult<Option<Self>> {
|
||||||
|
let key_str: PyBackedStr = key.extract()?;
|
||||||
|
|
||||||
let e = match key_str {
|
let e = match &*key_str {
|
||||||
"out_of_band_membership" => EventInternalMetadataData::OutOfBandMembership(
|
"out_of_band_membership" => EventInternalMetadataData::OutOfBandMembership(
|
||||||
value
|
value
|
||||||
.extract()
|
.extract()
|
||||||
|
@ -208,11 +214,11 @@ pub struct EventInternalMetadata {
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl EventInternalMetadata {
|
impl EventInternalMetadata {
|
||||||
#[new]
|
#[new]
|
||||||
fn new(dict: &PyDict) -> PyResult<Self> {
|
fn new(dict: &Bound<'_, PyDict>) -> PyResult<Self> {
|
||||||
let mut data = Vec::with_capacity(dict.len());
|
let mut data = Vec::with_capacity(dict.len());
|
||||||
|
|
||||||
for (key, value) in dict.iter() {
|
for (key, value) in dict.iter() {
|
||||||
match EventInternalMetadataData::from_python_pair(key, value) {
|
match EventInternalMetadataData::from_python_pair(&key, &value) {
|
||||||
Ok(Some(entry)) => data.push(entry),
|
Ok(Some(entry)) => data.push(entry),
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -234,8 +240,11 @@ impl EventInternalMetadata {
|
||||||
self.clone()
|
self.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a dict holding the data stored in the `internal_metadata` column in the database.
|
||||||
|
///
|
||||||
|
/// Note that `outlier` and `stream_ordering` are stored in separate columns so are not returned here.
|
||||||
fn get_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
|
fn get_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let dict = PyDict::new(py);
|
let dict = PyDict::new_bound(py);
|
||||||
|
|
||||||
for entry in &self.data {
|
for entry in &self.data {
|
||||||
let (key, value) = entry.to_python_pair(py);
|
let (key, value) = entry.to_python_pair(py);
|
||||||
|
|
|
@ -20,20 +20,23 @@
|
||||||
|
|
||||||
//! Classes for representing Events.
|
//! Classes for representing Events.
|
||||||
|
|
||||||
use pyo3::{types::PyModule, PyResult, Python};
|
use pyo3::{
|
||||||
|
types::{PyAnyMethods, PyModule, PyModuleMethods},
|
||||||
|
Bound, PyResult, Python,
|
||||||
|
};
|
||||||
|
|
||||||
mod internal_metadata;
|
mod internal_metadata;
|
||||||
|
|
||||||
/// Called when registering modules with python.
|
/// Called when registering modules with python.
|
||||||
pub fn register_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
let child_module = PyModule::new(py, "events")?;
|
let child_module = PyModule::new_bound(py, "events")?;
|
||||||
child_module.add_class::<internal_metadata::EventInternalMetadata>()?;
|
child_module.add_class::<internal_metadata::EventInternalMetadata>()?;
|
||||||
|
|
||||||
m.add_submodule(child_module)?;
|
m.add_submodule(&child_module)?;
|
||||||
|
|
||||||
// We need to manually add the module to sys.modules to make `from
|
// We need to manually add the module to sys.modules to make `from
|
||||||
// synapse.synapse_rust import events` work.
|
// synapse.synapse_rust import events` work.
|
||||||
py.import("sys")?
|
py.import_bound("sys")?
|
||||||
.getattr("modules")?
|
.getattr("modules")?
|
||||||
.set_item("synapse.synapse_rust.events", child_module)?;
|
.set_item("synapse.synapse_rust.events", child_module)?;
|
||||||
|
|
||||||
|
|
174
rust/src/http.rs
Normal file
174
rust/src/http.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 New Vector, Ltd
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it 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.
|
||||||
|
*
|
||||||
|
* See the GNU Affero General Public License for more details:
|
||||||
|
* <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
use headers::{Header, HeaderMapExt};
|
||||||
|
use http::{HeaderName, HeaderValue, Method, Request, Response, StatusCode, Uri};
|
||||||
|
use pyo3::{
|
||||||
|
exceptions::PyValueError,
|
||||||
|
types::{PyAnyMethods, PyBytes, PyBytesMethods, PySequence, PyTuple},
|
||||||
|
Bound, PyAny, PyResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::errors::SynapseError;
|
||||||
|
|
||||||
|
/// Read a file-like Python object by chunks
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if calling the `read` on the Python object failed
|
||||||
|
fn read_io_body(body: &Bound<'_, PyAny>, chunk_size: usize) -> PyResult<Bytes> {
|
||||||
|
let mut buf = BytesMut::new();
|
||||||
|
loop {
|
||||||
|
let bound = &body.call_method1("read", (chunk_size,))?;
|
||||||
|
let bytes: &Bound<'_, PyBytes> = bound.downcast()?;
|
||||||
|
if bytes.as_bytes().is_empty() {
|
||||||
|
return Ok(buf.into());
|
||||||
|
}
|
||||||
|
buf.put(bytes.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transform a Twisted `IRequest` to an [`http::Request`]
|
||||||
|
///
|
||||||
|
/// It uses the following members of `IRequest`:
|
||||||
|
/// - `content`, which is expected to be a file-like object with a `read` method
|
||||||
|
/// - `uri`, which is expected to be a valid URI as `bytes`
|
||||||
|
/// - `method`, which is expected to be a valid HTTP method as `bytes`
|
||||||
|
/// - `requestHeaders`, which is expected to have a `getAllRawHeaders` method
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the Python object doesn't properly implement `IRequest`
|
||||||
|
pub fn http_request_from_twisted(request: &Bound<'_, PyAny>) -> PyResult<Request<Bytes>> {
|
||||||
|
let content = request.getattr("content")?;
|
||||||
|
let body = read_io_body(&content, 4096)?;
|
||||||
|
|
||||||
|
let mut req = Request::new(body);
|
||||||
|
|
||||||
|
let bound = &request.getattr("uri")?;
|
||||||
|
let uri: &Bound<'_, PyBytes> = bound.downcast()?;
|
||||||
|
*req.uri_mut() =
|
||||||
|
Uri::try_from(uri.as_bytes()).map_err(|_| PyValueError::new_err("invalid uri"))?;
|
||||||
|
|
||||||
|
let bound = &request.getattr("method")?;
|
||||||
|
let method: &Bound<'_, PyBytes> = bound.downcast()?;
|
||||||
|
*req.method_mut() = Method::from_bytes(method.as_bytes())
|
||||||
|
.map_err(|_| PyValueError::new_err("invalid method"))?;
|
||||||
|
|
||||||
|
let headers_iter = request
|
||||||
|
.getattr("requestHeaders")?
|
||||||
|
.call_method0("getAllRawHeaders")?
|
||||||
|
.iter()?;
|
||||||
|
|
||||||
|
for header in headers_iter {
|
||||||
|
let header = header?;
|
||||||
|
let header: &Bound<'_, PyTuple> = header.downcast()?;
|
||||||
|
let bound = &header.get_item(0)?;
|
||||||
|
let name: &Bound<'_, PyBytes> = bound.downcast()?;
|
||||||
|
let name = HeaderName::from_bytes(name.as_bytes())
|
||||||
|
.map_err(|_| PyValueError::new_err("invalid header name"))?;
|
||||||
|
|
||||||
|
let bound = &header.get_item(1)?;
|
||||||
|
let values: &Bound<'_, PySequence> = bound.downcast()?;
|
||||||
|
for index in 0..values.len()? {
|
||||||
|
let bound = &values.get_item(index)?;
|
||||||
|
let value: &Bound<'_, PyBytes> = bound.downcast()?;
|
||||||
|
let value = HeaderValue::from_bytes(value.as_bytes())
|
||||||
|
.map_err(|_| PyValueError::new_err("invalid header value"))?;
|
||||||
|
req.headers_mut().append(name.clone(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send an [`http::Response`] through a Twisted `IRequest`
|
||||||
|
///
|
||||||
|
/// It uses the following members of `IRequest`:
|
||||||
|
///
|
||||||
|
/// - `responseHeaders`, which is expected to have a `addRawHeader(bytes, bytes)` method
|
||||||
|
/// - `setResponseCode(int)` method
|
||||||
|
/// - `write(bytes)` method
|
||||||
|
/// - `finish()` method
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the Python object doesn't properly implement `IRequest`
|
||||||
|
pub fn http_response_to_twisted<B>(
|
||||||
|
request: &Bound<'_, PyAny>,
|
||||||
|
response: Response<B>,
|
||||||
|
) -> PyResult<()>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
let (parts, mut body) = response.into_parts();
|
||||||
|
|
||||||
|
request.call_method1("setResponseCode", (parts.status.as_u16(),))?;
|
||||||
|
|
||||||
|
let response_headers = request.getattr("responseHeaders")?;
|
||||||
|
for (name, value) in parts.headers.iter() {
|
||||||
|
response_headers.call_method1("addRawHeader", (name.as_str(), value.as_bytes()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
while body.remaining() != 0 {
|
||||||
|
let chunk = body.chunk();
|
||||||
|
request.call_method1("write", (chunk,))?;
|
||||||
|
body.advance(chunk.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
request.call_method0("finish")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extension trait for [`HeaderMap`] that provides typed access to headers, and throws the
|
||||||
|
/// right python exceptions when the header is missing or fails to parse.
|
||||||
|
///
|
||||||
|
/// [`HeaderMap`]: headers::HeaderMap
|
||||||
|
pub trait HeaderMapPyExt: HeaderMapExt {
|
||||||
|
/// Get a header from the map, returning an error if it is missing or invalid.
|
||||||
|
fn typed_get_required<H>(&self) -> PyResult<H>
|
||||||
|
where
|
||||||
|
H: Header,
|
||||||
|
{
|
||||||
|
self.typed_get_optional::<H>()?.ok_or_else(|| {
|
||||||
|
SynapseError::new(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
format!("Missing required header: {}", H::name()),
|
||||||
|
"M_MISSING_PARAM",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a header from the map, returning `None` if it is missing and an error if it is invalid.
|
||||||
|
fn typed_get_optional<H>(&self) -> PyResult<Option<H>>
|
||||||
|
where
|
||||||
|
H: Header,
|
||||||
|
{
|
||||||
|
self.typed_try_get::<H>().map_err(|_| {
|
||||||
|
SynapseError::new(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
format!("Invalid header: {}", H::name()),
|
||||||
|
"M_INVALID_PARAM",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HeaderMapExt> HeaderMapPyExt for T {}
|
|
@ -3,8 +3,11 @@ use pyo3::prelude::*;
|
||||||
use pyo3_log::ResetHandle;
|
use pyo3_log::ResetHandle;
|
||||||
|
|
||||||
pub mod acl;
|
pub mod acl;
|
||||||
|
pub mod errors;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
pub mod http;
|
||||||
pub mod push;
|
pub mod push;
|
||||||
|
pub mod rendezvous;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref LOGGING_HANDLE: ResetHandle = pyo3_log::init();
|
static ref LOGGING_HANDLE: ResetHandle = pyo3_log::init();
|
||||||
|
@ -35,7 +38,7 @@ fn reset_logging_config() {
|
||||||
|
|
||||||
/// The entry point for defining the Python module.
|
/// The entry point for defining the Python module.
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
fn synapse_rust(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
fn synapse_rust(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
|
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(get_rust_file_digest, m)?)?;
|
m.add_function(wrap_pyfunction!(get_rust_file_digest, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(reset_logging_config, m)?)?;
|
m.add_function(wrap_pyfunction!(reset_logging_config, m)?)?;
|
||||||
|
@ -43,6 +46,7 @@ fn synapse_rust(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||||
acl::register_module(py, m)?;
|
acl::register_module(py, m)?;
|
||||||
push::register_module(py, m)?;
|
push::register_module(py, m)?;
|
||||||
events::register_module(py, m)?;
|
events::register_module(py, m)?;
|
||||||
|
rendezvous::register_module(py, m)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ use log::warn;
|
||||||
use pyo3::exceptions::PyTypeError;
|
use pyo3::exceptions::PyTypeError;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyBool, PyList, PyLong, PyString};
|
use pyo3::types::{PyBool, PyList, PyLong, PyString};
|
||||||
use pythonize::{depythonize, pythonize};
|
use pythonize::{depythonize_bound, pythonize};
|
||||||
use serde::de::Error as _;
|
use serde::de::Error as _;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
@ -78,19 +78,19 @@ pub mod evaluator;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
/// Called when registering modules with python.
|
/// Called when registering modules with python.
|
||||||
pub fn register_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
let child_module = PyModule::new(py, "push")?;
|
let child_module = PyModule::new_bound(py, "push")?;
|
||||||
child_module.add_class::<PushRule>()?;
|
child_module.add_class::<PushRule>()?;
|
||||||
child_module.add_class::<PushRules>()?;
|
child_module.add_class::<PushRules>()?;
|
||||||
child_module.add_class::<FilteredPushRules>()?;
|
child_module.add_class::<FilteredPushRules>()?;
|
||||||
child_module.add_class::<PushRuleEvaluator>()?;
|
child_module.add_class::<PushRuleEvaluator>()?;
|
||||||
child_module.add_function(wrap_pyfunction!(get_base_rule_ids, m)?)?;
|
child_module.add_function(wrap_pyfunction!(get_base_rule_ids, m)?)?;
|
||||||
|
|
||||||
m.add_submodule(child_module)?;
|
m.add_submodule(&child_module)?;
|
||||||
|
|
||||||
// We need to manually add the module to sys.modules to make `from
|
// We need to manually add the module to sys.modules to make `from
|
||||||
// synapse.synapse_rust import push` work.
|
// synapse.synapse_rust import push` work.
|
||||||
py.import("sys")?
|
py.import_bound("sys")?
|
||||||
.getattr("modules")?
|
.getattr("modules")?
|
||||||
.set_item("synapse.synapse_rust.push", child_module)?;
|
.set_item("synapse.synapse_rust.push", child_module)?;
|
||||||
|
|
||||||
|
@ -271,12 +271,12 @@ pub enum SimpleJsonValue {
|
||||||
|
|
||||||
impl<'source> FromPyObject<'source> for SimpleJsonValue {
|
impl<'source> FromPyObject<'source> for SimpleJsonValue {
|
||||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||||
if let Ok(s) = <PyString as pyo3::PyTryFrom>::try_from(ob) {
|
if let Ok(s) = ob.downcast::<PyString>() {
|
||||||
Ok(SimpleJsonValue::Str(Cow::Owned(s.to_string())))
|
Ok(SimpleJsonValue::Str(Cow::Owned(s.to_string())))
|
||||||
// A bool *is* an int, ensure we try bool first.
|
// A bool *is* an int, ensure we try bool first.
|
||||||
} else if let Ok(b) = <PyBool as pyo3::PyTryFrom>::try_from(ob) {
|
} else if let Ok(b) = ob.downcast::<PyBool>() {
|
||||||
Ok(SimpleJsonValue::Bool(b.extract()?))
|
Ok(SimpleJsonValue::Bool(b.extract()?))
|
||||||
} else if let Ok(i) = <PyLong as pyo3::PyTryFrom>::try_from(ob) {
|
} else if let Ok(i) = ob.downcast::<PyLong>() {
|
||||||
Ok(SimpleJsonValue::Int(i.extract()?))
|
Ok(SimpleJsonValue::Int(i.extract()?))
|
||||||
} else if ob.is_none() {
|
} else if ob.is_none() {
|
||||||
Ok(SimpleJsonValue::Null)
|
Ok(SimpleJsonValue::Null)
|
||||||
|
@ -299,7 +299,7 @@ pub enum JsonValue {
|
||||||
|
|
||||||
impl<'source> FromPyObject<'source> for JsonValue {
|
impl<'source> FromPyObject<'source> for JsonValue {
|
||||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||||
if let Ok(l) = <PyList as pyo3::PyTryFrom>::try_from(ob) {
|
if let Ok(l) = ob.downcast::<PyList>() {
|
||||||
match l.iter().map(SimpleJsonValue::extract).collect() {
|
match l.iter().map(SimpleJsonValue::extract).collect() {
|
||||||
Ok(a) => Ok(JsonValue::Array(a)),
|
Ok(a) => Ok(JsonValue::Array(a)),
|
||||||
Err(e) => Err(PyTypeError::new_err(format!(
|
Err(e) => Err(PyTypeError::new_err(format!(
|
||||||
|
@ -370,8 +370,8 @@ impl IntoPy<PyObject> for Condition {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'source> FromPyObject<'source> for Condition {
|
impl<'source> FromPyObject<'source> for Condition {
|
||||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
|
||||||
Ok(depythonize(ob)?)
|
Ok(depythonize_bound(ob.clone())?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
327
rust/src/rendezvous/mod.rs
Normal file
327
rust/src/rendezvous/mod.rs
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
/*
|
||||||
|
* This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 New Vector, Ltd
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it 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.
|
||||||
|
*
|
||||||
|
* See the GNU Affero General Public License for more details:
|
||||||
|
* <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use headers::{
|
||||||
|
AccessControlAllowOrigin, AccessControlExposeHeaders, CacheControl, ContentLength, ContentType,
|
||||||
|
HeaderMapExt, IfMatch, IfNoneMatch, Pragma,
|
||||||
|
};
|
||||||
|
use http::{header::ETAG, HeaderMap, Response, StatusCode, Uri};
|
||||||
|
use mime::Mime;
|
||||||
|
use pyo3::{
|
||||||
|
exceptions::PyValueError,
|
||||||
|
pyclass, pymethods,
|
||||||
|
types::{PyAnyMethods, PyModule, PyModuleMethods},
|
||||||
|
Bound, Py, PyAny, PyObject, PyResult, Python, ToPyObject,
|
||||||
|
};
|
||||||
|
use ulid::Ulid;
|
||||||
|
|
||||||
|
use self::session::Session;
|
||||||
|
use crate::{
|
||||||
|
errors::{NotFoundError, SynapseError},
|
||||||
|
http::{http_request_from_twisted, http_response_to_twisted, HeaderMapPyExt},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod session;
|
||||||
|
|
||||||
|
// n.b. Because OPTIONS requests are handled by the Python code, we don't need to set Access-Control-Allow-Headers.
|
||||||
|
fn prepare_headers(headers: &mut HeaderMap, session: &Session) {
|
||||||
|
headers.typed_insert(AccessControlAllowOrigin::ANY);
|
||||||
|
headers.typed_insert(AccessControlExposeHeaders::from_iter([ETAG]));
|
||||||
|
headers.typed_insert(Pragma::no_cache());
|
||||||
|
headers.typed_insert(CacheControl::new().with_no_store());
|
||||||
|
headers.typed_insert(session.etag());
|
||||||
|
headers.typed_insert(session.expires());
|
||||||
|
headers.typed_insert(session.last_modified());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct RendezvousHandler {
|
||||||
|
base: Uri,
|
||||||
|
clock: PyObject,
|
||||||
|
sessions: BTreeMap<Ulid, Session>,
|
||||||
|
capacity: usize,
|
||||||
|
max_content_length: u64,
|
||||||
|
ttl: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RendezvousHandler {
|
||||||
|
/// Check the input headers of a request which sets data for a session, and return the content type.
|
||||||
|
fn check_input_headers(&self, headers: &HeaderMap) -> PyResult<Mime> {
|
||||||
|
let ContentLength(content_length) = headers.typed_get_required()?;
|
||||||
|
|
||||||
|
if content_length > self.max_content_length {
|
||||||
|
return Err(SynapseError::new(
|
||||||
|
StatusCode::PAYLOAD_TOO_LARGE,
|
||||||
|
"Payload too large".to_owned(),
|
||||||
|
"M_TOO_LARGE",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_type: ContentType = headers.typed_get_required()?;
|
||||||
|
|
||||||
|
// Content-Type must be text/plain
|
||||||
|
if content_type != ContentType::text() {
|
||||||
|
return Err(SynapseError::new(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"Content-Type must be text/plain".to_owned(),
|
||||||
|
"M_INVALID_PARAM",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(content_type.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evict expired sessions and remove the oldest sessions until we're under the capacity.
|
||||||
|
fn evict(&mut self, now: SystemTime) {
|
||||||
|
// First remove all the entries which expired
|
||||||
|
self.sessions.retain(|_, session| !session.expired(now));
|
||||||
|
|
||||||
|
// Then we remove the oldest entires until we're under the limit
|
||||||
|
while self.sessions.len() > self.capacity {
|
||||||
|
self.sessions.pop_first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl RendezvousHandler {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (homeserver, /, capacity=100, max_content_length=4*1024, eviction_interval=60*1000, ttl=60*1000))]
|
||||||
|
fn new(
|
||||||
|
py: Python<'_>,
|
||||||
|
homeserver: &Bound<'_, PyAny>,
|
||||||
|
capacity: usize,
|
||||||
|
max_content_length: u64,
|
||||||
|
eviction_interval: u64,
|
||||||
|
ttl: u64,
|
||||||
|
) -> PyResult<Py<Self>> {
|
||||||
|
let base: String = homeserver
|
||||||
|
.getattr("config")?
|
||||||
|
.getattr("server")?
|
||||||
|
.getattr("public_baseurl")?
|
||||||
|
.extract()?;
|
||||||
|
let base = Uri::try_from(format!("{base}_synapse/client/rendezvous"))
|
||||||
|
.map_err(|_| PyValueError::new_err("Invalid base URI"))?;
|
||||||
|
|
||||||
|
let clock = homeserver.call_method0("get_clock")?.to_object(py);
|
||||||
|
|
||||||
|
// Construct a Python object so that we can get a reference to the
|
||||||
|
// evict method and schedule it to run.
|
||||||
|
let self_ = Py::new(
|
||||||
|
py,
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
clock,
|
||||||
|
sessions: BTreeMap::new(),
|
||||||
|
capacity,
|
||||||
|
max_content_length,
|
||||||
|
ttl: Duration::from_millis(ttl),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let evict = self_.getattr(py, "_evict")?;
|
||||||
|
homeserver.call_method0("get_clock")?.call_method(
|
||||||
|
"looping_call",
|
||||||
|
(evict, eviction_interval),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(self_)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _evict(&mut self, py: Python<'_>) -> PyResult<()> {
|
||||||
|
let clock = self.clock.bind(py);
|
||||||
|
let now: u64 = clock.call_method0("time_msec")?.extract()?;
|
||||||
|
let now = SystemTime::UNIX_EPOCH + Duration::from_millis(now);
|
||||||
|
self.evict(now);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_post(&mut self, py: Python<'_>, twisted_request: &Bound<'_, PyAny>) -> PyResult<()> {
|
||||||
|
let request = http_request_from_twisted(twisted_request)?;
|
||||||
|
|
||||||
|
let content_type = self.check_input_headers(request.headers())?;
|
||||||
|
|
||||||
|
let clock = self.clock.bind(py);
|
||||||
|
let now: u64 = clock.call_method0("time_msec")?.extract()?;
|
||||||
|
let now = SystemTime::UNIX_EPOCH + Duration::from_millis(now);
|
||||||
|
|
||||||
|
// We trigger an immediate eviction if we're at 2x the capacity
|
||||||
|
if self.sessions.len() >= self.capacity * 2 {
|
||||||
|
self.evict(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new ULID for the session from the current time.
|
||||||
|
let id = Ulid::from_datetime(now);
|
||||||
|
|
||||||
|
let uri = format!("{base}/{id}", base = self.base);
|
||||||
|
|
||||||
|
let body = request.into_body();
|
||||||
|
|
||||||
|
let session = Session::new(body, content_type, now, self.ttl);
|
||||||
|
|
||||||
|
let response = serde_json::json!({
|
||||||
|
"url": uri,
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut response = Response::new(response.as_bytes());
|
||||||
|
*response.status_mut() = StatusCode::CREATED;
|
||||||
|
response.headers_mut().typed_insert(ContentType::json());
|
||||||
|
prepare_headers(response.headers_mut(), &session);
|
||||||
|
http_response_to_twisted(twisted_request, response)?;
|
||||||
|
|
||||||
|
self.sessions.insert(id, session);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_get(
|
||||||
|
&mut self,
|
||||||
|
py: Python<'_>,
|
||||||
|
twisted_request: &Bound<'_, PyAny>,
|
||||||
|
id: &str,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let request = http_request_from_twisted(twisted_request)?;
|
||||||
|
|
||||||
|
let if_none_match: Option<IfNoneMatch> = request.headers().typed_get_optional()?;
|
||||||
|
|
||||||
|
let now: u64 = self.clock.call_method0(py, "time_msec")?.extract(py)?;
|
||||||
|
let now = SystemTime::UNIX_EPOCH + Duration::from_millis(now);
|
||||||
|
|
||||||
|
let id: Ulid = id.parse().map_err(|_| NotFoundError::new())?;
|
||||||
|
let session = self
|
||||||
|
.sessions
|
||||||
|
.get(&id)
|
||||||
|
.filter(|s| !s.expired(now))
|
||||||
|
.ok_or_else(NotFoundError::new)?;
|
||||||
|
|
||||||
|
if let Some(if_none_match) = if_none_match {
|
||||||
|
if !if_none_match.precondition_passes(&session.etag()) {
|
||||||
|
let mut response = Response::new(Bytes::new());
|
||||||
|
*response.status_mut() = StatusCode::NOT_MODIFIED;
|
||||||
|
prepare_headers(response.headers_mut(), session);
|
||||||
|
http_response_to_twisted(twisted_request, response)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = Response::new(session.data());
|
||||||
|
*response.status_mut() = StatusCode::OK;
|
||||||
|
let headers = response.headers_mut();
|
||||||
|
prepare_headers(headers, session);
|
||||||
|
headers.typed_insert(session.content_type());
|
||||||
|
headers.typed_insert(session.content_length());
|
||||||
|
http_response_to_twisted(twisted_request, response)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_put(
|
||||||
|
&mut self,
|
||||||
|
py: Python<'_>,
|
||||||
|
twisted_request: &Bound<'_, PyAny>,
|
||||||
|
id: &str,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let request = http_request_from_twisted(twisted_request)?;
|
||||||
|
|
||||||
|
let content_type = self.check_input_headers(request.headers())?;
|
||||||
|
|
||||||
|
let if_match: IfMatch = request.headers().typed_get_required()?;
|
||||||
|
|
||||||
|
let data = request.into_body();
|
||||||
|
|
||||||
|
let now: u64 = self.clock.call_method0(py, "time_msec")?.extract(py)?;
|
||||||
|
let now = SystemTime::UNIX_EPOCH + Duration::from_millis(now);
|
||||||
|
|
||||||
|
let id: Ulid = id.parse().map_err(|_| NotFoundError::new())?;
|
||||||
|
let session = self
|
||||||
|
.sessions
|
||||||
|
.get_mut(&id)
|
||||||
|
.filter(|s| !s.expired(now))
|
||||||
|
.ok_or_else(NotFoundError::new)?;
|
||||||
|
|
||||||
|
if !if_match.precondition_passes(&session.etag()) {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
prepare_headers(&mut headers, session);
|
||||||
|
|
||||||
|
let mut additional_fields = HashMap::with_capacity(1);
|
||||||
|
additional_fields.insert(
|
||||||
|
String::from("org.matrix.msc4108.errcode"),
|
||||||
|
String::from("M_CONCURRENT_WRITE"),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(SynapseError::new(
|
||||||
|
StatusCode::PRECONDITION_FAILED,
|
||||||
|
"ETag does not match".to_owned(),
|
||||||
|
"M_UNKNOWN", // Would be M_CONCURRENT_WRITE
|
||||||
|
Some(additional_fields),
|
||||||
|
Some(headers),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
session.update(data, content_type, now);
|
||||||
|
|
||||||
|
let mut response = Response::new(Bytes::new());
|
||||||
|
*response.status_mut() = StatusCode::ACCEPTED;
|
||||||
|
prepare_headers(response.headers_mut(), session);
|
||||||
|
http_response_to_twisted(twisted_request, response)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self, twisted_request: &Bound<'_, PyAny>, id: &str) -> PyResult<()> {
|
||||||
|
let _request = http_request_from_twisted(twisted_request)?;
|
||||||
|
|
||||||
|
let id: Ulid = id.parse().map_err(|_| NotFoundError::new())?;
|
||||||
|
let _session = self.sessions.remove(&id).ok_or_else(NotFoundError::new)?;
|
||||||
|
|
||||||
|
let mut response = Response::new(Bytes::new());
|
||||||
|
*response.status_mut() = StatusCode::NO_CONTENT;
|
||||||
|
response
|
||||||
|
.headers_mut()
|
||||||
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||||
|
http_response_to_twisted(twisted_request, response)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
|
let child_module = PyModule::new_bound(py, "rendezvous")?;
|
||||||
|
|
||||||
|
child_module.add_class::<RendezvousHandler>()?;
|
||||||
|
|
||||||
|
m.add_submodule(&child_module)?;
|
||||||
|
|
||||||
|
// We need to manually add the module to sys.modules to make `from
|
||||||
|
// synapse.synapse_rust import rendezvous` work.
|
||||||
|
py.import_bound("sys")?
|
||||||
|
.getattr("modules")?
|
||||||
|
.set_item("synapse.synapse_rust.rendezvous", child_module)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
91
rust/src/rendezvous/session.rs
Normal file
91
rust/src/rendezvous/session.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 New Vector, Ltd
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it 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.
|
||||||
|
*
|
||||||
|
* See the GNU Affero General Public License for more details:
|
||||||
|
* <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use headers::{ContentLength, ContentType, ETag, Expires, LastModified};
|
||||||
|
use mime::Mime;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
/// A single session, containing data, metadata, and expiry information.
|
||||||
|
pub struct Session {
|
||||||
|
hash: [u8; 32],
|
||||||
|
data: Bytes,
|
||||||
|
content_type: Mime,
|
||||||
|
last_modified: SystemTime,
|
||||||
|
expires: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
/// Create a new session with the given data, content type, and time-to-live.
|
||||||
|
pub fn new(data: Bytes, content_type: Mime, now: SystemTime, ttl: Duration) -> Self {
|
||||||
|
let hash = Sha256::digest(&data).into();
|
||||||
|
Self {
|
||||||
|
hash,
|
||||||
|
data,
|
||||||
|
content_type,
|
||||||
|
expires: now + ttl,
|
||||||
|
last_modified: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the session has expired at the given time.
|
||||||
|
pub fn expired(&self, now: SystemTime) -> bool {
|
||||||
|
self.expires <= now
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the session with new data, content type, and last modified time.
|
||||||
|
pub fn update(&mut self, data: Bytes, content_type: Mime, now: SystemTime) {
|
||||||
|
self.hash = Sha256::digest(&data).into();
|
||||||
|
self.data = data;
|
||||||
|
self.content_type = content_type;
|
||||||
|
self.last_modified = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Content-Type header of the session.
|
||||||
|
pub fn content_type(&self) -> ContentType {
|
||||||
|
self.content_type.clone().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Content-Length header of the session.
|
||||||
|
pub fn content_length(&self) -> ContentLength {
|
||||||
|
ContentLength(self.data.len() as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ETag header of the session.
|
||||||
|
pub fn etag(&self) -> ETag {
|
||||||
|
let encoded = URL_SAFE_NO_PAD.encode(self.hash);
|
||||||
|
// SAFETY: Base64 encoding is URL-safe, so ETag-safe
|
||||||
|
format!("\"{encoded}\"")
|
||||||
|
.parse()
|
||||||
|
.expect("base64-encoded hash should be URL-safe")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Last-Modified header of the session.
|
||||||
|
pub fn last_modified(&self) -> LastModified {
|
||||||
|
self.last_modified.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Expires header of the session.
|
||||||
|
pub fn expires(&self) -> Expires {
|
||||||
|
self.expires.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current data stored in the session.
|
||||||
|
pub fn data(&self) -> Bytes {
|
||||||
|
self.data.clone()
|
||||||
|
}
|
||||||
|
}
|
|
@ -214,7 +214,17 @@ fi
|
||||||
|
|
||||||
extra_test_args=()
|
extra_test_args=()
|
||||||
|
|
||||||
test_packages="./tests/csapi ./tests ./tests/msc3874 ./tests/msc3890 ./tests/msc3391 ./tests/msc3930 ./tests/msc3902"
|
test_packages=(
|
||||||
|
./tests/csapi
|
||||||
|
./tests
|
||||||
|
./tests/msc3874
|
||||||
|
./tests/msc3890
|
||||||
|
./tests/msc3391
|
||||||
|
./tests/msc3930
|
||||||
|
./tests/msc3902
|
||||||
|
./tests/msc3967
|
||||||
|
./tests/msc4115
|
||||||
|
)
|
||||||
|
|
||||||
# Enable dirty runs, so tests will reuse the same container where possible.
|
# Enable dirty runs, so tests will reuse the same container where possible.
|
||||||
# This significantly speeds up tests, but increases the possibility of test pollution.
|
# This significantly speeds up tests, but increases the possibility of test pollution.
|
||||||
|
@ -278,7 +288,7 @@ fi
|
||||||
export PASS_SYNAPSE_LOG_TESTING=1
|
export PASS_SYNAPSE_LOG_TESTING=1
|
||||||
|
|
||||||
# Run the tests!
|
# Run the tests!
|
||||||
echo "Images built; running complement with ${extra_test_args[@]} $@ $test_packages"
|
echo "Images built; running complement with ${extra_test_args[@]} $@ ${test_packages[@]}"
|
||||||
cd "$COMPLEMENT_DIR"
|
cd "$COMPLEMENT_DIR"
|
||||||
|
|
||||||
go test -v -tags "synapse_blacklist" -count=1 "${extra_test_args[@]}" "$@" $test_packages
|
go test -v -tags "synapse_blacklist" -count=1 "${extra_test_args[@]}" "$@" "${test_packages[@]}"
|
||||||
|
|
|
@ -91,7 +91,6 @@ else
|
||||||
"synapse" "docker" "tests"
|
"synapse" "docker" "tests"
|
||||||
"scripts-dev"
|
"scripts-dev"
|
||||||
"contrib" "synmark" "stubs" ".ci"
|
"contrib" "synmark" "stubs" ".ci"
|
||||||
"dev-docs"
|
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -127,7 +127,7 @@ BOOLEAN_COLUMNS = {
|
||||||
"redactions": ["have_censored"],
|
"redactions": ["have_censored"],
|
||||||
"room_stats_state": ["is_federatable"],
|
"room_stats_state": ["is_federatable"],
|
||||||
"rooms": ["is_public", "has_auth_chain_index"],
|
"rooms": ["is_public", "has_auth_chain_index"],
|
||||||
"users": ["shadow_banned", "approved", "locked"],
|
"users": ["shadow_banned", "approved", "locked", "suspended"],
|
||||||
"un_partial_stated_event_stream": ["rejection_status_changed"],
|
"un_partial_stated_event_stream": ["rejection_status_changed"],
|
||||||
"users_who_share_rooms": ["share_private"],
|
"users_who_share_rooms": ["share_private"],
|
||||||
"per_user_experimental_features": ["enabled"],
|
"per_user_experimental_features": ["enabled"],
|
||||||
|
|
|
@ -234,6 +234,13 @@ class EventContentFields:
|
||||||
TO_DEVICE_MSGID: Final = "org.matrix.msgid"
|
TO_DEVICE_MSGID: Final = "org.matrix.msgid"
|
||||||
|
|
||||||
|
|
||||||
|
class EventUnsignedContentFields:
|
||||||
|
"""Fields found inside the 'unsigned' data on events"""
|
||||||
|
|
||||||
|
# Requesting user's membership, per MSC4115
|
||||||
|
MSC4115_MEMBERSHIP: Final = "io.element.msc4115.membership"
|
||||||
|
|
||||||
|
|
||||||
class RoomTypes:
|
class RoomTypes:
|
||||||
"""Understood values of the room_type field of m.room.create events."""
|
"""Understood values of the room_type field of m.room.create events."""
|
||||||
|
|
||||||
|
|
|
@ -142,12 +142,12 @@ USER_FILTER_SCHEMA = {
|
||||||
|
|
||||||
@FormatChecker.cls_checks("matrix_room_id")
|
@FormatChecker.cls_checks("matrix_room_id")
|
||||||
def matrix_room_id_validator(room_id: object) -> bool:
|
def matrix_room_id_validator(room_id: object) -> bool:
|
||||||
return isinstance(room_id, str) and RoomID.is_valid(room_id)
|
return isinstance(room_id, str) and (RoomID.is_valid(room_id) or room_id == "*")
|
||||||
|
|
||||||
|
|
||||||
@FormatChecker.cls_checks("matrix_user_id")
|
@FormatChecker.cls_checks("matrix_user_id")
|
||||||
def matrix_user_id_validator(user_id: object) -> bool:
|
def matrix_user_id_validator(user_id: object) -> bool:
|
||||||
return isinstance(user_id, str) and UserID.is_valid(user_id)
|
return isinstance(user_id, str) and (UserID.is_valid(user_id) or user_id == "*")
|
||||||
|
|
||||||
|
|
||||||
class Filtering:
|
class Filtering:
|
||||||
|
|
|
@ -316,6 +316,10 @@ class Ratelimiter:
|
||||||
)
|
)
|
||||||
|
|
||||||
if not allowed:
|
if not allowed:
|
||||||
|
# We pause for a bit here to stop clients from "tight-looping" on
|
||||||
|
# retrying their request.
|
||||||
|
await self.clock.sleep(0.5)
|
||||||
|
|
||||||
raise LimitExceededError(
|
raise LimitExceededError(
|
||||||
limiter_name=self._limiter_name,
|
limiter_name=self._limiter_name,
|
||||||
retry_after_ms=int(1000 * (time_allowed - time_now_s)),
|
retry_after_ms=int(1000 * (time_allowed - time_now_s)),
|
||||||
|
|
|
@ -35,6 +35,7 @@ from synapse.config import ( # noqa: F401
|
||||||
jwt,
|
jwt,
|
||||||
key,
|
key,
|
||||||
logger,
|
logger,
|
||||||
|
meow,
|
||||||
metrics,
|
metrics,
|
||||||
modules,
|
modules,
|
||||||
oembed,
|
oembed,
|
||||||
|
@ -91,6 +92,7 @@ class RootConfig:
|
||||||
voip: voip.VoipConfig
|
voip: voip.VoipConfig
|
||||||
registration: registration.RegistrationConfig
|
registration: registration.RegistrationConfig
|
||||||
account_validity: account_validity.AccountValidityConfig
|
account_validity: account_validity.AccountValidityConfig
|
||||||
|
meow: meow.MeowConfig
|
||||||
metrics: metrics.MetricsConfig
|
metrics: metrics.MetricsConfig
|
||||||
api: api.ApiConfig
|
api: api.ApiConfig
|
||||||
appservice: appservice.AppServiceConfig
|
appservice: appservice.AppServiceConfig
|
||||||
|
|
|
@ -66,6 +66,17 @@ class CasConfig(Config):
|
||||||
|
|
||||||
self.cas_enable_registration = cas_config.get("enable_registration", True)
|
self.cas_enable_registration = cas_config.get("enable_registration", True)
|
||||||
|
|
||||||
|
self.cas_allow_numeric_ids = cas_config.get("allow_numeric_ids")
|
||||||
|
self.cas_numeric_ids_prefix = cas_config.get("numeric_ids_prefix")
|
||||||
|
if (
|
||||||
|
self.cas_numeric_ids_prefix is not None
|
||||||
|
and self.cas_numeric_ids_prefix.isalnum() is False
|
||||||
|
):
|
||||||
|
raise ConfigError(
|
||||||
|
"Only alphanumeric characters are allowed for numeric IDs prefix",
|
||||||
|
("cas_config", "numeric_ids_prefix"),
|
||||||
|
)
|
||||||
|
|
||||||
self.idp_name = cas_config.get("idp_name", "CAS")
|
self.idp_name = cas_config.get("idp_name", "CAS")
|
||||||
self.idp_icon = cas_config.get("idp_icon")
|
self.idp_icon = cas_config.get("idp_icon")
|
||||||
self.idp_brand = cas_config.get("idp_brand")
|
self.idp_brand = cas_config.get("idp_brand")
|
||||||
|
@ -77,6 +88,8 @@ class CasConfig(Config):
|
||||||
self.cas_displayname_attribute = None
|
self.cas_displayname_attribute = None
|
||||||
self.cas_required_attributes = []
|
self.cas_required_attributes = []
|
||||||
self.cas_enable_registration = False
|
self.cas_enable_registration = False
|
||||||
|
self.cas_allow_numeric_ids = False
|
||||||
|
self.cas_numeric_ids_prefix = "u"
|
||||||
|
|
||||||
|
|
||||||
# CAS uses a legacy required attributes mapping, not the one provided by
|
# CAS uses a legacy required attributes mapping, not the one provided by
|
||||||
|
|
|
@ -52,6 +52,7 @@ DEFAULT_SUBJECTS = {
|
||||||
"invite_from_person_to_space": "[%(app)s] %(person)s has invited you to join the %(space)s space on %(app)s...",
|
"invite_from_person_to_space": "[%(app)s] %(person)s has invited you to join the %(space)s space on %(app)s...",
|
||||||
"password_reset": "[%(server_name)s] Password reset",
|
"password_reset": "[%(server_name)s] Password reset",
|
||||||
"email_validation": "[%(server_name)s] Validate your email",
|
"email_validation": "[%(server_name)s] Validate your email",
|
||||||
|
"email_already_in_use": "[%(server_name)s] Email already in use",
|
||||||
}
|
}
|
||||||
|
|
||||||
LEGACY_TEMPLATE_DIR_WARNING = """
|
LEGACY_TEMPLATE_DIR_WARNING = """
|
||||||
|
@ -76,6 +77,7 @@ class EmailSubjectConfig:
|
||||||
invite_from_person_to_space: str
|
invite_from_person_to_space: str
|
||||||
password_reset: str
|
password_reset: str
|
||||||
email_validation: str
|
email_validation: str
|
||||||
|
email_already_in_use: str
|
||||||
|
|
||||||
|
|
||||||
class EmailConfig(Config):
|
class EmailConfig(Config):
|
||||||
|
@ -180,6 +182,12 @@ class EmailConfig(Config):
|
||||||
registration_template_text = email_config.get(
|
registration_template_text = email_config.get(
|
||||||
"registration_template_text", "registration.txt"
|
"registration_template_text", "registration.txt"
|
||||||
)
|
)
|
||||||
|
already_in_use_template_html = email_config.get(
|
||||||
|
"already_in_use_template_html", "already_in_use.html"
|
||||||
|
)
|
||||||
|
already_in_use_template_text = email_config.get(
|
||||||
|
"already_in_use_template_html", "already_in_use.txt"
|
||||||
|
)
|
||||||
add_threepid_template_html = email_config.get(
|
add_threepid_template_html = email_config.get(
|
||||||
"add_threepid_template_html", "add_threepid.html"
|
"add_threepid_template_html", "add_threepid.html"
|
||||||
)
|
)
|
||||||
|
@ -215,6 +223,8 @@ class EmailConfig(Config):
|
||||||
self.email_password_reset_template_text,
|
self.email_password_reset_template_text,
|
||||||
self.email_registration_template_html,
|
self.email_registration_template_html,
|
||||||
self.email_registration_template_text,
|
self.email_registration_template_text,
|
||||||
|
self.email_already_in_use_template_html,
|
||||||
|
self.email_already_in_use_template_text,
|
||||||
self.email_add_threepid_template_html,
|
self.email_add_threepid_template_html,
|
||||||
self.email_add_threepid_template_text,
|
self.email_add_threepid_template_text,
|
||||||
self.email_password_reset_template_confirmation_html,
|
self.email_password_reset_template_confirmation_html,
|
||||||
|
@ -230,6 +240,8 @@ class EmailConfig(Config):
|
||||||
password_reset_template_text,
|
password_reset_template_text,
|
||||||
registration_template_html,
|
registration_template_html,
|
||||||
registration_template_text,
|
registration_template_text,
|
||||||
|
already_in_use_template_html,
|
||||||
|
already_in_use_template_text,
|
||||||
add_threepid_template_html,
|
add_threepid_template_html,
|
||||||
add_threepid_template_text,
|
add_threepid_template_text,
|
||||||
"password_reset_confirmation.html",
|
"password_reset_confirmation.html",
|
||||||
|
|
|
@ -411,3 +411,28 @@ class ExperimentalConfig(Config):
|
||||||
self.msc4069_profile_inhibit_propagation = experimental.get(
|
self.msc4069_profile_inhibit_propagation = experimental.get(
|
||||||
"msc4069_profile_inhibit_propagation", False
|
"msc4069_profile_inhibit_propagation", False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code
|
||||||
|
self.msc4108_enabled = experimental.get("msc4108_enabled", False)
|
||||||
|
|
||||||
|
self.msc4108_delegation_endpoint: Optional[str] = experimental.get(
|
||||||
|
"msc4108_delegation_endpoint", None
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.msc4108_enabled or self.msc4108_delegation_endpoint is not None
|
||||||
|
) and not self.msc3861.enabled:
|
||||||
|
raise ConfigError(
|
||||||
|
"MSC4108 requires MSC3861 to be enabled",
|
||||||
|
("experimental", "msc4108_delegation_endpoint"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.msc4108_delegation_endpoint is not None and self.msc4108_enabled:
|
||||||
|
raise ConfigError(
|
||||||
|
"You cannot have MSC4108 both enabled and delegated at the same time",
|
||||||
|
("experimental", "msc4108_delegation_endpoint"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.msc4115_membership_on_events = experimental.get(
|
||||||
|
"msc4115_membership_on_events", False
|
||||||
|
)
|
||||||
|
|
|
@ -42,6 +42,10 @@ class FederationConfig(Config):
|
||||||
for domain in federation_domain_whitelist:
|
for domain in federation_domain_whitelist:
|
||||||
self.federation_domain_whitelist[domain] = True
|
self.federation_domain_whitelist[domain] = True
|
||||||
|
|
||||||
|
self.federation_whitelist_endpoint_enabled = config.get(
|
||||||
|
"federation_whitelist_endpoint_enabled", False
|
||||||
|
)
|
||||||
|
|
||||||
federation_metrics_domains = config.get("federation_metrics_domains") or []
|
federation_metrics_domains = config.get("federation_metrics_domains") or []
|
||||||
validate_config(
|
validate_config(
|
||||||
_METRICS_FOR_DOMAINS_SCHEMA,
|
_METRICS_FOR_DOMAINS_SCHEMA,
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
from ._base import RootConfig
|
from ._base import RootConfig
|
||||||
|
from .meow import MeowConfig
|
||||||
from .account_validity import AccountValidityConfig
|
from .account_validity import AccountValidityConfig
|
||||||
from .api import ApiConfig
|
from .api import ApiConfig
|
||||||
from .appservice import AppServiceConfig
|
from .appservice import AppServiceConfig
|
||||||
|
@ -64,6 +65,7 @@ from .workers import WorkerConfig
|
||||||
|
|
||||||
class HomeServerConfig(RootConfig):
|
class HomeServerConfig(RootConfig):
|
||||||
config_classes = [
|
config_classes = [
|
||||||
|
MeowConfig,
|
||||||
ModulesConfig,
|
ModulesConfig,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
RetentionConfig,
|
RetentionConfig,
|
||||||
|
|
33
synapse/config/meow.py
Normal file
33
synapse/config/meow.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Maunium
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from ._base import Config
|
||||||
|
|
||||||
|
|
||||||
|
class MeowConfig(Config):
|
||||||
|
"""Meow Configuration
|
||||||
|
Configuration for disabling dumb limits in Synapse
|
||||||
|
"""
|
||||||
|
|
||||||
|
section = "meow"
|
||||||
|
|
||||||
|
def read_config(self, config, **kwargs):
|
||||||
|
meow_config = config.get("meow", {})
|
||||||
|
self.validation_override = set(meow_config.get("validation_override", []))
|
||||||
|
self.filter_override = set(meow_config.get("filter_override", []))
|
||||||
|
self.timestamp_override = set(meow_config.get("timestamp_override", []))
|
||||||
|
self.admin_api_register_invalid = meow_config.get(
|
||||||
|
"admin_api_register_invalid", True
|
||||||
|
)
|
|
@ -54,10 +54,8 @@ THUMBNAIL_SIZE_YAML = """\
|
||||||
THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP = {
|
THUMBNAIL_SUPPORTED_MEDIA_FORMAT_MAP = {
|
||||||
"image/jpeg": "jpeg",
|
"image/jpeg": "jpeg",
|
||||||
"image/jpg": "jpeg",
|
"image/jpg": "jpeg",
|
||||||
"image/webp": "jpeg",
|
"image/webp": "webp",
|
||||||
# Thumbnails can only be jpeg or png. We choose png thumbnails for gif
|
"image/gif": "webp",
|
||||||
# because it can have transparency.
|
|
||||||
"image/gif": "png",
|
|
||||||
"image/png": "png",
|
"image/png": "png",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +107,10 @@ def parse_thumbnail_requirements(
|
||||||
requirement.append(
|
requirement.append(
|
||||||
ThumbnailRequirement(width, height, method, "image/png")
|
ThumbnailRequirement(width, height, method, "image/png")
|
||||||
)
|
)
|
||||||
|
elif thumbnail_format == "webp":
|
||||||
|
requirement.append(
|
||||||
|
ThumbnailRequirement(width, height, method, "image/webp")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Unknown thumbnail mapping from %s to %s. This is a Synapse problem, please report!"
|
"Unknown thumbnail mapping from %s to %s. This is a Synapse problem, please report!"
|
||||||
|
|
|
@ -49,7 +49,7 @@ from synapse.api.errors import Codes, SynapseError
|
||||||
from synapse.api.room_versions import RoomVersion
|
from synapse.api.room_versions import RoomVersion
|
||||||
from synapse.types import JsonDict, Requester
|
from synapse.types import JsonDict, Requester
|
||||||
|
|
||||||
from . import EventBase
|
from . import EventBase, make_event_from_dict
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.handlers.relations import BundledAggregations
|
from synapse.handlers.relations import BundledAggregations
|
||||||
|
@ -82,17 +82,14 @@ def prune_event(event: EventBase) -> EventBase:
|
||||||
"""
|
"""
|
||||||
pruned_event_dict = prune_event_dict(event.room_version, event.get_dict())
|
pruned_event_dict = prune_event_dict(event.room_version, event.get_dict())
|
||||||
|
|
||||||
from . import make_event_from_dict
|
|
||||||
|
|
||||||
pruned_event = make_event_from_dict(
|
pruned_event = make_event_from_dict(
|
||||||
pruned_event_dict, event.room_version, event.internal_metadata.get_dict()
|
pruned_event_dict, event.room_version, event.internal_metadata.get_dict()
|
||||||
)
|
)
|
||||||
|
|
||||||
# copy the internal fields
|
# Copy the bits of `internal_metadata` that aren't returned by `get_dict`
|
||||||
pruned_event.internal_metadata.stream_ordering = (
|
pruned_event.internal_metadata.stream_ordering = (
|
||||||
event.internal_metadata.stream_ordering
|
event.internal_metadata.stream_ordering
|
||||||
)
|
)
|
||||||
|
|
||||||
pruned_event.internal_metadata.outlier = event.internal_metadata.outlier
|
pruned_event.internal_metadata.outlier = event.internal_metadata.outlier
|
||||||
|
|
||||||
# Mark the event as redacted
|
# Mark the event as redacted
|
||||||
|
@ -101,6 +98,29 @@ def prune_event(event: EventBase) -> EventBase:
|
||||||
return pruned_event
|
return pruned_event
|
||||||
|
|
||||||
|
|
||||||
|
def clone_event(event: EventBase) -> EventBase:
|
||||||
|
"""Take a copy of the event.
|
||||||
|
|
||||||
|
This is mostly useful because it does a *shallow* copy of the `unsigned` data,
|
||||||
|
which means it can then be updated without corrupting the in-memory cache. Note that
|
||||||
|
other properties of the event, such as `content`, are *not* (currently) copied here.
|
||||||
|
"""
|
||||||
|
# XXX: We rely on at least one of `event.get_dict()` and `make_event_from_dict()`
|
||||||
|
# making a copy of `unsigned`. Currently, both do, though I don't really know why.
|
||||||
|
# Still, as long as they do, there's not much point doing yet another copy here.
|
||||||
|
new_event = make_event_from_dict(
|
||||||
|
event.get_dict(), event.room_version, event.internal_metadata.get_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy the bits of `internal_metadata` that aren't returned by `get_dict`.
|
||||||
|
new_event.internal_metadata.stream_ordering = (
|
||||||
|
event.internal_metadata.stream_ordering
|
||||||
|
)
|
||||||
|
new_event.internal_metadata.outlier = event.internal_metadata.outlier
|
||||||
|
|
||||||
|
return new_event
|
||||||
|
|
||||||
|
|
||||||
def prune_event_dict(room_version: RoomVersion, event_dict: JsonDict) -> JsonDict:
|
def prune_event_dict(room_version: RoomVersion, event_dict: JsonDict) -> JsonDict:
|
||||||
"""Redacts the event_dict in the same way as `prune_event`, except it
|
"""Redacts the event_dict in the same way as `prune_event`, except it
|
||||||
operates on dicts rather than event objects
|
operates on dicts rather than event objects
|
||||||
|
@ -485,6 +505,11 @@ def serialize_event(
|
||||||
):
|
):
|
||||||
d["unsigned"]["transaction_id"] = txn_id
|
d["unsigned"]["transaction_id"] = txn_id
|
||||||
|
|
||||||
|
# Beeper: include internal stream ordering as HS order unsigned hint
|
||||||
|
stream_ordering = getattr(e.internal_metadata, "stream_ordering", None)
|
||||||
|
if stream_ordering:
|
||||||
|
d["unsigned"]["com.beeper.hs.order"] = stream_ordering
|
||||||
|
|
||||||
# invite_room_state and knock_room_state are a list of stripped room state events
|
# invite_room_state and knock_room_state are a list of stripped room state events
|
||||||
# that are meant to provide metadata about a room to an invitee/knocker. They are
|
# that are meant to provide metadata about a room to an invitee/knocker. They are
|
||||||
# intended to only be included in specific circumstances, such as down sync, and
|
# intended to only be included in specific circumstances, such as down sync, and
|
||||||
|
|
|
@ -64,7 +64,7 @@ class EventValidator:
|
||||||
event: The event to validate.
|
event: The event to validate.
|
||||||
config: The homeserver's configuration.
|
config: The homeserver's configuration.
|
||||||
"""
|
"""
|
||||||
self.validate_builder(event)
|
self.validate_builder(event, config)
|
||||||
|
|
||||||
if event.format_version == EventFormatVersions.ROOM_V1_V2:
|
if event.format_version == EventFormatVersions.ROOM_V1_V2:
|
||||||
EventID.from_string(event.event_id)
|
EventID.from_string(event.event_id)
|
||||||
|
@ -95,6 +95,12 @@ class EventValidator:
|
||||||
# Note that only the client controlled portion of the event is
|
# Note that only the client controlled portion of the event is
|
||||||
# checked, since we trust the portions of the event we created.
|
# checked, since we trust the portions of the event we created.
|
||||||
validate_canonicaljson(event.content)
|
validate_canonicaljson(event.content)
|
||||||
|
if not 0 < event.origin_server_ts < 2**53:
|
||||||
|
raise SynapseError(400, "Event timestamp is out of range")
|
||||||
|
|
||||||
|
# meow: allow specific users to send potentially dangerous events.
|
||||||
|
if event.sender in config.meow.validation_override:
|
||||||
|
return
|
||||||
|
|
||||||
if event.type == EventTypes.Aliases:
|
if event.type == EventTypes.Aliases:
|
||||||
if "aliases" in event.content:
|
if "aliases" in event.content:
|
||||||
|
@ -193,7 +199,9 @@ class EventValidator:
|
||||||
errcode=Codes.BAD_JSON,
|
errcode=Codes.BAD_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_builder(self, event: Union[EventBase, EventBuilder]) -> None:
|
def validate_builder(
|
||||||
|
self, event: Union[EventBase, EventBuilder], config: HomeServerConfig
|
||||||
|
) -> None:
|
||||||
"""Validates that the builder/event has roughly the right format. Only
|
"""Validates that the builder/event has roughly the right format. Only
|
||||||
checks values that we expect a proto event to have, rather than all the
|
checks values that we expect a proto event to have, rather than all the
|
||||||
fields an event would have
|
fields an event would have
|
||||||
|
@ -211,6 +219,10 @@ class EventValidator:
|
||||||
RoomID.from_string(event.room_id)
|
RoomID.from_string(event.room_id)
|
||||||
UserID.from_string(event.sender)
|
UserID.from_string(event.sender)
|
||||||
|
|
||||||
|
# meow: allow specific users to send so-called invalid events
|
||||||
|
if event.sender in config.meow.validation_override:
|
||||||
|
return
|
||||||
|
|
||||||
if event.type == EventTypes.Message:
|
if event.type == EventTypes.Message:
|
||||||
strings = ["body", "msgtype"]
|
strings = ["body", "msgtype"]
|
||||||
|
|
||||||
|
|
|
@ -546,7 +546,25 @@ class FederationServer(FederationBase):
|
||||||
edu_type=edu_dict["edu_type"],
|
edu_type=edu_dict["edu_type"],
|
||||||
content=edu_dict["content"],
|
content=edu_dict["content"],
|
||||||
)
|
)
|
||||||
await self.registry.on_edu(edu.edu_type, origin, edu.content)
|
try:
|
||||||
|
await self.registry.on_edu(edu.edu_type, origin, edu.content)
|
||||||
|
except Exception:
|
||||||
|
# If there was an error handling the EDU, we must reject the
|
||||||
|
# transaction.
|
||||||
|
#
|
||||||
|
# Some EDU types (notably, to-device messages) are, despite their name,
|
||||||
|
# expected to be reliable; if we weren't able to do something with it,
|
||||||
|
# we have to tell the sender that, and the only way the protocol gives
|
||||||
|
# us to do so is by sending an HTTP error back on the transaction.
|
||||||
|
#
|
||||||
|
# We log the exception now, and then raise a new SynapseError to cause
|
||||||
|
# the transaction to be failed.
|
||||||
|
logger.exception("Error handling EDU of type %s", edu.edu_type)
|
||||||
|
raise SynapseError(500, f"Error handing EDU of type {edu.edu_type}")
|
||||||
|
|
||||||
|
# TODO: if the first EDU fails, we should probably abort the whole
|
||||||
|
# thing rather than carrying on with the rest of them. That would
|
||||||
|
# probably be best done inside `concurrently_execute`.
|
||||||
|
|
||||||
await concurrently_execute(
|
await concurrently_execute(
|
||||||
_process_edu,
|
_process_edu,
|
||||||
|
@ -1414,12 +1432,7 @@ class FederationHandlerRegistry:
|
||||||
handler = self.edu_handlers.get(edu_type)
|
handler = self.edu_handlers.get(edu_type)
|
||||||
if handler:
|
if handler:
|
||||||
with start_active_span_from_edu(content, "handle_edu"):
|
with start_active_span_from_edu(content, "handle_edu"):
|
||||||
try:
|
await handler(origin, content)
|
||||||
await handler(origin, content)
|
|
||||||
except SynapseError as e:
|
|
||||||
logger.info("Failed to handle edu %r: %r", edu_type, e)
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Failed to handle edu %r", edu_type)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if we can route it somewhere else that isn't us
|
# Check if we can route it somewhere else that isn't us
|
||||||
|
@ -1428,17 +1441,12 @@ class FederationHandlerRegistry:
|
||||||
# Pick an instance randomly so that we don't overload one.
|
# Pick an instance randomly so that we don't overload one.
|
||||||
route_to = random.choice(instances)
|
route_to = random.choice(instances)
|
||||||
|
|
||||||
try:
|
await self._send_edu(
|
||||||
await self._send_edu(
|
instance_name=route_to,
|
||||||
instance_name=route_to,
|
edu_type=edu_type,
|
||||||
edu_type=edu_type,
|
origin=origin,
|
||||||
origin=origin,
|
content=content,
|
||||||
content=content,
|
)
|
||||||
)
|
|
||||||
except SynapseError as e:
|
|
||||||
logger.info("Failed to handle edu %r: %r", edu_type, e)
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Failed to handle edu %r", edu_type)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Oh well, let's just log and move on.
|
# Oh well, let's just log and move on.
|
||||||
|
|
|
@ -180,7 +180,11 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str, Optional[str
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
header_str = header_bytes.decode("utf-8")
|
header_str = header_bytes.decode("utf-8")
|
||||||
params = re.split(" +", header_str)[1].split(",")
|
space_or_tab = "[ \t]"
|
||||||
|
params = re.split(
|
||||||
|
rf"{space_or_tab}*,{space_or_tab}*",
|
||||||
|
re.split(r"^X-Matrix +", header_str, maxsplit=1)[1],
|
||||||
|
)
|
||||||
param_dict: Dict[str, str] = {
|
param_dict: Dict[str, str] = {
|
||||||
k.lower(): v for k, v in [param.split("=", maxsplit=1) for param in params]
|
k.lower(): v for k, v in [param.split("=", maxsplit=1) for param in params]
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ class AdminHandler:
|
||||||
self._device_handler = hs.get_device_handler()
|
self._device_handler = hs.get_device_handler()
|
||||||
self._storage_controllers = hs.get_storage_controllers()
|
self._storage_controllers = hs.get_storage_controllers()
|
||||||
self._state_storage_controller = self._storage_controllers.state
|
self._state_storage_controller = self._storage_controllers.state
|
||||||
|
self._hs_config = hs.config
|
||||||
self._msc3866_enabled = hs.config.experimental.msc3866.enabled
|
self._msc3866_enabled = hs.config.experimental.msc3866.enabled
|
||||||
|
|
||||||
async def get_whois(self, user: UserID) -> JsonMapping:
|
async def get_whois(self, user: UserID) -> JsonMapping:
|
||||||
|
@ -217,7 +218,10 @@ class AdminHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
events = await filter_events_for_client(
|
events = await filter_events_for_client(
|
||||||
self._storage_controllers, user_id, events
|
self._storage_controllers,
|
||||||
|
user_id,
|
||||||
|
events,
|
||||||
|
msc4115_membership_on_events=self._hs_config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
writer.write_events(room_id, events)
|
writer.write_events(room_id, events)
|
||||||
|
|
|
@ -78,6 +78,8 @@ class CasHandler:
|
||||||
self._cas_displayname_attribute = hs.config.cas.cas_displayname_attribute
|
self._cas_displayname_attribute = hs.config.cas.cas_displayname_attribute
|
||||||
self._cas_required_attributes = hs.config.cas.cas_required_attributes
|
self._cas_required_attributes = hs.config.cas.cas_required_attributes
|
||||||
self._cas_enable_registration = hs.config.cas.cas_enable_registration
|
self._cas_enable_registration = hs.config.cas.cas_enable_registration
|
||||||
|
self._cas_allow_numeric_ids = hs.config.cas.cas_allow_numeric_ids
|
||||||
|
self._cas_numeric_ids_prefix = hs.config.cas.cas_numeric_ids_prefix
|
||||||
|
|
||||||
self._http_client = hs.get_proxied_http_client()
|
self._http_client = hs.get_proxied_http_client()
|
||||||
|
|
||||||
|
@ -188,6 +190,9 @@ class CasHandler:
|
||||||
for child in root[0]:
|
for child in root[0]:
|
||||||
if child.tag.endswith("user"):
|
if child.tag.endswith("user"):
|
||||||
user = child.text
|
user = child.text
|
||||||
|
# if numeric user IDs are allowed and username is numeric then we add the prefix so Synapse can handle it
|
||||||
|
if self._cas_allow_numeric_ids and user is not None and user.isdigit():
|
||||||
|
user = f"{self._cas_numeric_ids_prefix}{user}"
|
||||||
if child.tag.endswith("attributes"):
|
if child.tag.endswith("attributes"):
|
||||||
for attribute in child:
|
for attribute in child:
|
||||||
# ElementTree library expands the namespace in
|
# ElementTree library expands the namespace in
|
||||||
|
|
|
@ -261,11 +261,22 @@ class DeactivateAccountHandler:
|
||||||
user = UserID.from_string(user_id)
|
user = UserID.from_string(user_id)
|
||||||
|
|
||||||
rooms_for_user = await self.store.get_rooms_for_user(user_id)
|
rooms_for_user = await self.store.get_rooms_for_user(user_id)
|
||||||
|
requester = create_requester(user, authenticated_entity=self._server_name)
|
||||||
|
should_erase = await self.store.is_user_erased(user_id)
|
||||||
|
|
||||||
for room_id in rooms_for_user:
|
for room_id in rooms_for_user:
|
||||||
logger.info("User parter parting %r from %r", user_id, room_id)
|
logger.info("User parter parting %r from %r", user_id, room_id)
|
||||||
try:
|
try:
|
||||||
|
# Before parting the user, redact all membership events if requested
|
||||||
|
if should_erase:
|
||||||
|
event_ids = await self.store.get_membership_event_ids_for_user(
|
||||||
|
user_id, room_id
|
||||||
|
)
|
||||||
|
for event_id in event_ids:
|
||||||
|
await self.store.expire_event(event_id)
|
||||||
|
|
||||||
await self._room_member_handler.update_membership(
|
await self._room_member_handler.update_membership(
|
||||||
create_requester(user, authenticated_entity=self._server_name),
|
requester,
|
||||||
user,
|
user,
|
||||||
room_id,
|
room_id,
|
||||||
"leave",
|
"leave",
|
||||||
|
|
|
@ -104,6 +104,9 @@ class DeviceMessageHandler:
|
||||||
"""
|
"""
|
||||||
Handle receiving to-device messages from remote homeservers.
|
Handle receiving to-device messages from remote homeservers.
|
||||||
|
|
||||||
|
Note that any errors thrown from this method will cause the federation /send
|
||||||
|
request to receive an error response.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
origin: The remote homeserver.
|
origin: The remote homeserver.
|
||||||
content: The JSON dictionary containing the to-device messages.
|
content: The JSON dictionary containing the to-device messages.
|
||||||
|
|
|
@ -80,9 +80,11 @@ class DirectoryHandler:
|
||||||
) -> None:
|
) -> None:
|
||||||
# general association creation for both human users and app services
|
# general association creation for both human users and app services
|
||||||
|
|
||||||
for wchar in string.whitespace:
|
# meow: allow specific users to include anything in room aliases
|
||||||
if wchar in room_alias.localpart:
|
if creator not in self.config.meow.validation_override:
|
||||||
raise SynapseError(400, "Invalid characters in room alias")
|
for wchar in string.whitespace:
|
||||||
|
if wchar in room_alias.localpart:
|
||||||
|
raise SynapseError(400, "Invalid characters in room alias")
|
||||||
|
|
||||||
if ":" in room_alias.localpart:
|
if ":" in room_alias.localpart:
|
||||||
raise SynapseError(400, "Invalid character in room alias localpart: ':'.")
|
raise SynapseError(400, "Invalid character in room alias localpart: ':'.")
|
||||||
|
@ -127,7 +129,10 @@ class DirectoryHandler:
|
||||||
user_id = requester.user.to_string()
|
user_id = requester.user.to_string()
|
||||||
room_alias_str = room_alias.to_string()
|
room_alias_str = room_alias.to_string()
|
||||||
|
|
||||||
if len(room_alias_str) > MAX_ALIAS_LENGTH:
|
if (
|
||||||
|
user_id not in self.hs.config.meow.validation_override
|
||||||
|
and len(room_alias_str) > MAX_ALIAS_LENGTH
|
||||||
|
):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400,
|
400,
|
||||||
"Can't create aliases longer than %s characters" % MAX_ALIAS_LENGTH,
|
"Can't create aliases longer than %s characters" % MAX_ALIAS_LENGTH,
|
||||||
|
@ -169,7 +174,7 @@ class DirectoryHandler:
|
||||||
|
|
||||||
if not self.config.roomdirectory.is_alias_creation_allowed(
|
if not self.config.roomdirectory.is_alias_creation_allowed(
|
||||||
user_id, room_id, room_alias_str
|
user_id, room_id, room_alias_str
|
||||||
):
|
) and not is_admin:
|
||||||
# Let's just return a generic message, as there may be all sorts of
|
# Let's just return a generic message, as there may be all sorts of
|
||||||
# reasons why we said no. TODO: Allow configurable error messages
|
# reasons why we said no. TODO: Allow configurable error messages
|
||||||
# per alias creation rule?
|
# per alias creation rule?
|
||||||
|
@ -505,7 +510,7 @@ class DirectoryHandler:
|
||||||
|
|
||||||
if not self.config.roomdirectory.is_publishing_room_allowed(
|
if not self.config.roomdirectory.is_publishing_room_allowed(
|
||||||
user_id, room_id, room_aliases
|
user_id, room_id, room_aliases
|
||||||
):
|
) and not await self.auth.is_server_admin(requester):
|
||||||
# Let's just return a generic message, as there may be all sorts of
|
# Let's just return a generic message, as there may be all sorts of
|
||||||
# reasons why we said no. TODO: Allow configurable error messages
|
# reasons why we said no. TODO: Allow configurable error messages
|
||||||
# per alias creation rule?
|
# per alias creation rule?
|
||||||
|
|
|
@ -1476,6 +1476,42 @@ class E2eKeysHandler:
|
||||||
else:
|
else:
|
||||||
return exists, self.clock.time_msec() < ts_replacable_without_uia_before
|
return exists, self.clock.time_msec() < ts_replacable_without_uia_before
|
||||||
|
|
||||||
|
async def has_different_keys(self, user_id: str, body: JsonDict) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a key provided in `body` differs from the same key stored in the DB. Returns
|
||||||
|
true on the first difference. If a key exists in `body` but does not exist in the DB,
|
||||||
|
returns True. If `body` has no keys, this always returns False.
|
||||||
|
Note by 'key' we mean Matrix key rather than JSON key.
|
||||||
|
|
||||||
|
The purpose of this function is to detect whether or not we need to apply UIA checks.
|
||||||
|
We must apply UIA checks if any key in the database is being overwritten. If a key is
|
||||||
|
being inserted for the first time, or if the key exactly matches what is in the database,
|
||||||
|
then no UIA check needs to be performed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The user who sent the `body`.
|
||||||
|
body: The JSON request body from POST /keys/device_signing/upload
|
||||||
|
Returns:
|
||||||
|
True if any key in `body` has a different value in the database.
|
||||||
|
"""
|
||||||
|
# Ensure that each key provided in the request body exactly matches the one we have stored.
|
||||||
|
# The first time we see the DB having a different key to the matching request key, bail.
|
||||||
|
# Note: we do not care if the DB has a key which the request does not specify, as we only
|
||||||
|
# care about *replacements* or *insertions* (i.e UPSERT)
|
||||||
|
req_body_key_to_db_key = {
|
||||||
|
"master_key": "master",
|
||||||
|
"self_signing_key": "self_signing",
|
||||||
|
"user_signing_key": "user_signing",
|
||||||
|
}
|
||||||
|
for req_body_key, db_key in req_body_key_to_db_key.items():
|
||||||
|
if req_body_key in body:
|
||||||
|
existing_key = await self.store.get_e2e_cross_signing_key(
|
||||||
|
user_id, db_key
|
||||||
|
)
|
||||||
|
if existing_key != body[req_body_key]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _check_cross_signing_key(
|
def _check_cross_signing_key(
|
||||||
key: JsonDict, user_id: str, key_type: str, signing_key: Optional[VerifyKey] = None
|
key: JsonDict, user_id: str, key_type: str, signing_key: Optional[VerifyKey] = None
|
||||||
|
|
|
@ -148,6 +148,7 @@ class EventHandler:
|
||||||
def __init__(self, hs: "HomeServer"):
|
def __init__(self, hs: "HomeServer"):
|
||||||
self.store = hs.get_datastores().main
|
self.store = hs.get_datastores().main
|
||||||
self._storage_controllers = hs.get_storage_controllers()
|
self._storage_controllers = hs.get_storage_controllers()
|
||||||
|
self._config = hs.config
|
||||||
|
|
||||||
async def get_event(
|
async def get_event(
|
||||||
self,
|
self,
|
||||||
|
@ -189,7 +190,11 @@ class EventHandler:
|
||||||
is_peeking = not is_user_in_room
|
is_peeking = not is_user_in_room
|
||||||
|
|
||||||
filtered = await filter_events_for_client(
|
filtered = await filter_events_for_client(
|
||||||
self._storage_controllers, user.to_string(), [event], is_peeking=is_peeking
|
self._storage_controllers,
|
||||||
|
user.to_string(),
|
||||||
|
[event],
|
||||||
|
is_peeking=is_peeking,
|
||||||
|
msc4115_membership_on_events=self._config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not filtered:
|
if not filtered:
|
||||||
|
|
|
@ -1452,7 +1452,7 @@ class FederationHandler:
|
||||||
room_version_obj, event_dict
|
room_version_obj, event_dict
|
||||||
)
|
)
|
||||||
|
|
||||||
EventValidator().validate_builder(builder)
|
EventValidator().validate_builder(builder, self.hs.config)
|
||||||
|
|
||||||
# Try several times, it could fail with PartialStateConflictError
|
# Try several times, it could fail with PartialStateConflictError
|
||||||
# in send_membership_event, cf comment in except block.
|
# in send_membership_event, cf comment in except block.
|
||||||
|
@ -1617,7 +1617,7 @@ class FederationHandler:
|
||||||
builder = self.event_builder_factory.for_room_version(
|
builder = self.event_builder_factory.for_room_version(
|
||||||
room_version_obj, event_dict
|
room_version_obj, event_dict
|
||||||
)
|
)
|
||||||
EventValidator().validate_builder(builder)
|
EventValidator().validate_builder(builder, self.hs.config)
|
||||||
|
|
||||||
(
|
(
|
||||||
event,
|
event,
|
||||||
|
|
|
@ -221,7 +221,10 @@ class InitialSyncHandler:
|
||||||
).addErrback(unwrapFirstError)
|
).addErrback(unwrapFirstError)
|
||||||
|
|
||||||
messages = await filter_events_for_client(
|
messages = await filter_events_for_client(
|
||||||
self._storage_controllers, user_id, messages
|
self._storage_controllers,
|
||||||
|
user_id,
|
||||||
|
messages,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace(StreamKeyType.ROOM, token)
|
start_token = now_token.copy_and_replace(StreamKeyType.ROOM, token)
|
||||||
|
@ -380,6 +383,7 @@ class InitialSyncHandler:
|
||||||
requester.user.to_string(),
|
requester.user.to_string(),
|
||||||
messages,
|
messages,
|
||||||
is_peeking=is_peeking,
|
is_peeking=is_peeking,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = StreamToken.START.copy_and_replace(StreamKeyType.ROOM, token)
|
start_token = StreamToken.START.copy_and_replace(StreamKeyType.ROOM, token)
|
||||||
|
@ -494,6 +498,7 @@ class InitialSyncHandler:
|
||||||
requester.user.to_string(),
|
requester.user.to_string(),
|
||||||
messages,
|
messages,
|
||||||
is_peeking=is_peeking,
|
is_peeking=is_peeking,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_token = now_token.copy_and_replace(StreamKeyType.ROOM, token)
|
start_token = now_token.copy_and_replace(StreamKeyType.ROOM, token)
|
||||||
|
|
|
@ -670,7 +670,7 @@ class EventCreationHandler:
|
||||||
room_version_obj, event_dict
|
room_version_obj, event_dict
|
||||||
)
|
)
|
||||||
|
|
||||||
self.validator.validate_builder(builder)
|
self.validator.validate_builder(builder, self.config)
|
||||||
|
|
||||||
if builder.type == EventTypes.Member:
|
if builder.type == EventTypes.Member:
|
||||||
membership = builder.content.get("membership", None)
|
membership = builder.content.get("membership", None)
|
||||||
|
@ -1352,6 +1352,8 @@ class EventCreationHandler:
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError if the event is invalid.
|
SynapseError if the event is invalid.
|
||||||
"""
|
"""
|
||||||
|
if event.sender in self.config.meow.validation_override:
|
||||||
|
return
|
||||||
|
|
||||||
relation = relation_from_event(event)
|
relation = relation_from_event(event)
|
||||||
if not relation:
|
if not relation:
|
||||||
|
@ -1770,7 +1772,8 @@ class EventCreationHandler:
|
||||||
|
|
||||||
await self._maybe_kick_guest_users(event, context)
|
await self._maybe_kick_guest_users(event, context)
|
||||||
|
|
||||||
if event.type == EventTypes.CanonicalAlias:
|
validation_override = event.sender in self.config.meow.validation_override
|
||||||
|
if event.type == EventTypes.CanonicalAlias and not validation_override:
|
||||||
# Validate a newly added alias or newly added alt_aliases.
|
# Validate a newly added alias or newly added alt_aliases.
|
||||||
|
|
||||||
original_alias = None
|
original_alias = None
|
||||||
|
@ -2125,7 +2128,7 @@ class EventCreationHandler:
|
||||||
builder = self.event_builder_factory.for_room_version(
|
builder = self.event_builder_factory.for_room_version(
|
||||||
original_event.room_version, third_party_result
|
original_event.room_version, third_party_result
|
||||||
)
|
)
|
||||||
self.validator.validate_builder(builder)
|
self.validator.validate_builder(builder, self.config)
|
||||||
except SynapseError as e:
|
except SynapseError as e:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Third party rules module created an invalid event: " + e.msg,
|
"Third party rules module created an invalid event: " + e.msg,
|
||||||
|
|
|
@ -623,6 +623,7 @@ class PaginationHandler:
|
||||||
user_id,
|
user_id,
|
||||||
events,
|
events,
|
||||||
is_peeking=(member_event_id is None),
|
is_peeking=(member_event_id is None),
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if after the filter applied there are no more events
|
# if after the filter applied there are no more events
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from typing import TYPE_CHECKING, Optional, Union
|
from typing import TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
AuthError,
|
AuthError,
|
||||||
|
@ -64,8 +64,10 @@ class ProfileHandler:
|
||||||
self.user_directory_handler = hs.get_user_directory_handler()
|
self.user_directory_handler = hs.get_user_directory_handler()
|
||||||
self.request_ratelimiter = hs.get_request_ratelimiter()
|
self.request_ratelimiter = hs.get_request_ratelimiter()
|
||||||
|
|
||||||
self.max_avatar_size = hs.config.server.max_avatar_size
|
self.max_avatar_size: Optional[int] = hs.config.server.max_avatar_size
|
||||||
self.allowed_avatar_mimetypes = hs.config.server.allowed_avatar_mimetypes
|
self.allowed_avatar_mimetypes: Optional[List[str]] = (
|
||||||
|
hs.config.server.allowed_avatar_mimetypes
|
||||||
|
)
|
||||||
|
|
||||||
self._is_mine_server_name = hs.is_mine_server_name
|
self._is_mine_server_name = hs.is_mine_server_name
|
||||||
|
|
||||||
|
@ -337,6 +339,12 @@ class ProfileHandler:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.max_avatar_size:
|
if self.max_avatar_size:
|
||||||
|
if media_info.media_length is None:
|
||||||
|
logger.warning(
|
||||||
|
"Forbidding avatar change to %s: unknown media size",
|
||||||
|
mxc,
|
||||||
|
)
|
||||||
|
return False
|
||||||
# Ensure avatar does not exceed max allowed avatar size
|
# Ensure avatar does not exceed max allowed avatar size
|
||||||
if media_info.media_length > self.max_avatar_size:
|
if media_info.media_length > self.max_avatar_size:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|
|
@ -20,11 +20,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from synapse.api.constants import ReceiptTypes
|
from synapse.api.constants import ReceiptTypes
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.util.async_helpers import Linearizer
|
from synapse.util.async_helpers import Linearizer
|
||||||
|
from synapse.types import JsonDict
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
@ -39,7 +40,11 @@ class ReadMarkerHandler:
|
||||||
self.read_marker_linearizer = Linearizer(name="read_marker")
|
self.read_marker_linearizer = Linearizer(name="read_marker")
|
||||||
|
|
||||||
async def received_client_read_marker(
|
async def received_client_read_marker(
|
||||||
self, room_id: str, user_id: str, event_id: str
|
self,
|
||||||
|
room_id: str,
|
||||||
|
user_id: str,
|
||||||
|
event_id: str,
|
||||||
|
extra_content: Optional[JsonDict] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Updates the read marker for a given user in a given room if the event ID given
|
"""Updates the read marker for a given user in a given room if the event ID given
|
||||||
is ahead in the stream relative to the current read marker.
|
is ahead in the stream relative to the current read marker.
|
||||||
|
@ -71,7 +76,7 @@ class ReadMarkerHandler:
|
||||||
should_update = event_ordering > old_event_ordering
|
should_update = event_ordering > old_event_ordering
|
||||||
|
|
||||||
if should_update:
|
if should_update:
|
||||||
content = {"event_id": event_id}
|
content = {"event_id": event_id, **(extra_content or {})}
|
||||||
await self.account_data_handler.add_account_data_to_room(
|
await self.account_data_handler.add_account_data_to_room(
|
||||||
user_id, room_id, ReceiptTypes.FULLY_READ, content
|
user_id, room_id, ReceiptTypes.FULLY_READ, content
|
||||||
)
|
)
|
||||||
|
|
|
@ -181,6 +181,7 @@ class ReceiptsHandler:
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
thread_id: Optional[str],
|
thread_id: Optional[str],
|
||||||
|
extra_content: Optional[JsonDict] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called when a client tells us a local user has read up to the given
|
"""Called when a client tells us a local user has read up to the given
|
||||||
event_id in the room.
|
event_id in the room.
|
||||||
|
@ -197,7 +198,7 @@ class ReceiptsHandler:
|
||||||
user_id=user_id.to_string(),
|
user_id=user_id.to_string(),
|
||||||
event_ids=[event_id],
|
event_ids=[event_id],
|
||||||
thread_id=thread_id,
|
thread_id=thread_id,
|
||||||
data={"ts": int(self.clock.time_msec())},
|
data={"ts": int(self.clock.time_msec()), **(extra_content or {})},
|
||||||
)
|
)
|
||||||
|
|
||||||
is_new = await self._handle_new_receipts([receipt])
|
is_new = await self._handle_new_receipts([receipt])
|
||||||
|
|
|
@ -148,22 +148,25 @@ class RegistrationHandler:
|
||||||
localpart: str,
|
localpart: str,
|
||||||
guest_access_token: Optional[str] = None,
|
guest_access_token: Optional[str] = None,
|
||||||
assigned_user_id: Optional[str] = None,
|
assigned_user_id: Optional[str] = None,
|
||||||
|
allow_invalid: bool = False,
|
||||||
inhibit_user_in_use_error: bool = False,
|
inhibit_user_in_use_error: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if types.contains_invalid_mxid_characters(localpart):
|
# meow: allow admins to register invalid user ids
|
||||||
raise SynapseError(
|
if not allow_invalid:
|
||||||
400,
|
if types.contains_invalid_mxid_characters(localpart):
|
||||||
"User ID can only contain characters a-z, 0-9, or '=_-./+'",
|
raise SynapseError(
|
||||||
Codes.INVALID_USERNAME,
|
400,
|
||||||
)
|
"User ID can only contain characters a-z, 0-9, or '=_-./+'",
|
||||||
|
Codes.INVALID_USERNAME,
|
||||||
|
)
|
||||||
|
|
||||||
if not localpart:
|
if not localpart:
|
||||||
raise SynapseError(400, "User ID cannot be empty", Codes.INVALID_USERNAME)
|
raise SynapseError(400, "User ID cannot be empty", Codes.INVALID_USERNAME)
|
||||||
|
|
||||||
if localpart[0] == "_":
|
if localpart[0] == "_":
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400, "User ID may not begin with _", Codes.INVALID_USERNAME
|
400, "User ID may not begin with _", Codes.INVALID_USERNAME
|
||||||
)
|
)
|
||||||
|
|
||||||
user = UserID(localpart, self.hs.hostname)
|
user = UserID(localpart, self.hs.hostname)
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
|
@ -177,14 +180,16 @@ class RegistrationHandler:
|
||||||
"A different user ID has already been registered for this session",
|
"A different user ID has already been registered for this session",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.check_user_id_not_appservice_exclusive(user_id)
|
# meow: allow admins to register reserved user ids and long user ids
|
||||||
|
if not allow_invalid:
|
||||||
|
self.check_user_id_not_appservice_exclusive(user_id)
|
||||||
|
|
||||||
if len(user_id) > MAX_USERID_LENGTH:
|
if len(user_id) > MAX_USERID_LENGTH:
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400,
|
400,
|
||||||
"User ID may not be longer than %s characters" % (MAX_USERID_LENGTH,),
|
"User ID may not be longer than %s characters" % (MAX_USERID_LENGTH,),
|
||||||
Codes.INVALID_USERNAME,
|
Codes.INVALID_USERNAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
users = await self.store.get_users_by_id_case_insensitive(user_id)
|
users = await self.store.get_users_by_id_case_insensitive(user_id)
|
||||||
if users:
|
if users:
|
||||||
|
@ -290,7 +295,12 @@ class RegistrationHandler:
|
||||||
await self.auth_blocking.check_auth_blocking(threepid=threepid)
|
await self.auth_blocking.check_auth_blocking(threepid=threepid)
|
||||||
|
|
||||||
if localpart is not None:
|
if localpart is not None:
|
||||||
await self.check_username(localpart, guest_access_token=guest_access_token)
|
allow_invalid = by_admin and self.hs.config.meow.admin_api_register_invalid
|
||||||
|
await self.check_username(
|
||||||
|
localpart,
|
||||||
|
guest_access_token=guest_access_token,
|
||||||
|
allow_invalid=allow_invalid,
|
||||||
|
)
|
||||||
|
|
||||||
was_guest = guest_access_token is not None
|
was_guest = guest_access_token is not None
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ class RelationsHandler:
|
||||||
self._event_handler = hs.get_event_handler()
|
self._event_handler = hs.get_event_handler()
|
||||||
self._event_serializer = hs.get_event_client_serializer()
|
self._event_serializer = hs.get_event_client_serializer()
|
||||||
self._event_creation_handler = hs.get_event_creation_handler()
|
self._event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
self._config = hs.config
|
||||||
|
|
||||||
async def get_relations(
|
async def get_relations(
|
||||||
self,
|
self,
|
||||||
|
@ -163,6 +164,7 @@ class RelationsHandler:
|
||||||
user_id,
|
user_id,
|
||||||
events,
|
events,
|
||||||
is_peeking=(member_event_id is None),
|
is_peeking=(member_event_id is None),
|
||||||
|
msc4115_membership_on_events=self._config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
# The relations returned for the requested event do include their
|
# The relations returned for the requested event do include their
|
||||||
|
@ -608,6 +610,7 @@ class RelationsHandler:
|
||||||
user_id,
|
user_id,
|
||||||
events,
|
events,
|
||||||
is_peeking=(member_event_id is None),
|
is_peeking=(member_event_id is None),
|
||||||
|
msc4115_membership_on_events=self._config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
aggregations = await self.get_bundled_aggregations(
|
aggregations = await self.get_bundled_aggregations(
|
||||||
|
|
|
@ -893,11 +893,23 @@ class RoomCreationHandler:
|
||||||
|
|
||||||
self._validate_room_config(config, visibility)
|
self._validate_room_config(config, visibility)
|
||||||
|
|
||||||
room_id = await self._generate_and_create_room_id(
|
if "room_id" in config:
|
||||||
creator_id=user_id,
|
room_id = config["room_id"]
|
||||||
is_public=is_public,
|
try:
|
||||||
room_version=room_version,
|
await self.store.store_room(
|
||||||
)
|
room_id=room_id,
|
||||||
|
room_creator_user_id=user_id,
|
||||||
|
is_public=is_public,
|
||||||
|
room_version=room_version,
|
||||||
|
)
|
||||||
|
except StoreError:
|
||||||
|
raise SynapseError(409, "Room ID already in use", errcode="M_CONFLICT")
|
||||||
|
else:
|
||||||
|
room_id = await self._generate_and_create_room_id(
|
||||||
|
creator_id=user_id,
|
||||||
|
is_public=is_public,
|
||||||
|
room_version=room_version,
|
||||||
|
)
|
||||||
|
|
||||||
# Check whether this visibility value is blocked by a third party module
|
# Check whether this visibility value is blocked by a third party module
|
||||||
allowed_by_third_party_rules = (
|
allowed_by_third_party_rules = (
|
||||||
|
@ -916,7 +928,7 @@ class RoomCreationHandler:
|
||||||
room_aliases.append(room_alias.to_string())
|
room_aliases.append(room_alias.to_string())
|
||||||
if not self.config.roomdirectory.is_publishing_room_allowed(
|
if not self.config.roomdirectory.is_publishing_room_allowed(
|
||||||
user_id, room_id, room_aliases
|
user_id, room_id, room_aliases
|
||||||
):
|
) and not is_requester_admin:
|
||||||
# allow room creation to continue but do not publish room
|
# allow room creation to continue but do not publish room
|
||||||
await self.store.set_room_is_public(room_id, False)
|
await self.store.set_room_is_public(room_id, False)
|
||||||
|
|
||||||
|
@ -1189,7 +1201,7 @@ class RoomCreationHandler:
|
||||||
events_to_send.append((power_event, power_context))
|
events_to_send.append((power_event, power_context))
|
||||||
else:
|
else:
|
||||||
power_level_content: JsonDict = {
|
power_level_content: JsonDict = {
|
||||||
"users": {creator_id: 100},
|
"users": {creator_id: 9001},
|
||||||
"users_default": 0,
|
"users_default": 0,
|
||||||
"events": {
|
"events": {
|
||||||
EventTypes.Name: 50,
|
EventTypes.Name: 50,
|
||||||
|
@ -1476,6 +1488,7 @@ class RoomContextHandler:
|
||||||
user.to_string(),
|
user.to_string(),
|
||||||
events,
|
events,
|
||||||
is_peeking=is_peeking,
|
is_peeking=is_peeking,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
event = await self.store.get_event(
|
event = await self.store.get_event(
|
||||||
|
|
|
@ -752,6 +752,36 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
and requester.user.to_string() == self._server_notices_mxid
|
and requester.user.to_string() == self._server_notices_mxid
|
||||||
)
|
)
|
||||||
|
|
||||||
|
requester_suspended = await self.store.get_user_suspended_status(
|
||||||
|
requester.user.to_string()
|
||||||
|
)
|
||||||
|
if action == Membership.INVITE and requester_suspended:
|
||||||
|
raise SynapseError(
|
||||||
|
403,
|
||||||
|
"Sending invites while account is suspended is not allowed.",
|
||||||
|
Codes.USER_ACCOUNT_SUSPENDED,
|
||||||
|
)
|
||||||
|
|
||||||
|
if target.to_string() != requester.user.to_string():
|
||||||
|
target_suspended = await self.store.get_user_suspended_status(
|
||||||
|
target.to_string()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
target_suspended = requester_suspended
|
||||||
|
|
||||||
|
if action == Membership.JOIN and target_suspended:
|
||||||
|
raise SynapseError(
|
||||||
|
403,
|
||||||
|
"Joining rooms while account is suspended is not allowed.",
|
||||||
|
Codes.USER_ACCOUNT_SUSPENDED,
|
||||||
|
)
|
||||||
|
if action == Membership.KNOCK and target_suspended:
|
||||||
|
raise SynapseError(
|
||||||
|
403,
|
||||||
|
"Knocking on rooms while account is suspended is not allowed.",
|
||||||
|
Codes.USER_ACCOUNT_SUSPENDED,
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not self.allow_per_room_profiles and not is_requester_server_notices_user
|
not self.allow_per_room_profiles and not is_requester_server_notices_user
|
||||||
) or requester.shadow_banned:
|
) or requester.shadow_banned:
|
||||||
|
@ -761,26 +791,6 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
content.pop("displayname", None)
|
content.pop("displayname", None)
|
||||||
content.pop("avatar_url", None)
|
content.pop("avatar_url", None)
|
||||||
|
|
||||||
if len(content.get("displayname") or "") > MAX_DISPLAYNAME_LEN:
|
|
||||||
raise SynapseError(
|
|
||||||
400,
|
|
||||||
f"Displayname is too long (max {MAX_DISPLAYNAME_LEN})",
|
|
||||||
errcode=Codes.BAD_JSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(content.get("avatar_url") or "") > MAX_AVATAR_URL_LEN:
|
|
||||||
raise SynapseError(
|
|
||||||
400,
|
|
||||||
f"Avatar URL is too long (max {MAX_AVATAR_URL_LEN})",
|
|
||||||
errcode=Codes.BAD_JSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
if "avatar_url" in content and content.get("avatar_url") is not None:
|
|
||||||
if not await self.profile_handler.check_avatar_size_and_mime_type(
|
|
||||||
content["avatar_url"],
|
|
||||||
):
|
|
||||||
raise SynapseError(403, "This avatar is not allowed", Codes.FORBIDDEN)
|
|
||||||
|
|
||||||
# The event content should *not* include the authorising user as
|
# The event content should *not* include the authorising user as
|
||||||
# it won't be properly signed. Strip it out since it might come
|
# it won't be properly signed. Strip it out since it might come
|
||||||
# back from a client updating a display name / avatar.
|
# back from a client updating a display name / avatar.
|
||||||
|
|
|
@ -480,7 +480,10 @@ class SearchHandler:
|
||||||
filtered_events = await search_filter.filter([r["event"] for r in results])
|
filtered_events = await search_filter.filter([r["event"] for r in results])
|
||||||
|
|
||||||
events = await filter_events_for_client(
|
events = await filter_events_for_client(
|
||||||
self._storage_controllers, user.to_string(), filtered_events
|
self._storage_controllers,
|
||||||
|
user.to_string(),
|
||||||
|
filtered_events,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
events.sort(key=lambda e: -rank_map[e.event_id])
|
events.sort(key=lambda e: -rank_map[e.event_id])
|
||||||
|
@ -579,7 +582,10 @@ class SearchHandler:
|
||||||
filtered_events = await search_filter.filter([r["event"] for r in results])
|
filtered_events = await search_filter.filter([r["event"] for r in results])
|
||||||
|
|
||||||
events = await filter_events_for_client(
|
events = await filter_events_for_client(
|
||||||
self._storage_controllers, user.to_string(), filtered_events
|
self._storage_controllers,
|
||||||
|
user.to_string(),
|
||||||
|
filtered_events,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
room_events.extend(events)
|
room_events.extend(events)
|
||||||
|
@ -664,11 +670,17 @@ class SearchHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
events_before = await filter_events_for_client(
|
events_before = await filter_events_for_client(
|
||||||
self._storage_controllers, user.to_string(), res.events_before
|
self._storage_controllers,
|
||||||
|
user.to_string(),
|
||||||
|
res.events_before,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
events_after = await filter_events_for_client(
|
events_after = await filter_events_for_client(
|
||||||
self._storage_controllers, user.to_string(), res.events_after
|
self._storage_controllers,
|
||||||
|
user.to_string(),
|
||||||
|
res.events_after,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
context: JsonDict = {
|
context: JsonDict = {
|
||||||
|
|
|
@ -169,6 +169,7 @@ class UsernameMappingSession:
|
||||||
# attributes returned by the ID mapper
|
# attributes returned by the ID mapper
|
||||||
display_name: Optional[str]
|
display_name: Optional[str]
|
||||||
emails: StrCollection
|
emails: StrCollection
|
||||||
|
avatar_url: Optional[str]
|
||||||
|
|
||||||
# An optional dictionary of extra attributes to be provided to the client in the
|
# An optional dictionary of extra attributes to be provided to the client in the
|
||||||
# login response.
|
# login response.
|
||||||
|
@ -183,6 +184,7 @@ class UsernameMappingSession:
|
||||||
# choices made by the user
|
# choices made by the user
|
||||||
chosen_localpart: Optional[str] = None
|
chosen_localpart: Optional[str] = None
|
||||||
use_display_name: bool = True
|
use_display_name: bool = True
|
||||||
|
use_avatar: bool = True
|
||||||
emails_to_use: StrCollection = ()
|
emails_to_use: StrCollection = ()
|
||||||
terms_accepted_version: Optional[str] = None
|
terms_accepted_version: Optional[str] = None
|
||||||
|
|
||||||
|
@ -660,6 +662,9 @@ class SsoHandler:
|
||||||
remote_user_id=remote_user_id,
|
remote_user_id=remote_user_id,
|
||||||
display_name=attributes.display_name,
|
display_name=attributes.display_name,
|
||||||
emails=attributes.emails,
|
emails=attributes.emails,
|
||||||
|
avatar_url=attributes.picture,
|
||||||
|
# Default to using all mapped emails. Will be overwritten in handle_submit_username_request.
|
||||||
|
emails_to_use=attributes.emails,
|
||||||
client_redirect_url=client_redirect_url,
|
client_redirect_url=client_redirect_url,
|
||||||
expiry_time_ms=now + self._MAPPING_SESSION_VALIDITY_PERIOD_MS,
|
expiry_time_ms=now + self._MAPPING_SESSION_VALIDITY_PERIOD_MS,
|
||||||
extra_login_attributes=extra_login_attributes,
|
extra_login_attributes=extra_login_attributes,
|
||||||
|
@ -966,6 +971,7 @@ class SsoHandler:
|
||||||
session_id: str,
|
session_id: str,
|
||||||
localpart: str,
|
localpart: str,
|
||||||
use_display_name: bool,
|
use_display_name: bool,
|
||||||
|
use_avatar: bool,
|
||||||
emails_to_use: Iterable[str],
|
emails_to_use: Iterable[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle a request to the username-picker 'submit' endpoint
|
"""Handle a request to the username-picker 'submit' endpoint
|
||||||
|
@ -988,6 +994,7 @@ class SsoHandler:
|
||||||
# update the session with the user's choices
|
# update the session with the user's choices
|
||||||
session.chosen_localpart = localpart
|
session.chosen_localpart = localpart
|
||||||
session.use_display_name = use_display_name
|
session.use_display_name = use_display_name
|
||||||
|
session.use_avatar = use_avatar
|
||||||
|
|
||||||
emails_from_idp = set(session.emails)
|
emails_from_idp = set(session.emails)
|
||||||
filtered_emails: Set[str] = set()
|
filtered_emails: Set[str] = set()
|
||||||
|
@ -1068,6 +1075,9 @@ class SsoHandler:
|
||||||
if session.use_display_name:
|
if session.use_display_name:
|
||||||
attributes.display_name = session.display_name
|
attributes.display_name = session.display_name
|
||||||
|
|
||||||
|
if session.use_avatar:
|
||||||
|
attributes.picture = session.avatar_url
|
||||||
|
|
||||||
# the following will raise a 400 error if the username has been taken in the
|
# the following will raise a 400 error if the username has been taken in the
|
||||||
# meantime.
|
# meantime.
|
||||||
user_id = await self._register_mapped_user(
|
user_id = await self._register_mapped_user(
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#
|
#
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
from enum import Enum
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
AbstractSet,
|
AbstractSet,
|
||||||
|
@ -112,12 +113,28 @@ LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE = 100
|
||||||
SyncRequestKey = Tuple[Any, ...]
|
SyncRequestKey = Tuple[Any, ...]
|
||||||
|
|
||||||
|
|
||||||
|
class SyncVersion(Enum):
|
||||||
|
"""
|
||||||
|
Enum for specifying the version of sync request. This is used to key which type of
|
||||||
|
sync response that we are generating.
|
||||||
|
|
||||||
|
This is different than the `sync_type` you might see used in other code below; which
|
||||||
|
specifies the sub-type sync request (e.g. initial_sync, full_state_sync,
|
||||||
|
incremental_sync) and is really only relevant for the `/sync` v2 endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# These string values are semantically significant because they are used in the the
|
||||||
|
# metrics
|
||||||
|
|
||||||
|
# Traditional `/sync` endpoint
|
||||||
|
SYNC_V2 = "sync_v2"
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||||
class SyncConfig:
|
class SyncConfig:
|
||||||
user: UserID
|
user: UserID
|
||||||
filter_collection: FilterCollection
|
filter_collection: FilterCollection
|
||||||
is_guest: bool
|
is_guest: bool
|
||||||
request_key: SyncRequestKey
|
|
||||||
device_id: Optional[str]
|
device_id: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
@ -309,6 +326,8 @@ class SyncHandler:
|
||||||
self,
|
self,
|
||||||
requester: Requester,
|
requester: Requester,
|
||||||
sync_config: SyncConfig,
|
sync_config: SyncConfig,
|
||||||
|
sync_version: SyncVersion,
|
||||||
|
request_key: SyncRequestKey,
|
||||||
since_token: Optional[StreamToken] = None,
|
since_token: Optional[StreamToken] = None,
|
||||||
timeout: int = 0,
|
timeout: int = 0,
|
||||||
full_state: bool = False,
|
full_state: bool = False,
|
||||||
|
@ -316,6 +335,17 @@ class SyncHandler:
|
||||||
"""Get the sync for a client if we have new data for it now. Otherwise
|
"""Get the sync for a client if we have new data for it now. Otherwise
|
||||||
wait for new data to arrive on the server. If the timeout expires, then
|
wait for new data to arrive on the server. If the timeout expires, then
|
||||||
return an empty sync result.
|
return an empty sync result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: The user requesting the sync response.
|
||||||
|
sync_config: Config/info necessary to process the sync request.
|
||||||
|
sync_version: Determines what kind of sync response to generate.
|
||||||
|
request_key: The key to use for caching the response.
|
||||||
|
since_token: The point in the stream to sync from.
|
||||||
|
timeout: How long to wait for new data to arrive before giving up.
|
||||||
|
full_state: Whether to return the full state for each room.
|
||||||
|
Returns:
|
||||||
|
When `SyncVersion.SYNC_V2`, returns a full `SyncResult`.
|
||||||
"""
|
"""
|
||||||
# If the user is not part of the mau group, then check that limits have
|
# If the user is not part of the mau group, then check that limits have
|
||||||
# not been exceeded (if not part of the group by this point, almost certain
|
# not been exceeded (if not part of the group by this point, almost certain
|
||||||
|
@ -324,9 +354,10 @@ class SyncHandler:
|
||||||
await self.auth_blocking.check_auth_blocking(requester=requester)
|
await self.auth_blocking.check_auth_blocking(requester=requester)
|
||||||
|
|
||||||
res = await self.response_cache.wrap(
|
res = await self.response_cache.wrap(
|
||||||
sync_config.request_key,
|
request_key,
|
||||||
self._wait_for_sync_for_user,
|
self._wait_for_sync_for_user,
|
||||||
sync_config,
|
sync_config,
|
||||||
|
sync_version,
|
||||||
since_token,
|
since_token,
|
||||||
timeout,
|
timeout,
|
||||||
full_state,
|
full_state,
|
||||||
|
@ -338,6 +369,7 @@ class SyncHandler:
|
||||||
async def _wait_for_sync_for_user(
|
async def _wait_for_sync_for_user(
|
||||||
self,
|
self,
|
||||||
sync_config: SyncConfig,
|
sync_config: SyncConfig,
|
||||||
|
sync_version: SyncVersion,
|
||||||
since_token: Optional[StreamToken],
|
since_token: Optional[StreamToken],
|
||||||
timeout: int,
|
timeout: int,
|
||||||
full_state: bool,
|
full_state: bool,
|
||||||
|
@ -363,9 +395,11 @@ class SyncHandler:
|
||||||
else:
|
else:
|
||||||
sync_type = "incremental_sync"
|
sync_type = "incremental_sync"
|
||||||
|
|
||||||
|
sync_label = f"{sync_version}:{sync_type}"
|
||||||
|
|
||||||
context = current_context()
|
context = current_context()
|
||||||
if context:
|
if context:
|
||||||
context.tag = sync_type
|
context.tag = sync_label
|
||||||
|
|
||||||
# if we have a since token, delete any to-device messages before that token
|
# if we have a since token, delete any to-device messages before that token
|
||||||
# (since we now know that the device has received them)
|
# (since we now know that the device has received them)
|
||||||
|
@ -384,14 +418,16 @@ class SyncHandler:
|
||||||
# we are going to return immediately, so don't bother calling
|
# we are going to return immediately, so don't bother calling
|
||||||
# notifier.wait_for_events.
|
# notifier.wait_for_events.
|
||||||
result: SyncResult = await self.current_sync_for_user(
|
result: SyncResult = await self.current_sync_for_user(
|
||||||
sync_config, since_token, full_state=full_state
|
sync_config, sync_version, since_token, full_state=full_state
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Otherwise, we wait for something to happen and report it to the user.
|
# Otherwise, we wait for something to happen and report it to the user.
|
||||||
async def current_sync_callback(
|
async def current_sync_callback(
|
||||||
before_token: StreamToken, after_token: StreamToken
|
before_token: StreamToken, after_token: StreamToken
|
||||||
) -> SyncResult:
|
) -> SyncResult:
|
||||||
return await self.current_sync_for_user(sync_config, since_token)
|
return await self.current_sync_for_user(
|
||||||
|
sync_config, sync_version, since_token
|
||||||
|
)
|
||||||
|
|
||||||
result = await self.notifier.wait_for_events(
|
result = await self.notifier.wait_for_events(
|
||||||
sync_config.user.to_string(),
|
sync_config.user.to_string(),
|
||||||
|
@ -416,13 +452,14 @@ class SyncHandler:
|
||||||
lazy_loaded = "true"
|
lazy_loaded = "true"
|
||||||
else:
|
else:
|
||||||
lazy_loaded = "false"
|
lazy_loaded = "false"
|
||||||
non_empty_sync_counter.labels(sync_type, lazy_loaded).inc()
|
non_empty_sync_counter.labels(sync_label, lazy_loaded).inc()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def current_sync_for_user(
|
async def current_sync_for_user(
|
||||||
self,
|
self,
|
||||||
sync_config: SyncConfig,
|
sync_config: SyncConfig,
|
||||||
|
sync_version: SyncVersion,
|
||||||
since_token: Optional[StreamToken] = None,
|
since_token: Optional[StreamToken] = None,
|
||||||
full_state: bool = False,
|
full_state: bool = False,
|
||||||
) -> SyncResult:
|
) -> SyncResult:
|
||||||
|
@ -431,12 +468,26 @@ class SyncHandler:
|
||||||
This is a wrapper around `generate_sync_result` which starts an open tracing
|
This is a wrapper around `generate_sync_result` which starts an open tracing
|
||||||
span to track the sync. See `generate_sync_result` for the next part of your
|
span to track the sync. See `generate_sync_result` for the next part of your
|
||||||
indoctrination.
|
indoctrination.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sync_config: Config/info necessary to process the sync request.
|
||||||
|
sync_version: Determines what kind of sync response to generate.
|
||||||
|
since_token: The point in the stream to sync from.p.
|
||||||
|
full_state: Whether to return the full state for each room.
|
||||||
|
Returns:
|
||||||
|
When `SyncVersion.SYNC_V2`, returns a full `SyncResult`.
|
||||||
"""
|
"""
|
||||||
with start_active_span("sync.current_sync_for_user"):
|
with start_active_span("sync.current_sync_for_user"):
|
||||||
log_kv({"since_token": since_token})
|
log_kv({"since_token": since_token})
|
||||||
sync_result = await self.generate_sync_result(
|
# Go through the `/sync` v2 path
|
||||||
sync_config, since_token, full_state
|
if sync_version == SyncVersion.SYNC_V2:
|
||||||
)
|
sync_result: SyncResult = await self.generate_sync_result(
|
||||||
|
sync_config, since_token, full_state
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"Unknown sync_version (this is a Synapse problem): {sync_version}"
|
||||||
|
)
|
||||||
|
|
||||||
set_tag(SynapseTags.SYNC_RESULT, bool(sync_result))
|
set_tag(SynapseTags.SYNC_RESULT, bool(sync_result))
|
||||||
return sync_result
|
return sync_result
|
||||||
|
@ -596,6 +647,7 @@ class SyncHandler:
|
||||||
sync_config.user.to_string(),
|
sync_config.user.to_string(),
|
||||||
recents,
|
recents,
|
||||||
always_include_ids=current_state_ids,
|
always_include_ids=current_state_ids,
|
||||||
|
msc4115_membership_on_events=self.hs_config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
log_kv({"recents_after_visibility_filtering": len(recents)})
|
log_kv({"recents_after_visibility_filtering": len(recents)})
|
||||||
else:
|
else:
|
||||||
|
@ -681,6 +733,7 @@ class SyncHandler:
|
||||||
sync_config.user.to_string(),
|
sync_config.user.to_string(),
|
||||||
loaded_recents,
|
loaded_recents,
|
||||||
always_include_ids=current_state_ids,
|
always_include_ids=current_state_ids,
|
||||||
|
msc4115_membership_on_events=self.hs_config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
loaded_recents = []
|
loaded_recents = []
|
||||||
|
@ -1124,7 +1177,6 @@ class SyncHandler:
|
||||||
for e in await sync_config.filter_collection.filter_room_state(
|
for e in await sync_config.filter_collection.filter_room_state(
|
||||||
list(state.values())
|
list(state.values())
|
||||||
)
|
)
|
||||||
if e.type != EventTypes.Aliases # until MSC2261 or alternative solution
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _compute_state_delta_for_full_sync(
|
async def _compute_state_delta_for_full_sync(
|
||||||
|
@ -1516,128 +1568,17 @@ class SyncHandler:
|
||||||
# See https://github.com/matrix-org/matrix-doc/issues/1144
|
# See https://github.com/matrix-org/matrix-doc/issues/1144
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# Note: we get the users room list *before* we get the current token, this
|
sync_result_builder = await self.get_sync_result_builder(
|
||||||
# avoids checking back in history if rooms are joined after the token is fetched.
|
sync_config,
|
||||||
token_before_rooms = self.event_sources.get_current_token()
|
since_token,
|
||||||
mutable_joined_room_ids = set(await self.store.get_rooms_for_user(user_id))
|
full_state,
|
||||||
|
|
||||||
# NB: The now_token gets changed by some of the generate_sync_* methods,
|
|
||||||
# this is due to some of the underlying streams not supporting the ability
|
|
||||||
# to query up to a given point.
|
|
||||||
# Always use the `now_token` in `SyncResultBuilder`
|
|
||||||
now_token = self.event_sources.get_current_token()
|
|
||||||
log_kv({"now_token": now_token})
|
|
||||||
|
|
||||||
# Since we fetched the users room list before the token, there's a small window
|
|
||||||
# during which membership events may have been persisted, so we fetch these now
|
|
||||||
# and modify the joined room list for any changes between the get_rooms_for_user
|
|
||||||
# call and the get_current_token call.
|
|
||||||
membership_change_events = []
|
|
||||||
if since_token:
|
|
||||||
membership_change_events = await self.store.get_membership_changes_for_user(
|
|
||||||
user_id,
|
|
||||||
since_token.room_key,
|
|
||||||
now_token.room_key,
|
|
||||||
self.rooms_to_exclude_globally,
|
|
||||||
)
|
|
||||||
|
|
||||||
mem_last_change_by_room_id: Dict[str, EventBase] = {}
|
|
||||||
for event in membership_change_events:
|
|
||||||
mem_last_change_by_room_id[event.room_id] = event
|
|
||||||
|
|
||||||
# For the latest membership event in each room found, add/remove the room ID
|
|
||||||
# from the joined room list accordingly. In this case we only care if the
|
|
||||||
# latest change is JOIN.
|
|
||||||
|
|
||||||
for room_id, event in mem_last_change_by_room_id.items():
|
|
||||||
assert event.internal_metadata.stream_ordering
|
|
||||||
if (
|
|
||||||
event.internal_metadata.stream_ordering
|
|
||||||
< token_before_rooms.room_key.stream
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"User membership change between getting rooms and current token: %s %s %s",
|
|
||||||
user_id,
|
|
||||||
event.membership,
|
|
||||||
room_id,
|
|
||||||
)
|
|
||||||
# User joined a room - we have to then check the room state to ensure we
|
|
||||||
# respect any bans if there's a race between the join and ban events.
|
|
||||||
if event.membership == Membership.JOIN:
|
|
||||||
user_ids_in_room = await self.store.get_users_in_room(room_id)
|
|
||||||
if user_id in user_ids_in_room:
|
|
||||||
mutable_joined_room_ids.add(room_id)
|
|
||||||
# The user left the room, or left and was re-invited but not joined yet
|
|
||||||
else:
|
|
||||||
mutable_joined_room_ids.discard(room_id)
|
|
||||||
|
|
||||||
# Tweak the set of rooms to return to the client for eager (non-lazy) syncs.
|
|
||||||
mutable_rooms_to_exclude = set(self.rooms_to_exclude_globally)
|
|
||||||
if not sync_config.filter_collection.lazy_load_members():
|
|
||||||
# Non-lazy syncs should never include partially stated rooms.
|
|
||||||
# Exclude all partially stated rooms from this sync.
|
|
||||||
results = await self.store.is_partial_state_room_batched(
|
|
||||||
mutable_joined_room_ids
|
|
||||||
)
|
|
||||||
mutable_rooms_to_exclude.update(
|
|
||||||
room_id
|
|
||||||
for room_id, is_partial_state in results.items()
|
|
||||||
if is_partial_state
|
|
||||||
)
|
|
||||||
membership_change_events = [
|
|
||||||
event
|
|
||||||
for event in membership_change_events
|
|
||||||
if not results.get(event.room_id, False)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Incremental eager syncs should additionally include rooms that
|
|
||||||
# - we are joined to
|
|
||||||
# - are full-stated
|
|
||||||
# - became fully-stated at some point during the sync period
|
|
||||||
# (These rooms will have been omitted during a previous eager sync.)
|
|
||||||
forced_newly_joined_room_ids: Set[str] = set()
|
|
||||||
if since_token and not sync_config.filter_collection.lazy_load_members():
|
|
||||||
un_partial_stated_rooms = (
|
|
||||||
await self.store.get_un_partial_stated_rooms_between(
|
|
||||||
since_token.un_partial_stated_rooms_key,
|
|
||||||
now_token.un_partial_stated_rooms_key,
|
|
||||||
mutable_joined_room_ids,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
results = await self.store.is_partial_state_room_batched(
|
|
||||||
un_partial_stated_rooms
|
|
||||||
)
|
|
||||||
forced_newly_joined_room_ids.update(
|
|
||||||
room_id
|
|
||||||
for room_id, is_partial_state in results.items()
|
|
||||||
if not is_partial_state
|
|
||||||
)
|
|
||||||
|
|
||||||
# Now we have our list of joined room IDs, exclude as configured and freeze
|
|
||||||
joined_room_ids = frozenset(
|
|
||||||
room_id
|
|
||||||
for room_id in mutable_joined_room_ids
|
|
||||||
if room_id not in mutable_rooms_to_exclude
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Calculating sync response for %r between %s and %s",
|
"Calculating sync response for %r between %s and %s",
|
||||||
sync_config.user,
|
sync_config.user,
|
||||||
since_token,
|
sync_result_builder.since_token,
|
||||||
now_token,
|
sync_result_builder.now_token,
|
||||||
)
|
|
||||||
|
|
||||||
sync_result_builder = SyncResultBuilder(
|
|
||||||
sync_config,
|
|
||||||
full_state,
|
|
||||||
since_token=since_token,
|
|
||||||
now_token=now_token,
|
|
||||||
joined_room_ids=joined_room_ids,
|
|
||||||
excluded_room_ids=frozenset(mutable_rooms_to_exclude),
|
|
||||||
forced_newly_joined_room_ids=frozenset(forced_newly_joined_room_ids),
|
|
||||||
membership_change_events=membership_change_events,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Fetching account data")
|
logger.debug("Fetching account data")
|
||||||
|
@ -1749,6 +1690,149 @@ class SyncHandler:
|
||||||
next_batch=sync_result_builder.now_token,
|
next_batch=sync_result_builder.now_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_sync_result_builder(
|
||||||
|
self,
|
||||||
|
sync_config: SyncConfig,
|
||||||
|
since_token: Optional[StreamToken] = None,
|
||||||
|
full_state: bool = False,
|
||||||
|
) -> "SyncResultBuilder":
|
||||||
|
"""
|
||||||
|
Assemble a `SyncResultBuilder` with all of the initial context to
|
||||||
|
start building up the sync response:
|
||||||
|
|
||||||
|
- Membership changes between the last sync and the current sync.
|
||||||
|
- Joined room IDs (minus any rooms to exclude).
|
||||||
|
- Rooms that became fully-stated/un-partial stated since the last sync.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sync_config: Config/info necessary to process the sync request.
|
||||||
|
since_token: The point in the stream to sync from.
|
||||||
|
full_state: Whether to return the full state for each room.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`SyncResultBuilder` ready to start generating parts of the sync response.
|
||||||
|
"""
|
||||||
|
user_id = sync_config.user.to_string()
|
||||||
|
|
||||||
|
# Note: we get the users room list *before* we get the current token, this
|
||||||
|
# avoids checking back in history if rooms are joined after the token is fetched.
|
||||||
|
token_before_rooms = self.event_sources.get_current_token()
|
||||||
|
mutable_joined_room_ids = set(await self.store.get_rooms_for_user(user_id))
|
||||||
|
|
||||||
|
# NB: The `now_token` gets changed by some of the `generate_sync_*` methods,
|
||||||
|
# this is due to some of the underlying streams not supporting the ability
|
||||||
|
# to query up to a given point.
|
||||||
|
# Always use the `now_token` in `SyncResultBuilder`
|
||||||
|
now_token = self.event_sources.get_current_token()
|
||||||
|
log_kv({"now_token": now_token})
|
||||||
|
|
||||||
|
# Since we fetched the users room list before the token, there's a small window
|
||||||
|
# during which membership events may have been persisted, so we fetch these now
|
||||||
|
# and modify the joined room list for any changes between the get_rooms_for_user
|
||||||
|
# call and the get_current_token call.
|
||||||
|
membership_change_events = []
|
||||||
|
if since_token:
|
||||||
|
membership_change_events = await self.store.get_membership_changes_for_user(
|
||||||
|
user_id,
|
||||||
|
since_token.room_key,
|
||||||
|
now_token.room_key,
|
||||||
|
self.rooms_to_exclude_globally,
|
||||||
|
)
|
||||||
|
|
||||||
|
mem_last_change_by_room_id: Dict[str, EventBase] = {}
|
||||||
|
for event in membership_change_events:
|
||||||
|
mem_last_change_by_room_id[event.room_id] = event
|
||||||
|
|
||||||
|
# For the latest membership event in each room found, add/remove the room ID
|
||||||
|
# from the joined room list accordingly. In this case we only care if the
|
||||||
|
# latest change is JOIN.
|
||||||
|
|
||||||
|
for room_id, event in mem_last_change_by_room_id.items():
|
||||||
|
assert event.internal_metadata.stream_ordering
|
||||||
|
if (
|
||||||
|
event.internal_metadata.stream_ordering
|
||||||
|
< token_before_rooms.room_key.stream
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"User membership change between getting rooms and current token: %s %s %s",
|
||||||
|
user_id,
|
||||||
|
event.membership,
|
||||||
|
room_id,
|
||||||
|
)
|
||||||
|
# User joined a room - we have to then check the room state to ensure we
|
||||||
|
# respect any bans if there's a race between the join and ban events.
|
||||||
|
if event.membership == Membership.JOIN:
|
||||||
|
user_ids_in_room = await self.store.get_users_in_room(room_id)
|
||||||
|
if user_id in user_ids_in_room:
|
||||||
|
mutable_joined_room_ids.add(room_id)
|
||||||
|
# The user left the room, or left and was re-invited but not joined yet
|
||||||
|
else:
|
||||||
|
mutable_joined_room_ids.discard(room_id)
|
||||||
|
|
||||||
|
# Tweak the set of rooms to return to the client for eager (non-lazy) syncs.
|
||||||
|
mutable_rooms_to_exclude = set(self.rooms_to_exclude_globally)
|
||||||
|
if not sync_config.filter_collection.lazy_load_members():
|
||||||
|
# Non-lazy syncs should never include partially stated rooms.
|
||||||
|
# Exclude all partially stated rooms from this sync.
|
||||||
|
results = await self.store.is_partial_state_room_batched(
|
||||||
|
mutable_joined_room_ids
|
||||||
|
)
|
||||||
|
mutable_rooms_to_exclude.update(
|
||||||
|
room_id
|
||||||
|
for room_id, is_partial_state in results.items()
|
||||||
|
if is_partial_state
|
||||||
|
)
|
||||||
|
membership_change_events = [
|
||||||
|
event
|
||||||
|
for event in membership_change_events
|
||||||
|
if not results.get(event.room_id, False)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Incremental eager syncs should additionally include rooms that
|
||||||
|
# - we are joined to
|
||||||
|
# - are full-stated
|
||||||
|
# - became fully-stated at some point during the sync period
|
||||||
|
# (These rooms will have been omitted during a previous eager sync.)
|
||||||
|
forced_newly_joined_room_ids: Set[str] = set()
|
||||||
|
if since_token and not sync_config.filter_collection.lazy_load_members():
|
||||||
|
un_partial_stated_rooms = (
|
||||||
|
await self.store.get_un_partial_stated_rooms_between(
|
||||||
|
since_token.un_partial_stated_rooms_key,
|
||||||
|
now_token.un_partial_stated_rooms_key,
|
||||||
|
mutable_joined_room_ids,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
results = await self.store.is_partial_state_room_batched(
|
||||||
|
un_partial_stated_rooms
|
||||||
|
)
|
||||||
|
forced_newly_joined_room_ids.update(
|
||||||
|
room_id
|
||||||
|
for room_id, is_partial_state in results.items()
|
||||||
|
if not is_partial_state
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now we have our list of joined room IDs, exclude as configured and freeze
|
||||||
|
joined_room_ids = frozenset(
|
||||||
|
room_id
|
||||||
|
for room_id in mutable_joined_room_ids
|
||||||
|
if room_id not in mutable_rooms_to_exclude
|
||||||
|
)
|
||||||
|
|
||||||
|
sync_result_builder = SyncResultBuilder(
|
||||||
|
sync_config,
|
||||||
|
full_state,
|
||||||
|
since_token=since_token,
|
||||||
|
now_token=now_token,
|
||||||
|
joined_room_ids=joined_room_ids,
|
||||||
|
excluded_room_ids=frozenset(mutable_rooms_to_exclude),
|
||||||
|
forced_newly_joined_room_ids=frozenset(forced_newly_joined_room_ids),
|
||||||
|
membership_change_events=membership_change_events,
|
||||||
|
)
|
||||||
|
|
||||||
|
return sync_result_builder
|
||||||
|
|
||||||
@measure_func("_generate_sync_entry_for_device_list")
|
@measure_func("_generate_sync_entry_for_device_list")
|
||||||
async def _generate_sync_entry_for_device_list(
|
async def _generate_sync_entry_for_device_list(
|
||||||
self,
|
self,
|
||||||
|
@ -1797,7 +1881,7 @@ class SyncHandler:
|
||||||
|
|
||||||
users_that_have_changed = set()
|
users_that_have_changed = set()
|
||||||
|
|
||||||
joined_rooms = sync_result_builder.joined_room_ids
|
joined_room_ids = sync_result_builder.joined_room_ids
|
||||||
|
|
||||||
# Step 1a, check for changes in devices of users we share a room
|
# Step 1a, check for changes in devices of users we share a room
|
||||||
# with
|
# with
|
||||||
|
@ -1822,7 +1906,7 @@ class SyncHandler:
|
||||||
# or if the changed user is the syncing user (as we always
|
# or if the changed user is the syncing user (as we always
|
||||||
# want to include device list updates of their own devices).
|
# want to include device list updates of their own devices).
|
||||||
if user_id == changed_user_id or any(
|
if user_id == changed_user_id or any(
|
||||||
rid in joined_rooms for rid in entries
|
rid in joined_room_ids for rid in entries
|
||||||
):
|
):
|
||||||
users_that_have_changed.add(changed_user_id)
|
users_that_have_changed.add(changed_user_id)
|
||||||
else:
|
else:
|
||||||
|
@ -1856,7 +1940,7 @@ class SyncHandler:
|
||||||
# Remove any users that we still share a room with.
|
# Remove any users that we still share a room with.
|
||||||
left_users_rooms = await self.store.get_rooms_for_users(newly_left_users)
|
left_users_rooms = await self.store.get_rooms_for_users(newly_left_users)
|
||||||
for user_id, entries in left_users_rooms.items():
|
for user_id, entries in left_users_rooms.items():
|
||||||
if any(rid in joined_rooms for rid in entries):
|
if any(rid in joined_room_ids for rid in entries):
|
||||||
newly_left_users.discard(user_id)
|
newly_left_users.discard(user_id)
|
||||||
|
|
||||||
return DeviceListUpdates(changed=users_that_have_changed, left=newly_left_users)
|
return DeviceListUpdates(changed=users_that_have_changed, left=newly_left_users)
|
||||||
|
@ -1943,23 +2027,19 @@ class SyncHandler:
|
||||||
)
|
)
|
||||||
|
|
||||||
if push_rules_changed:
|
if push_rules_changed:
|
||||||
global_account_data = {
|
global_account_data = dict(global_account_data)
|
||||||
AccountDataTypes.PUSH_RULES: await self._push_rules_handler.push_rules_for_user(
|
global_account_data[AccountDataTypes.PUSH_RULES] = (
|
||||||
sync_config.user
|
await self._push_rules_handler.push_rules_for_user(sync_config.user)
|
||||||
),
|
)
|
||||||
**global_account_data,
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
all_global_account_data = await self.store.get_global_account_data_for_user(
|
all_global_account_data = await self.store.get_global_account_data_for_user(
|
||||||
user_id
|
user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
global_account_data = {
|
global_account_data = dict(all_global_account_data)
|
||||||
AccountDataTypes.PUSH_RULES: await self._push_rules_handler.push_rules_for_user(
|
global_account_data[AccountDataTypes.PUSH_RULES] = (
|
||||||
sync_config.user
|
await self._push_rules_handler.push_rules_for_user(sync_config.user)
|
||||||
),
|
)
|
||||||
**all_global_account_data,
|
|
||||||
}
|
|
||||||
|
|
||||||
account_data_for_user = (
|
account_data_for_user = (
|
||||||
await sync_config.filter_collection.filter_global_account_data(
|
await sync_config.filter_collection.filter_global_account_data(
|
||||||
|
|
|
@ -262,7 +262,8 @@ class _ProxyResponseBody(protocol.Protocol):
|
||||||
self._request.finish()
|
self._request.finish()
|
||||||
else:
|
else:
|
||||||
# Abort the underlying request since our remote request also failed.
|
# Abort the underlying request since our remote request also failed.
|
||||||
self._request.transport.abortConnection()
|
if self._request.channel:
|
||||||
|
self._request.channel.forceAbortClient()
|
||||||
|
|
||||||
|
|
||||||
class ProxySite(Site):
|
class ProxySite(Site):
|
||||||
|
|
|
@ -153,9 +153,9 @@ def return_json_error(
|
||||||
# Only respond with an error response if we haven't already started writing,
|
# Only respond with an error response if we haven't already started writing,
|
||||||
# otherwise lets just kill the connection
|
# otherwise lets just kill the connection
|
||||||
if request.startedWriting:
|
if request.startedWriting:
|
||||||
if request.transport:
|
if request.channel:
|
||||||
try:
|
try:
|
||||||
request.transport.abortConnection()
|
request.channel.forceAbortClient()
|
||||||
except Exception:
|
except Exception:
|
||||||
# abortConnection throws if the connection is already closed
|
# abortConnection throws if the connection is already closed
|
||||||
pass
|
pass
|
||||||
|
@ -909,7 +909,19 @@ def set_cors_headers(request: "SynapseRequest") -> None:
|
||||||
request.setHeader(
|
request.setHeader(
|
||||||
b"Access-Control-Allow-Methods", b"GET, HEAD, POST, PUT, DELETE, OPTIONS"
|
b"Access-Control-Allow-Methods", b"GET, HEAD, POST, PUT, DELETE, OPTIONS"
|
||||||
)
|
)
|
||||||
if request.experimental_cors_msc3886:
|
if request.path is not None and (
|
||||||
|
request.path == b"/_matrix/client/unstable/org.matrix.msc4108/rendezvous"
|
||||||
|
or request.path.startswith(b"/_synapse/client/rendezvous")
|
||||||
|
):
|
||||||
|
request.setHeader(
|
||||||
|
b"Access-Control-Allow-Headers",
|
||||||
|
b"Content-Type, If-Match, If-None-Match",
|
||||||
|
)
|
||||||
|
request.setHeader(
|
||||||
|
b"Access-Control-Expose-Headers",
|
||||||
|
b"Synapse-Trace-Id, Server, ETag",
|
||||||
|
)
|
||||||
|
elif request.experimental_cors_msc3886:
|
||||||
request.setHeader(
|
request.setHeader(
|
||||||
b"Access-Control-Allow-Headers",
|
b"Access-Control-Allow-Headers",
|
||||||
b"X-Requested-With, Content-Type, Authorization, Date, If-Match, If-None-Match",
|
b"X-Requested-With, Content-Type, Authorization, Date, If-Match, If-None-Match",
|
||||||
|
|
|
@ -19,9 +19,11 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
""" This module contains base REST classes for constructing REST servlets. """
|
"""This module contains base REST classes for constructing REST servlets."""
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
|
import urllib.parse as urlparse
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
|
@ -65,17 +67,49 @@ def parse_integer(request: Request, name: str, default: int) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_integer(request: Request, name: str, *, required: Literal[True]) -> int: ...
|
def parse_integer(
|
||||||
|
request: Request, name: str, *, default: int, negative: bool
|
||||||
|
) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_integer(
|
def parse_integer(
|
||||||
request: Request, name: str, default: Optional[int] = None, required: bool = False
|
request: Request, name: str, *, default: int, negative: bool = False
|
||||||
|
) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_integer(
|
||||||
|
request: Request, name: str, *, required: Literal[True], negative: bool = False
|
||||||
|
) -> int: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_integer(
|
||||||
|
request: Request, name: str, *, default: Literal[None], negative: bool = False
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_integer(request: Request, name: str, *, negative: bool) -> Optional[int]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def parse_integer(
|
||||||
|
request: Request,
|
||||||
|
name: str,
|
||||||
|
default: Optional[int] = None,
|
||||||
|
required: bool = False,
|
||||||
|
negative: bool = False,
|
||||||
) -> Optional[int]: ...
|
) -> Optional[int]: ...
|
||||||
|
|
||||||
|
|
||||||
def parse_integer(
|
def parse_integer(
|
||||||
request: Request, name: str, default: Optional[int] = None, required: bool = False
|
request: Request,
|
||||||
|
name: str,
|
||||||
|
default: Optional[int] = None,
|
||||||
|
required: bool = False,
|
||||||
|
negative: bool = False,
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
"""Parse an integer parameter from the request string
|
"""Parse an integer parameter from the request string
|
||||||
|
|
||||||
|
@ -85,16 +119,17 @@ def parse_integer(
|
||||||
default: value to use if the parameter is absent, defaults to None.
|
default: value to use if the parameter is absent, defaults to None.
|
||||||
required: whether to raise a 400 SynapseError if the parameter is absent,
|
required: whether to raise a 400 SynapseError if the parameter is absent,
|
||||||
defaults to False.
|
defaults to False.
|
||||||
|
negative: whether to allow negative integers, defaults to True.
|
||||||
Returns:
|
Returns:
|
||||||
An int value or the default.
|
An int value or the default.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError: if the parameter is absent and required, or if the
|
SynapseError: if the parameter is absent and required, if the
|
||||||
parameter is present and not an integer.
|
parameter is present and not an integer, or if the
|
||||||
|
parameter is illegitimate negative.
|
||||||
"""
|
"""
|
||||||
args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore
|
args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore
|
||||||
return parse_integer_from_args(args, name, default, required)
|
return parse_integer_from_args(args, name, default, required, negative)
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@ -120,6 +155,7 @@ def parse_integer_from_args(
|
||||||
name: str,
|
name: str,
|
||||||
default: Optional[int] = None,
|
default: Optional[int] = None,
|
||||||
required: bool = False,
|
required: bool = False,
|
||||||
|
negative: bool = False,
|
||||||
) -> Optional[int]: ...
|
) -> Optional[int]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,6 +164,7 @@ def parse_integer_from_args(
|
||||||
name: str,
|
name: str,
|
||||||
default: Optional[int] = None,
|
default: Optional[int] = None,
|
||||||
required: bool = False,
|
required: bool = False,
|
||||||
|
negative: bool = True,
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
"""Parse an integer parameter from the request string
|
"""Parse an integer parameter from the request string
|
||||||
|
|
||||||
|
@ -137,33 +174,37 @@ def parse_integer_from_args(
|
||||||
default: value to use if the parameter is absent, defaults to None.
|
default: value to use if the parameter is absent, defaults to None.
|
||||||
required: whether to raise a 400 SynapseError if the parameter is absent,
|
required: whether to raise a 400 SynapseError if the parameter is absent,
|
||||||
defaults to False.
|
defaults to False.
|
||||||
|
negative: whether to allow negative integers, defaults to True.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An int value or the default.
|
An int value or the default.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError: if the parameter is absent and required, or if the
|
SynapseError: if the parameter is absent and required, if the
|
||||||
parameter is present and not an integer.
|
parameter is present and not an integer, or if the
|
||||||
|
parameter is illegitimate negative.
|
||||||
"""
|
"""
|
||||||
name_bytes = name.encode("ascii")
|
name_bytes = name.encode("ascii")
|
||||||
|
|
||||||
if name_bytes in args:
|
if name_bytes not in args:
|
||||||
try:
|
if not required:
|
||||||
return int(args[name_bytes][0])
|
|
||||||
except Exception:
|
|
||||||
message = "Query parameter %r must be an integer" % (name,)
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST, message, errcode=Codes.INVALID_PARAM
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if required:
|
|
||||||
message = "Missing integer query parameter %r" % (name,)
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST, message, errcode=Codes.MISSING_PARAM
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
message = f"Missing required integer query parameter {name}"
|
||||||
|
raise SynapseError(HTTPStatus.BAD_REQUEST, message, errcode=Codes.MISSING_PARAM)
|
||||||
|
|
||||||
|
try:
|
||||||
|
integer = int(args[name_bytes][0])
|
||||||
|
except Exception:
|
||||||
|
message = f"Query parameter {name} must be an integer"
|
||||||
|
raise SynapseError(HTTPStatus.BAD_REQUEST, message, errcode=Codes.INVALID_PARAM)
|
||||||
|
|
||||||
|
if not negative and integer < 0:
|
||||||
|
message = f"Query parameter {name} must be a positive integer."
|
||||||
|
raise SynapseError(HTTPStatus.BAD_REQUEST, message, errcode=Codes.INVALID_PARAM)
|
||||||
|
|
||||||
|
return integer
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def parse_boolean(request: Request, name: str, default: bool) -> bool: ...
|
def parse_boolean(request: Request, name: str, default: bool) -> bool: ...
|
||||||
|
@ -410,6 +451,87 @@ def parse_string(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json(
|
||||||
|
request: Request,
|
||||||
|
name: str,
|
||||||
|
default: Optional[dict] = None,
|
||||||
|
required: bool = False,
|
||||||
|
encoding: str = "ascii",
|
||||||
|
) -> Optional[JsonDict]:
|
||||||
|
"""
|
||||||
|
Parse a JSON parameter from the request query string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: the twisted HTTP request.
|
||||||
|
name: the name of the query parameter.
|
||||||
|
default: value to use if the parameter is absent,
|
||||||
|
defaults to None.
|
||||||
|
required: whether to raise a 400 SynapseError if the
|
||||||
|
parameter is absent, defaults to False.
|
||||||
|
encoding: The encoding to decode the string content with.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A JSON value, or `default` if the named query parameter was not found
|
||||||
|
and `required` was False.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
SynapseError if the parameter is absent and required, or if the
|
||||||
|
parameter is present and not a JSON object.
|
||||||
|
"""
|
||||||
|
args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore
|
||||||
|
return parse_json_from_args(
|
||||||
|
args,
|
||||||
|
name,
|
||||||
|
default,
|
||||||
|
required=required,
|
||||||
|
encoding=encoding,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_from_args(
|
||||||
|
args: Mapping[bytes, Sequence[bytes]],
|
||||||
|
name: str,
|
||||||
|
default: Optional[dict] = None,
|
||||||
|
required: bool = False,
|
||||||
|
encoding: str = "ascii",
|
||||||
|
) -> Optional[JsonDict]:
|
||||||
|
"""
|
||||||
|
Parse a JSON parameter from the request query string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: a mapping of request args as bytes to a list of bytes (e.g. request.args).
|
||||||
|
name: the name of the query parameter.
|
||||||
|
default: value to use if the parameter is absent,
|
||||||
|
defaults to None.
|
||||||
|
required: whether to raise a 400 SynapseError if the
|
||||||
|
parameter is absent, defaults to False.
|
||||||
|
encoding: the encoding to decode the string content with.
|
||||||
|
|
||||||
|
A JSON value, or `default` if the named query parameter was not found
|
||||||
|
and `required` was False.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
SynapseError if the parameter is absent and required, or if the
|
||||||
|
parameter is present and not a JSON object.
|
||||||
|
"""
|
||||||
|
name_bytes = name.encode("ascii")
|
||||||
|
|
||||||
|
if name_bytes not in args:
|
||||||
|
if not required:
|
||||||
|
return default
|
||||||
|
|
||||||
|
message = f"Missing required integer query parameter {name}"
|
||||||
|
raise SynapseError(HTTPStatus.BAD_REQUEST, message, errcode=Codes.MISSING_PARAM)
|
||||||
|
|
||||||
|
json_str = parse_string_from_args(args, name, required=True, encoding=encoding)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json_decoder.decode(urlparse.unquote(json_str))
|
||||||
|
except Exception:
|
||||||
|
message = f"Query parameter {name} must be a valid JSON object"
|
||||||
|
raise SynapseError(HTTPStatus.BAD_REQUEST, message, errcode=Codes.NOT_JSON)
|
||||||
|
|
||||||
|
|
||||||
EnumT = TypeVar("EnumT", bound=enum.Enum)
|
EnumT = TypeVar("EnumT", bound=enum.Enum)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,8 @@ class SynapseRequest(Request):
|
||||||
self.get_method(),
|
self.get_method(),
|
||||||
self.get_redacted_uri(),
|
self.get_redacted_uri(),
|
||||||
)
|
)
|
||||||
self.transport.abortConnection()
|
if self.channel:
|
||||||
|
self.channel.forceAbortClient()
|
||||||
return
|
return
|
||||||
super().handleContentChunk(data)
|
super().handleContentChunk(data)
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ INLINE_CONTENT_TYPES = [
|
||||||
"text/csv",
|
"text/csv",
|
||||||
"application/json",
|
"application/json",
|
||||||
"application/ld+json",
|
"application/ld+json",
|
||||||
|
"application/pdf",
|
||||||
# We allow some media files deemed as safe, which comes from the matrix-react-sdk.
|
# We allow some media files deemed as safe, which comes from the matrix-react-sdk.
|
||||||
# https://github.com/matrix-org/matrix-react-sdk/blob/a70fcfd0bcf7f8c85986da18001ea11597989a7c/src/utils/blobs.ts#L51
|
# https://github.com/matrix-org/matrix-react-sdk/blob/a70fcfd0bcf7f8c85986da18001ea11597989a7c/src/utils/blobs.ts#L51
|
||||||
# SVGs are *intentionally* omitted.
|
# SVGs are *intentionally* omitted.
|
||||||
|
@ -206,7 +207,9 @@ def add_file_headers(
|
||||||
# recommend caching as it's sensitive or private - or at least
|
# recommend caching as it's sensitive or private - or at least
|
||||||
# select private. don't bother setting Expires as all our
|
# select private. don't bother setting Expires as all our
|
||||||
# clients are smart enough to be happy with Cache-Control
|
# clients are smart enough to be happy with Cache-Control
|
||||||
request.setHeader(b"Cache-Control", b"public,max-age=86400,s-maxage=86400")
|
request.setHeader(
|
||||||
|
b"Cache-Control", b"public,immutable,max-age=86400,s-maxage=86400"
|
||||||
|
)
|
||||||
if file_size is not None:
|
if file_size is not None:
|
||||||
request.setHeader(b"Content-Length", b"%d" % (file_size,))
|
request.setHeader(b"Content-Length", b"%d" % (file_size,))
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ class ThumbnailError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class Thumbnailer:
|
class Thumbnailer:
|
||||||
FORMATS = {"image/jpeg": "JPEG", "image/png": "PNG"}
|
FORMATS = {"image/jpeg": "JPEG", "image/png": "PNG", "image/webp": "WEBP"}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_limits(max_image_pixels: int) -> None:
|
def set_limits(max_image_pixels: int) -> None:
|
||||||
|
|
|
@ -721,6 +721,7 @@ class Notifier:
|
||||||
user.to_string(),
|
user.to_string(),
|
||||||
new_events,
|
new_events,
|
||||||
is_peeking=is_peeking,
|
is_peeking=is_peeking,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
elif keyname == StreamKeyType.PRESENCE:
|
elif keyname == StreamKeyType.PRESENCE:
|
||||||
now = self.clock.time_msec()
|
now = self.clock.time_msec()
|
||||||
|
|
|
@ -140,13 +140,6 @@ class HttpPusher(Pusher):
|
||||||
url = self.data["url"]
|
url = self.data["url"]
|
||||||
if not isinstance(url, str):
|
if not isinstance(url, str):
|
||||||
raise PusherConfigException("'url' must be a string")
|
raise PusherConfigException("'url' must be a string")
|
||||||
url_parts = urllib.parse.urlparse(url)
|
|
||||||
# Note that the specification also says the scheme must be HTTPS, but
|
|
||||||
# it isn't up to the homeserver to verify that.
|
|
||||||
if url_parts.path != "/_matrix/push/v1/notify":
|
|
||||||
raise PusherConfigException(
|
|
||||||
"'url' must have a path of '/_matrix/push/v1/notify'"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.http_client = hs.get_proxied_blocklisted_http_client()
|
self.http_client = hs.get_proxied_blocklisted_http_client()
|
||||||
|
|
|
@ -205,6 +205,22 @@ class Mailer:
|
||||||
template_vars,
|
template_vars,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
emails_sent_counter.labels("already_in_use")
|
||||||
|
|
||||||
|
async def send_already_in_use_mail(self, email_address: str) -> None:
|
||||||
|
"""Send an email if the address is already bound to an user account
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email_address: Email address we're sending to the "already in use" mail
|
||||||
|
"""
|
||||||
|
|
||||||
|
await self.send_email(
|
||||||
|
email_address,
|
||||||
|
self.email_subjects.email_already_in_use
|
||||||
|
% {"server_name": self.hs.config.server.server_name, "app": self.app_name},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
emails_sent_counter.labels("add_threepid")
|
emails_sent_counter.labels("add_threepid")
|
||||||
|
|
||||||
async def send_add_threepid_mail(
|
async def send_add_threepid_mail(
|
||||||
|
@ -513,7 +529,10 @@ class Mailer:
|
||||||
}
|
}
|
||||||
|
|
||||||
the_events = await filter_events_for_client(
|
the_events = await filter_events_for_client(
|
||||||
self._storage_controllers, user_id, results.events_before
|
self._storage_controllers,
|
||||||
|
user_id,
|
||||||
|
results.events_before,
|
||||||
|
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
|
||||||
)
|
)
|
||||||
the_events.append(notif_event)
|
the_events.append(notif_event)
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ from synapse.replication.tcp.streams.partial_state import (
|
||||||
)
|
)
|
||||||
from synapse.types import PersistedEventPosition, ReadReceipt, StreamKeyType, UserID
|
from synapse.types import PersistedEventPosition, ReadReceipt, StreamKeyType, UserID
|
||||||
from synapse.util.async_helpers import Linearizer, timeout_deferred
|
from synapse.util.async_helpers import Linearizer, timeout_deferred
|
||||||
|
from synapse.util.iterutils import batch_iter
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -150,9 +151,15 @@ class ReplicationDataHandler:
|
||||||
if row.entity.startswith("@") and not row.is_signature:
|
if row.entity.startswith("@") and not row.is_signature:
|
||||||
room_ids = await self.store.get_rooms_for_user(row.entity)
|
room_ids = await self.store.get_rooms_for_user(row.entity)
|
||||||
all_room_ids.update(room_ids)
|
all_room_ids.update(room_ids)
|
||||||
self.notifier.on_new_event(
|
|
||||||
StreamKeyType.DEVICE_LIST, token, rooms=all_room_ids
|
# `all_room_ids` can be large, so let's wake up those streams in batches
|
||||||
)
|
for batched_room_ids in batch_iter(all_room_ids, 100):
|
||||||
|
self.notifier.on_new_event(
|
||||||
|
StreamKeyType.DEVICE_LIST, token, rooms=batched_room_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
# Yield to reactor so that we don't block.
|
||||||
|
await self._clock.sleep(0)
|
||||||
elif stream_name == PushersStream.NAME:
|
elif stream_name == PushersStream.NAME:
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row.deleted:
|
if row.deleted:
|
||||||
|
|
12
synapse/res/templates/already_in_use.html
Normal file
12
synapse/res/templates/already_in_use.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "_base.html" %}
|
||||||
|
{% block title %}Email already in use{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<p>You have asked us to register this email with a new Matrix account, but this email is already registered with an existing account.</p>
|
||||||
|
|
||||||
|
<p>Please reset your password if needed.</p>
|
||||||
|
|
||||||
|
<p>If this was not you, you can safely disregard this email.</p>
|
||||||
|
|
||||||
|
<p>Thank you.</p>
|
||||||
|
{% endblock %}
|
10
synapse/res/templates/already_in_use.txt
Normal file
10
synapse/res/templates/already_in_use.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Hello there,
|
||||||
|
|
||||||
|
You have asked us to register this email with a new Matrix account,
|
||||||
|
but this email is already registered with an existing account.
|
||||||
|
|
||||||
|
Please reset your password if needed.
|
||||||
|
|
||||||
|
If this was not you, you can safely disregard this email.
|
||||||
|
|
||||||
|
Thank you.
|
|
@ -23,7 +23,7 @@ from http import HTTPStatus
|
||||||
from typing import TYPE_CHECKING, Tuple
|
from typing import TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
from synapse.api.constants import Direction
|
from synapse.api.constants import Direction
|
||||||
from synapse.api.errors import Codes, NotFoundError, SynapseError
|
from synapse.api.errors import NotFoundError, SynapseError
|
||||||
from synapse.federation.transport.server import Authenticator
|
from synapse.federation.transport.server import Authenticator
|
||||||
from synapse.http.servlet import RestServlet, parse_enum, parse_integer, parse_string
|
from synapse.http.servlet import RestServlet, parse_enum, parse_integer, parse_string
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
|
@ -61,22 +61,8 @@ class ListDestinationsRestServlet(RestServlet):
|
||||||
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
||||||
await assert_requester_is_admin(self._auth, request)
|
await assert_requester_is_admin(self._auth, request)
|
||||||
|
|
||||||
start = parse_integer(request, "from", default=0)
|
start = parse_integer(request, "from", default=0, negative=False)
|
||||||
limit = parse_integer(request, "limit", default=100)
|
limit = parse_integer(request, "limit", default=100, negative=False)
|
||||||
|
|
||||||
if start < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter from must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
if limit < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter limit must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
destination = parse_string(request, "destination")
|
destination = parse_string(request, "destination")
|
||||||
|
|
||||||
|
@ -195,22 +181,8 @@ class DestinationMembershipRestServlet(RestServlet):
|
||||||
if not await self._store.is_destination_known(destination):
|
if not await self._store.is_destination_known(destination):
|
||||||
raise NotFoundError("Unknown destination")
|
raise NotFoundError("Unknown destination")
|
||||||
|
|
||||||
start = parse_integer(request, "from", default=0)
|
start = parse_integer(request, "from", default=0, negative=False)
|
||||||
limit = parse_integer(request, "limit", default=100)
|
limit = parse_integer(request, "limit", default=100, negative=False)
|
||||||
|
|
||||||
if start < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter from must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
if limit < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter limit must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS)
|
direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS)
|
||||||
|
|
||||||
|
|
|
@ -311,29 +311,17 @@ class DeleteMediaByDateSize(RestServlet):
|
||||||
) -> Tuple[int, JsonDict]:
|
) -> Tuple[int, JsonDict]:
|
||||||
await assert_requester_is_admin(self.auth, request)
|
await assert_requester_is_admin(self.auth, request)
|
||||||
|
|
||||||
before_ts = parse_integer(request, "before_ts", required=True)
|
before_ts = parse_integer(request, "before_ts", required=True, negative=False)
|
||||||
size_gt = parse_integer(request, "size_gt", default=0)
|
size_gt = parse_integer(request, "size_gt", default=0, negative=False)
|
||||||
keep_profiles = parse_boolean(request, "keep_profiles", default=True)
|
keep_profiles = parse_boolean(request, "keep_profiles", default=True)
|
||||||
|
|
||||||
if before_ts < 0:
|
if before_ts < 30000000000: # Dec 1970 in milliseconds, Aug 2920 in seconds
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter before_ts must be a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
elif before_ts < 30000000000: # Dec 1970 in milliseconds, Aug 2920 in seconds
|
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
"Query parameter before_ts you provided is from the year 1970. "
|
"Query parameter before_ts you provided is from the year 1970. "
|
||||||
+ "Double check that you are providing a timestamp in milliseconds.",
|
+ "Double check that you are providing a timestamp in milliseconds.",
|
||||||
errcode=Codes.INVALID_PARAM,
|
errcode=Codes.INVALID_PARAM,
|
||||||
)
|
)
|
||||||
if size_gt < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter size_gt must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
# This check is useless, we keep it for the legacy endpoint only.
|
# This check is useless, we keep it for the legacy endpoint only.
|
||||||
if server_name is not None and self.server_name != server_name:
|
if server_name is not None and self.server_name != server_name:
|
||||||
|
@ -389,22 +377,8 @@ class UserMediaRestServlet(RestServlet):
|
||||||
if user is None:
|
if user is None:
|
||||||
raise NotFoundError("Unknown user")
|
raise NotFoundError("Unknown user")
|
||||||
|
|
||||||
start = parse_integer(request, "from", default=0)
|
start = parse_integer(request, "from", default=0, negative=False)
|
||||||
limit = parse_integer(request, "limit", default=100)
|
limit = parse_integer(request, "limit", default=100, negative=False)
|
||||||
|
|
||||||
if start < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter from must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
if limit < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter limit must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
# If neither `order_by` nor `dir` is set, set the default order
|
# If neither `order_by` nor `dir` is set, set the default order
|
||||||
# to newest media is on top for backward compatibility.
|
# to newest media is on top for backward compatibility.
|
||||||
|
@ -447,22 +421,8 @@ class UserMediaRestServlet(RestServlet):
|
||||||
if user is None:
|
if user is None:
|
||||||
raise NotFoundError("Unknown user")
|
raise NotFoundError("Unknown user")
|
||||||
|
|
||||||
start = parse_integer(request, "from", default=0)
|
start = parse_integer(request, "from", default=0, negative=False)
|
||||||
limit = parse_integer(request, "limit", default=100)
|
limit = parse_integer(request, "limit", default=100, negative=False)
|
||||||
|
|
||||||
if start < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter from must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
if limit < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter limit must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
# If neither `order_by` nor `dir` is set, set the default order
|
# If neither `order_by` nor `dir` is set, set the default order
|
||||||
# to newest media is on top for backward compatibility.
|
# to newest media is on top for backward compatibility.
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
import logging
|
import logging
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import TYPE_CHECKING, List, Optional, Tuple, cast
|
from typing import TYPE_CHECKING, List, Optional, Tuple, cast
|
||||||
from urllib import parse as urlparse
|
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
@ -38,6 +37,7 @@ from synapse.http.servlet import (
|
||||||
assert_params_in_dict,
|
assert_params_in_dict,
|
||||||
parse_enum,
|
parse_enum,
|
||||||
parse_integer,
|
parse_integer,
|
||||||
|
parse_json,
|
||||||
parse_json_object_from_request,
|
parse_json_object_from_request,
|
||||||
parse_string,
|
parse_string,
|
||||||
)
|
)
|
||||||
|
@ -51,7 +51,6 @@ from synapse.storage.databases.main.room import RoomSortOrder
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
from synapse.types import JsonDict, RoomID, ScheduledTask, UserID, create_requester
|
from synapse.types import JsonDict, RoomID, ScheduledTask, UserID, create_requester
|
||||||
from synapse.types.state import StateFilter
|
from synapse.types.state import StateFilter
|
||||||
from synapse.util import json_decoder
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.api.auth import Auth
|
from synapse.api.auth import Auth
|
||||||
|
@ -776,14 +775,8 @@ class RoomEventContextServlet(RestServlet):
|
||||||
limit = parse_integer(request, "limit", default=10)
|
limit = parse_integer(request, "limit", default=10)
|
||||||
|
|
||||||
# picking the API shape for symmetry with /messages
|
# picking the API shape for symmetry with /messages
|
||||||
filter_str = parse_string(request, "filter", encoding="utf-8")
|
filter_json = parse_json(request, "filter", encoding="utf-8")
|
||||||
if filter_str:
|
event_filter = Filter(self._hs, filter_json) if filter_json else None
|
||||||
filter_json = urlparse.unquote(filter_str)
|
|
||||||
event_filter: Optional[Filter] = Filter(
|
|
||||||
self._hs, json_decoder.decode(filter_json)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
event_filter = None
|
|
||||||
|
|
||||||
event_context = await self.room_context_handler.get_event_context(
|
event_context = await self.room_context_handler.get_event_context(
|
||||||
requester,
|
requester,
|
||||||
|
@ -914,21 +907,16 @@ class RoomMessagesRestServlet(RestServlet):
|
||||||
)
|
)
|
||||||
# Twisted will have processed the args by now.
|
# Twisted will have processed the args by now.
|
||||||
assert request.args is not None
|
assert request.args is not None
|
||||||
|
|
||||||
|
filter_json = parse_json(request, "filter", encoding="utf-8")
|
||||||
|
event_filter = Filter(self._hs, filter_json) if filter_json else None
|
||||||
|
|
||||||
as_client_event = b"raw" not in request.args
|
as_client_event = b"raw" not in request.args
|
||||||
filter_str = parse_string(request, "filter", encoding="utf-8")
|
if (
|
||||||
if filter_str:
|
event_filter
|
||||||
filter_json = urlparse.unquote(filter_str)
|
and event_filter.filter_json.get("event_format", "client") == "federation"
|
||||||
event_filter: Optional[Filter] = Filter(
|
):
|
||||||
self._hs, json_decoder.decode(filter_json)
|
as_client_event = False
|
||||||
)
|
|
||||||
if (
|
|
||||||
event_filter
|
|
||||||
and event_filter.filter_json.get("event_format", "client")
|
|
||||||
== "federation"
|
|
||||||
):
|
|
||||||
as_client_event = False
|
|
||||||
else:
|
|
||||||
event_filter = None
|
|
||||||
|
|
||||||
msgs = await self._pagination_handler.get_messages(
|
msgs = await self._pagination_handler.get_messages(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
|
|
|
@ -63,38 +63,12 @@ class UserMediaStatisticsRestServlet(RestServlet):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
start = parse_integer(request, "from", default=0)
|
start = parse_integer(request, "from", default=0, negative=False)
|
||||||
if start < 0:
|
limit = parse_integer(request, "limit", default=100, negative=False)
|
||||||
raise SynapseError(
|
from_ts = parse_integer(request, "from_ts", default=0, negative=False)
|
||||||
HTTPStatus.BAD_REQUEST,
|
until_ts = parse_integer(request, "until_ts", negative=False)
|
||||||
"Query parameter from must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
limit = parse_integer(request, "limit", default=100)
|
|
||||||
if limit < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter limit must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
from_ts = parse_integer(request, "from_ts", default=0)
|
|
||||||
if from_ts < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter from_ts must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
|
|
||||||
until_ts = parse_integer(request, "until_ts")
|
|
||||||
if until_ts is not None:
|
if until_ts is not None:
|
||||||
if until_ts < 0:
|
|
||||||
raise SynapseError(
|
|
||||||
HTTPStatus.BAD_REQUEST,
|
|
||||||
"Query parameter until_ts must be a string representing a positive integer.",
|
|
||||||
errcode=Codes.INVALID_PARAM,
|
|
||||||
)
|
|
||||||
if until_ts <= from_ts:
|
if until_ts <= from_ts:
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue