mirror of
https://git.anonymousland.org/anonymousland/synapse-product.git
synced 2024-10-01 12:25:44 +00:00
Merge remote-tracking branch 'origin/develop' into rav/saml2_client
This commit is contained in:
commit
69a43d9974
@ -36,8 +36,6 @@ steps:
|
|||||||
image: "python:3.6"
|
image: "python:3.6"
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
|
|
||||||
- wait
|
|
||||||
|
|
||||||
- command:
|
- command:
|
||||||
- "python -m pip install tox"
|
- "python -m pip install tox"
|
||||||
- "tox -e check-sampleconfig"
|
- "tox -e check-sampleconfig"
|
||||||
@ -46,6 +44,8 @@ steps:
|
|||||||
- docker#v3.0.1:
|
- docker#v3.0.1:
|
||||||
image: "python:3.6"
|
image: "python:3.6"
|
||||||
|
|
||||||
|
- wait
|
||||||
|
|
||||||
- command:
|
- command:
|
||||||
- "python -m pip install tox"
|
- "python -m pip install tox"
|
||||||
- "tox -e py27,codecov"
|
- "tox -e py27,codecov"
|
||||||
@ -56,6 +56,12 @@ steps:
|
|||||||
- docker#v3.0.1:
|
- docker#v3.0.1:
|
||||||
image: "python:2.7"
|
image: "python:2.7"
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- command:
|
- command:
|
||||||
- "python -m pip install tox"
|
- "python -m pip install tox"
|
||||||
@ -67,6 +73,12 @@ steps:
|
|||||||
- docker#v3.0.1:
|
- docker#v3.0.1:
|
||||||
image: "python:3.5"
|
image: "python:3.5"
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- command:
|
- command:
|
||||||
- "python -m pip install tox"
|
- "python -m pip install tox"
|
||||||
@ -78,6 +90,12 @@ steps:
|
|||||||
- docker#v3.0.1:
|
- docker#v3.0.1:
|
||||||
image: "python:3.6"
|
image: "python:3.6"
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- command:
|
- command:
|
||||||
- "python -m pip install tox"
|
- "python -m pip install tox"
|
||||||
@ -89,6 +107,12 @@ steps:
|
|||||||
- docker#v3.0.1:
|
- docker#v3.0.1:
|
||||||
image: "python:3.7"
|
image: "python:3.7"
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- command:
|
- command:
|
||||||
- "python -m pip install tox"
|
- "python -m pip install tox"
|
||||||
@ -100,6 +124,12 @@ steps:
|
|||||||
- docker#v3.0.1:
|
- docker#v3.0.1:
|
||||||
image: "python:2.7"
|
image: "python:2.7"
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- label: ":python: 2.7 / :postgres: 9.4"
|
- label: ":python: 2.7 / :postgres: 9.4"
|
||||||
env:
|
env:
|
||||||
@ -111,6 +141,12 @@ steps:
|
|||||||
run: testenv
|
run: testenv
|
||||||
config:
|
config:
|
||||||
- .buildkite/docker-compose.py27.pg94.yaml
|
- .buildkite/docker-compose.py27.pg94.yaml
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- label: ":python: 2.7 / :postgres: 9.5"
|
- label: ":python: 2.7 / :postgres: 9.5"
|
||||||
env:
|
env:
|
||||||
@ -122,6 +158,12 @@ steps:
|
|||||||
run: testenv
|
run: testenv
|
||||||
config:
|
config:
|
||||||
- .buildkite/docker-compose.py27.pg95.yaml
|
- .buildkite/docker-compose.py27.pg95.yaml
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- label: ":python: 3.5 / :postgres: 9.4"
|
- label: ":python: 3.5 / :postgres: 9.4"
|
||||||
env:
|
env:
|
||||||
@ -133,6 +175,12 @@ steps:
|
|||||||
run: testenv
|
run: testenv
|
||||||
config:
|
config:
|
||||||
- .buildkite/docker-compose.py35.pg94.yaml
|
- .buildkite/docker-compose.py35.pg94.yaml
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- label: ":python: 3.5 / :postgres: 9.5"
|
- label: ":python: 3.5 / :postgres: 9.5"
|
||||||
env:
|
env:
|
||||||
@ -144,6 +192,12 @@ steps:
|
|||||||
run: testenv
|
run: testenv
|
||||||
config:
|
config:
|
||||||
- .buildkite/docker-compose.py35.pg95.yaml
|
- .buildkite/docker-compose.py35.pg95.yaml
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- label: ":python: 3.7 / :postgres: 9.5"
|
- label: ":python: 3.7 / :postgres: 9.5"
|
||||||
env:
|
env:
|
||||||
@ -155,6 +209,12 @@ steps:
|
|||||||
run: testenv
|
run: testenv
|
||||||
config:
|
config:
|
||||||
- .buildkite/docker-compose.py37.pg95.yaml
|
- .buildkite/docker-compose.py37.pg95.yaml
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
|
||||||
- label: ":python: 3.7 / :postgres: 11"
|
- label: ":python: 3.7 / :postgres: 11"
|
||||||
env:
|
env:
|
||||||
@ -166,3 +226,9 @@ steps:
|
|||||||
run: testenv
|
run: testenv
|
||||||
config:
|
config:
|
||||||
- .buildkite/docker-compose.py37.pg11.yaml
|
- .buildkite/docker-compose.py37.pg11.yaml
|
||||||
|
retry:
|
||||||
|
automatic:
|
||||||
|
- exit_status: -1
|
||||||
|
limit: 2
|
||||||
|
- exit_status: 2
|
||||||
|
limit: 2
|
||||||
|
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# One username per supported platform and one custom link
|
||||||
|
patreon: matrixdotorg
|
||||||
|
liberapay: matrixdotorg
|
||||||
|
custom: https://paypal.me/matrixdotorg
|
94
CHANGES.md
94
CHANGES.md
@ -1,3 +1,97 @@
|
|||||||
|
Synapse 1.0.0rc2 (2019-06-10)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Remove redundant warning about key server response validation. ([\#5392](https://github.com/matrix-org/synapse/issues/5392))
|
||||||
|
- Fix bug where old keys stored in the database with a null valid until timestamp caused all verification requests for that key to fail. ([\#5415](https://github.com/matrix-org/synapse/issues/5415))
|
||||||
|
- Fix excessive memory using with default `federation_verify_certificates: true` configuration. ([\#5417](https://github.com/matrix-org/synapse/issues/5417))
|
||||||
|
|
||||||
|
|
||||||
|
Synapse 1.0.0rc1 (2019-06-07)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Synapse now more efficiently collates room statistics. ([\#4338](https://github.com/matrix-org/synapse/issues/4338), [\#5260](https://github.com/matrix-org/synapse/issues/5260), [\#5324](https://github.com/matrix-org/synapse/issues/5324))
|
||||||
|
- Add experimental support for relations (aka reactions and edits). ([\#5220](https://github.com/matrix-org/synapse/issues/5220))
|
||||||
|
- Ability to configure default room version. ([\#5223](https://github.com/matrix-org/synapse/issues/5223), [\#5249](https://github.com/matrix-org/synapse/issues/5249))
|
||||||
|
- Allow configuring a range for the account validity startup job. ([\#5276](https://github.com/matrix-org/synapse/issues/5276))
|
||||||
|
- CAS login will now hit the r0 API, not the deprecated v1 one. ([\#5286](https://github.com/matrix-org/synapse/issues/5286))
|
||||||
|
- Validate federation server TLS certificates by default (implements [MSC1711](https://github.com/matrix-org/matrix-doc/blob/master/proposals/1711-x509-for-federation.md)). ([\#5359](https://github.com/matrix-org/synapse/issues/5359))
|
||||||
|
- Update /_matrix/client/versions to reference support for r0.5.0. ([\#5360](https://github.com/matrix-org/synapse/issues/5360))
|
||||||
|
- Add a script to generate new signing-key files. ([\#5361](https://github.com/matrix-org/synapse/issues/5361))
|
||||||
|
- Update upgrade and installation guides ahead of 1.0. ([\#5371](https://github.com/matrix-org/synapse/issues/5371))
|
||||||
|
- Replace the `perspectives` configuration section with `trusted_key_servers`, and make validating the signatures on responses optional (since TLS will do this job for us). ([\#5374](https://github.com/matrix-org/synapse/issues/5374))
|
||||||
|
- Add ability to perform password reset via email without trusting the identity server. ([\#5377](https://github.com/matrix-org/synapse/issues/5377))
|
||||||
|
- Set default room version to v4. ([\#5379](https://github.com/matrix-org/synapse/issues/5379))
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Fixes client-server API not sending "m.heroes" to lazy-load /sync requests when a rooms name or its canonical alias are empty. Thanks to @dnaf for this work! ([\#5089](https://github.com/matrix-org/synapse/issues/5089))
|
||||||
|
- Prevent federation device list updates breaking when processing multiple updates at once. ([\#5156](https://github.com/matrix-org/synapse/issues/5156))
|
||||||
|
- Fix worker registration bug caused by ClientReaderSlavedStore being unable to see get_profileinfo. ([\#5200](https://github.com/matrix-org/synapse/issues/5200))
|
||||||
|
- Fix race when backfilling in rooms with worker mode. ([\#5221](https://github.com/matrix-org/synapse/issues/5221))
|
||||||
|
- Fix appservice timestamp massaging. ([\#5233](https://github.com/matrix-org/synapse/issues/5233))
|
||||||
|
- Ensure that server_keys fetched via a notary server are correctly signed. ([\#5251](https://github.com/matrix-org/synapse/issues/5251))
|
||||||
|
- Show the correct error when logging out and access token is missing. ([\#5256](https://github.com/matrix-org/synapse/issues/5256))
|
||||||
|
- Fix error code when there is an invalid parameter on /_matrix/client/r0/publicRooms ([\#5257](https://github.com/matrix-org/synapse/issues/5257))
|
||||||
|
- Fix error when downloading thumbnail with missing width/height parameter. ([\#5258](https://github.com/matrix-org/synapse/issues/5258))
|
||||||
|
- Fix schema update for account validity. ([\#5268](https://github.com/matrix-org/synapse/issues/5268))
|
||||||
|
- Fix bug where we leaked extremities when we soft failed events, leading to performance degradation. ([\#5274](https://github.com/matrix-org/synapse/issues/5274), [\#5278](https://github.com/matrix-org/synapse/issues/5278), [\#5291](https://github.com/matrix-org/synapse/issues/5291))
|
||||||
|
- Fix "db txn 'update_presence' from sentinel context" log messages. ([\#5275](https://github.com/matrix-org/synapse/issues/5275))
|
||||||
|
- Fix dropped logcontexts during high outbound traffic. ([\#5277](https://github.com/matrix-org/synapse/issues/5277))
|
||||||
|
- Fix a bug where it is not possible to get events in the federation format with the request `GET /_matrix/client/r0/rooms/{roomId}/messages`. ([\#5293](https://github.com/matrix-org/synapse/issues/5293))
|
||||||
|
- Fix performance problems with the rooms stats background update. ([\#5294](https://github.com/matrix-org/synapse/issues/5294))
|
||||||
|
- Fix noisy 'no key for server' logs. ([\#5300](https://github.com/matrix-org/synapse/issues/5300))
|
||||||
|
- Fix bug where a notary server would sometimes forget old keys. ([\#5307](https://github.com/matrix-org/synapse/issues/5307))
|
||||||
|
- Prevent users from setting huge displaynames and avatar URLs. ([\#5309](https://github.com/matrix-org/synapse/issues/5309))
|
||||||
|
- Fix handling of failures when processing incoming events where calling `/event_auth` on remote server fails. ([\#5317](https://github.com/matrix-org/synapse/issues/5317))
|
||||||
|
- Ensure that we have an up-to-date copy of the signing key when validating incoming federation requests. ([\#5321](https://github.com/matrix-org/synapse/issues/5321))
|
||||||
|
- Fix various problems which made the signing-key notary server time out for some requests. ([\#5333](https://github.com/matrix-org/synapse/issues/5333))
|
||||||
|
- Fix bug which would make certain operations (such as room joins) block for 20 minutes while attemoting to fetch verification keys. ([\#5334](https://github.com/matrix-org/synapse/issues/5334))
|
||||||
|
- Fix a bug where we could rapidly mark a server as unreachable even though it was only down for a few minutes. ([\#5335](https://github.com/matrix-org/synapse/issues/5335), [\#5340](https://github.com/matrix-org/synapse/issues/5340))
|
||||||
|
- Fix a bug where account validity renewal emails could only be sent when email notifs were enabled. ([\#5341](https://github.com/matrix-org/synapse/issues/5341))
|
||||||
|
- Fix failure when fetching batches of events during backfill, etc. ([\#5342](https://github.com/matrix-org/synapse/issues/5342))
|
||||||
|
- Add a new room version where the timestamps on events are checked against the validity periods on signing keys. ([\#5348](https://github.com/matrix-org/synapse/issues/5348), [\#5354](https://github.com/matrix-org/synapse/issues/5354))
|
||||||
|
- Fix room stats and presence background updates to correctly handle missing events. ([\#5352](https://github.com/matrix-org/synapse/issues/5352))
|
||||||
|
- Include left members in room summaries' heroes. ([\#5355](https://github.com/matrix-org/synapse/issues/5355))
|
||||||
|
- Fix `federation_custom_ca_list` configuration option. ([\#5362](https://github.com/matrix-org/synapse/issues/5362))
|
||||||
|
- Fix missing logcontext warnings on shutdown. ([\#5369](https://github.com/matrix-org/synapse/issues/5369))
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- Fix docs on resetting the user directory. ([\#5282](https://github.com/matrix-org/synapse/issues/5282))
|
||||||
|
- Fix notes about ACME in the MSC1711 faq. ([\#5357](https://github.com/matrix-org/synapse/issues/5357))
|
||||||
|
|
||||||
|
|
||||||
|
Internal Changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- Synapse will now serve the experimental "room complexity" API endpoint. ([\#5216](https://github.com/matrix-org/synapse/issues/5216))
|
||||||
|
- The base classes for the v1 and v2_alpha REST APIs have been unified. ([\#5226](https://github.com/matrix-org/synapse/issues/5226), [\#5328](https://github.com/matrix-org/synapse/issues/5328))
|
||||||
|
- Simplifications and comments in do_auth. ([\#5227](https://github.com/matrix-org/synapse/issues/5227))
|
||||||
|
- Remove urllib3 pin as requests 2.22.0 has been released supporting urllib3 1.25.2. ([\#5230](https://github.com/matrix-org/synapse/issues/5230))
|
||||||
|
- Preparatory work for key-validity features. ([\#5232](https://github.com/matrix-org/synapse/issues/5232), [\#5234](https://github.com/matrix-org/synapse/issues/5234), [\#5235](https://github.com/matrix-org/synapse/issues/5235), [\#5236](https://github.com/matrix-org/synapse/issues/5236), [\#5237](https://github.com/matrix-org/synapse/issues/5237), [\#5244](https://github.com/matrix-org/synapse/issues/5244), [\#5250](https://github.com/matrix-org/synapse/issues/5250), [\#5296](https://github.com/matrix-org/synapse/issues/5296), [\#5299](https://github.com/matrix-org/synapse/issues/5299), [\#5343](https://github.com/matrix-org/synapse/issues/5343), [\#5347](https://github.com/matrix-org/synapse/issues/5347), [\#5356](https://github.com/matrix-org/synapse/issues/5356))
|
||||||
|
- Specify the type of reCAPTCHA key to use. ([\#5283](https://github.com/matrix-org/synapse/issues/5283))
|
||||||
|
- Improve sample config for monthly active user blocking. ([\#5284](https://github.com/matrix-org/synapse/issues/5284))
|
||||||
|
- Remove spurious debug from MatrixFederationHttpClient.get_json. ([\#5287](https://github.com/matrix-org/synapse/issues/5287))
|
||||||
|
- Improve logging for logcontext leaks. ([\#5288](https://github.com/matrix-org/synapse/issues/5288))
|
||||||
|
- Clarify that the admin change password API logs the user out. ([\#5303](https://github.com/matrix-org/synapse/issues/5303))
|
||||||
|
- New installs will now use the v54 full schema, rather than the full schema v14 and applying incremental updates to v54. ([\#5320](https://github.com/matrix-org/synapse/issues/5320))
|
||||||
|
- Improve docstrings on MatrixFederationClient. ([\#5332](https://github.com/matrix-org/synapse/issues/5332))
|
||||||
|
- Clean up FederationClient.get_events for clarity. ([\#5344](https://github.com/matrix-org/synapse/issues/5344))
|
||||||
|
- Various improvements to debug logging. ([\#5353](https://github.com/matrix-org/synapse/issues/5353))
|
||||||
|
- Don't run CI build checks until sample config check has passed. ([\#5370](https://github.com/matrix-org/synapse/issues/5370))
|
||||||
|
- Automatically retry buildkite builds (max twice) when an agent is lost. ([\#5380](https://github.com/matrix-org/synapse/issues/5380))
|
||||||
|
|
||||||
|
|
||||||
Synapse 0.99.5.2 (2019-05-30)
|
Synapse 0.99.5.2 (2019-05-30)
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
16
INSTALL.md
16
INSTALL.md
@ -5,6 +5,7 @@
|
|||||||
* [Prebuilt packages](#prebuilt-packages)
|
* [Prebuilt packages](#prebuilt-packages)
|
||||||
* [Setting up Synapse](#setting-up-synapse)
|
* [Setting up Synapse](#setting-up-synapse)
|
||||||
* [TLS certificates](#tls-certificates)
|
* [TLS certificates](#tls-certificates)
|
||||||
|
* [Email](#email)
|
||||||
* [Registering a user](#registering-a-user)
|
* [Registering a user](#registering-a-user)
|
||||||
* [Setting up a TURN server](#setting-up-a-turn-server)
|
* [Setting up a TURN server](#setting-up-a-turn-server)
|
||||||
* [URL previews](#url-previews)
|
* [URL previews](#url-previews)
|
||||||
@ -394,9 +395,22 @@ To configure Synapse to expose an HTTPS port, you will need to edit
|
|||||||
instance, if using certbot, use `fullchain.pem` as your certificate, not
|
instance, if using certbot, use `fullchain.pem` as your certificate, not
|
||||||
`cert.pem`).
|
`cert.pem`).
|
||||||
|
|
||||||
For those of you upgrading your TLS certificate in readiness for Synapse 1.0,
|
For those of you upgrading your TLS certificate for Synapse 1.0 compliance,
|
||||||
please take a look at [our guide](docs/MSC1711_certificates_FAQ.md#configuring-certificates-for-compatibility-with-synapse-100).
|
please take a look at [our guide](docs/MSC1711_certificates_FAQ.md#configuring-certificates-for-compatibility-with-synapse-100).
|
||||||
|
|
||||||
|
## Email
|
||||||
|
|
||||||
|
It is desirable for Synapse to have the capability to send email. For example,
|
||||||
|
this is required to support the 'password reset' feature.
|
||||||
|
|
||||||
|
To configure an SMTP server for Synapse, modify the configuration section
|
||||||
|
headed ``email``, and be sure to have at least the ``smtp_host``, ``smtp_port``
|
||||||
|
and ``notif_from`` fields filled out. You may also need to set ``smtp_user``,
|
||||||
|
``smtp_pass``, and ``require_transport_security``.
|
||||||
|
|
||||||
|
If Synapse is not configured with an SMTP server, password reset via email will
|
||||||
|
be disabled by default.
|
||||||
|
|
||||||
## Registering a user
|
## Registering a user
|
||||||
|
|
||||||
You will need at least one user on your server in order to use a Matrix
|
You will need at least one user on your server in order to use a Matrix
|
||||||
|
@ -9,14 +9,19 @@ include demo/*.py
|
|||||||
include demo/*.sh
|
include demo/*.sh
|
||||||
|
|
||||||
recursive-include synapse/storage/schema *.sql
|
recursive-include synapse/storage/schema *.sql
|
||||||
|
recursive-include synapse/storage/schema *.sql.postgres
|
||||||
|
recursive-include synapse/storage/schema *.sql.sqlite
|
||||||
recursive-include synapse/storage/schema *.py
|
recursive-include synapse/storage/schema *.py
|
||||||
|
recursive-include synapse/storage/schema *.txt
|
||||||
|
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
recursive-include scripts *
|
recursive-include scripts *
|
||||||
recursive-include scripts-dev *
|
recursive-include scripts-dev *
|
||||||
recursive-include synapse *.pyi
|
recursive-include synapse *.pyi
|
||||||
recursive-include tests *.pem
|
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
|
include tests/http/ca.crt
|
||||||
|
include tests/http/ca.key
|
||||||
|
include tests/http/server.key
|
||||||
|
|
||||||
recursive-include synapse/res *
|
recursive-include synapse/res *
|
||||||
recursive-include synapse/static *.css
|
recursive-include synapse/static *.css
|
||||||
|
49
UPGRADE.rst
49
UPGRADE.rst
@ -49,6 +49,55 @@ returned by the Client-Server API:
|
|||||||
# configured on port 443.
|
# configured on port 443.
|
||||||
curl -kv https://<host.name>/_matrix/client/versions 2>&1 | grep "Server:"
|
curl -kv https://<host.name>/_matrix/client/versions 2>&1 | grep "Server:"
|
||||||
|
|
||||||
|
Upgrading to v1.0
|
||||||
|
=================
|
||||||
|
|
||||||
|
Validation of TLS certificates
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
Synapse v1.0 is the first release to enforce
|
||||||
|
validation of TLS certificates for the federation API. It is therefore
|
||||||
|
essential that your certificates are correctly configured. See the `FAQ
|
||||||
|
<docs/MSC1711_certificates_FAQ.md>`_ for more information.
|
||||||
|
|
||||||
|
Note, v1.0 installations will also no longer be able to federate with servers
|
||||||
|
that have not correctly configured their certificates.
|
||||||
|
|
||||||
|
In rare cases, it may be desirable to disable certificate checking: for
|
||||||
|
example, it might be essential to be able to federate with a given legacy
|
||||||
|
server in a closed federation. This can be done in one of two ways:-
|
||||||
|
|
||||||
|
* Configure the global switch ``federation_verify_certificates`` to ``false``.
|
||||||
|
* Configure a whitelist of server domains to trust via ``federation_certificate_verification_whitelist``.
|
||||||
|
|
||||||
|
See the `sample configuration file <docs/sample_config.yaml>`_
|
||||||
|
for more details on these settings.
|
||||||
|
|
||||||
|
Email
|
||||||
|
-----
|
||||||
|
When a user requests a password reset, Synapse will send an email to the
|
||||||
|
user to confirm the request.
|
||||||
|
|
||||||
|
Previous versions of Synapse delegated the job of sending this email to an
|
||||||
|
identity server. If the identity server was somehow malicious or became
|
||||||
|
compromised, it would be theoretically possible to hijack an account through
|
||||||
|
this means.
|
||||||
|
|
||||||
|
Therefore, by default, Synapse v1.0 will send the confirmation email itself. If
|
||||||
|
Synapse is not configured with an SMTP server, password reset via email will be
|
||||||
|
disabled.
|
||||||
|
|
||||||
|
To configure an SMTP server for Synapse, modify the configuration section
|
||||||
|
headed ``email``, and be sure to have at least the ``smtp_host``, ``smtp_port``
|
||||||
|
and ``notif_from`` fields filled out. You may also need to set ``smtp_user``,
|
||||||
|
``smtp_pass``, and ``require_transport_security``.
|
||||||
|
|
||||||
|
If you are absolutely certain that you wish to continue using an identity
|
||||||
|
server for password resets, set ``trust_identity_server_for_password_resets`` to ``true``.
|
||||||
|
|
||||||
|
See the `sample configuration file <docs/sample_config.yaml>`_
|
||||||
|
for more details on these settings.
|
||||||
|
|
||||||
Upgrading to v0.99.0
|
Upgrading to v0.99.0
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Synapse now more efficiently collates room statistics.
|
|
@ -1 +0,0 @@
|
|||||||
Fix worker registration bug caused by ClientReaderSlavedStore being unable to see get_profileinfo.
|
|
@ -1 +0,0 @@
|
|||||||
Synapse will now serve the experimental "room complexity" API endpoint.
|
|
@ -1 +0,0 @@
|
|||||||
Add experimental support for relations (aka reactions and edits).
|
|
@ -1 +0,0 @@
|
|||||||
Ability to configure default room version.
|
|
@ -1 +0,0 @@
|
|||||||
Simplifications and comments in do_auth.
|
|
@ -1 +0,0 @@
|
|||||||
Remove urllib3 pin as requests 2.22.0 has been released supporting urllib3 1.25.2.
|
|
@ -1 +0,0 @@
|
|||||||
Run black on synapse.crypto.keyring.
|
|
@ -1 +0,0 @@
|
|||||||
Fix appservice timestamp massaging.
|
|
@ -1 +0,0 @@
|
|||||||
Rewrite store_server_verify_key to store several keys at once.
|
|
@ -1 +0,0 @@
|
|||||||
Remove unused VerifyKey.expired and .time_added fields.
|
|
@ -1 +0,0 @@
|
|||||||
Simplify Keyring.process_v2_response.
|
|
@ -1 +0,0 @@
|
|||||||
Store key validity time in the storage layer.
|
|
@ -1 +0,0 @@
|
|||||||
Refactor synapse.crypto.keyring to use a KeyFetcher interface.
|
|
@ -1 +0,0 @@
|
|||||||
Ability to configure default room version.
|
|
@ -1 +0,0 @@
|
|||||||
Simplification to Keyring.wait_for_previous_lookups.
|
|
@ -1 +0,0 @@
|
|||||||
Ensure that server_keys fetched via a notary server are correctly signed.
|
|
@ -1 +0,0 @@
|
|||||||
Show the correct error when logging out and access token is missing.
|
|
@ -1 +0,0 @@
|
|||||||
Fix error code when there is an invalid parameter on /_matrix/client/r0/publicRooms
|
|
@ -1 +0,0 @@
|
|||||||
Fix error when downloading thumbnail with missing width/height parameter.
|
|
@ -1 +0,0 @@
|
|||||||
Synapse now more efficiently collates room statistics.
|
|
@ -1 +0,0 @@
|
|||||||
Fix schema update for account validity.
|
|
@ -1 +0,0 @@
|
|||||||
Fix bug where we leaked extremities when we soft failed events, leading to performance degradation.
|
|
@ -1 +0,0 @@
|
|||||||
Fix "db txn 'update_presence' from sentinel context" log messages.
|
|
@ -1 +0,0 @@
|
|||||||
Allow configuring a range for the account validity startup job.
|
|
@ -1 +0,0 @@
|
|||||||
Fix dropped logcontexts during high outbound traffic.
|
|
@ -1 +0,0 @@
|
|||||||
Fix bug where we leaked extremities when we soft failed events, leading to performance degradation.
|
|
@ -1 +0,0 @@
|
|||||||
Fix docs on resetting the user directory.
|
|
@ -1 +0,0 @@
|
|||||||
Specify the type of reCAPTCHA key to use.
|
|
@ -1 +0,0 @@
|
|||||||
CAS login will now hit the r0 API, not the deprecated v1 one.
|
|
@ -1 +0,0 @@
|
|||||||
Remove spurious debug from MatrixFederationHttpClient.get_json.
|
|
@ -1 +0,0 @@
|
|||||||
Improve logging for logcontext leaks.
|
|
@ -1 +0,0 @@
|
|||||||
Fix bug where we leaked extremities when we soft failed events, leading to performance degradation.
|
|
@ -1 +0,0 @@
|
|||||||
Fix a bug where it is not possible to get events in the federation format with the request `GET /_matrix/client/r0/rooms/{roomId}/messages`.
|
|
@ -1 +0,0 @@
|
|||||||
Fix performance problems with the rooms stats background update.
|
|
@ -1 +0,0 @@
|
|||||||
Refactor keyring.VerifyKeyRequest to use attr.s.
|
|
@ -1 +0,0 @@
|
|||||||
Rewrite get_server_verify_keys, again.
|
|
@ -1 +0,0 @@
|
|||||||
Fix noisy 'no key for server' logs.
|
|
@ -1 +0,0 @@
|
|||||||
Clarify that the admin change password API logs the user out.
|
|
@ -1 +0,0 @@
|
|||||||
Prevent users from setting huge displaynames and avatar URLs.
|
|
1
changelog.d/5325.bugfix
Normal file
1
changelog.d/5325.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix a bug where running synapse_port_db would cause the account validity feature to fail because it didn't set the type of the email_sent column to boolean.
|
1
changelog.d/5363.feature
Normal file
1
changelog.d/5363.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Allow expired user to trigger renewal email sending manually.
|
1
changelog.d/5382.misc
Normal file
1
changelog.d/5382.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add a sponsor button to the repo.
|
1
changelog.d/5386.misc
Normal file
1
changelog.d/5386.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add a sponsor button to the repo.
|
1
changelog.d/5412.feature
Normal file
1
changelog.d/5412.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add --no-daemonize option to run synapse in the foreground, per issue #4130. Contributed by Soham Gumaste.
|
@ -68,16 +68,14 @@ Admins should upgrade and configure a valid CA cert. Homeservers that require a
|
|||||||
.well-known entry (see below), should retain their SRV record and use it
|
.well-known entry (see below), should retain their SRV record and use it
|
||||||
alongside their .well-known record.
|
alongside their .well-known record.
|
||||||
|
|
||||||
**>= 5th March 2019 - Synapse 1.0.0 is released**
|
**10th June 2019 - Synapse 1.0.0 is released**
|
||||||
|
|
||||||
1.0.0 will land no sooner than 1 month after 0.99.0, leaving server admins one
|
1.0.0 is scheduled for release on 10th June. In
|
||||||
month after 5th February to upgrade to 0.99.0 and deploy their certificates. In
|
|
||||||
accordance with the the [S2S spec](https://matrix.org/docs/spec/server_server/r0.1.0.html)
|
accordance with the the [S2S spec](https://matrix.org/docs/spec/server_server/r0.1.0.html)
|
||||||
1.0.0 will enforce certificate validity. This means that any homeserver without a
|
1.0.0 will enforce certificate validity. This means that any homeserver without a
|
||||||
valid certificate after this point will no longer be able to federate with
|
valid certificate after this point will no longer be able to federate with
|
||||||
1.0.0 servers.
|
1.0.0 servers.
|
||||||
|
|
||||||
|
|
||||||
## Configuring certificates for compatibility with Synapse 1.0.0
|
## Configuring certificates for compatibility with Synapse 1.0.0
|
||||||
|
|
||||||
### If you do not currently have an SRV record
|
### If you do not currently have an SRV record
|
||||||
@ -145,12 +143,11 @@ You can do this with a `.well-known` file as follows:
|
|||||||
1. Keep the SRV record in place - it is needed for backwards compatibility
|
1. Keep the SRV record in place - it is needed for backwards compatibility
|
||||||
with Synapse 0.34 and earlier.
|
with Synapse 0.34 and earlier.
|
||||||
|
|
||||||
2. Give synapse a certificate corresponding to the target domain
|
2. Give Synapse a certificate corresponding to the target domain
|
||||||
(`customer.example.net` in the above example). Currently Synapse's ACME
|
(`customer.example.net` in the above example). You can either use Synapse's
|
||||||
support [does not support
|
built-in [ACME support](./ACME.md) for this (via the `domain` parameter in
|
||||||
this](https://github.com/matrix-org/synapse/issues/4552), so you will have
|
the `acme` section), or acquire a certificate yourself and give it to
|
||||||
to acquire a certificate yourself and give it to Synapse via
|
Synapse via `tls_certificate_path` and `tls_private_key_path`.
|
||||||
`tls_certificate_path` and `tls_private_key_path`.
|
|
||||||
|
|
||||||
3. Restart Synapse to ensure the new certificate is loaded.
|
3. Restart Synapse to ensure the new certificate is loaded.
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ pid_file: DATADIR/homeserver.pid
|
|||||||
# For example, for room version 1, default_room_version should be set
|
# For example, for room version 1, default_room_version should be set
|
||||||
# to "1".
|
# to "1".
|
||||||
#
|
#
|
||||||
#default_room_version: "1"
|
#default_room_version: "4"
|
||||||
|
|
||||||
# The GC threshold parameters to pass to `gc.set_threshold`, if defined
|
# The GC threshold parameters to pass to `gc.set_threshold`, if defined
|
||||||
#
|
#
|
||||||
@ -261,6 +261,22 @@ listeners:
|
|||||||
|
|
||||||
# Monthly Active User Blocking
|
# Monthly Active User Blocking
|
||||||
#
|
#
|
||||||
|
# Used in cases where the admin or server owner wants to limit to the
|
||||||
|
# number of monthly active users.
|
||||||
|
#
|
||||||
|
# 'limit_usage_by_mau' disables/enables monthly active user blocking. When
|
||||||
|
# anabled and a limit is reached the server returns a 'ResourceLimitError'
|
||||||
|
# with error type Codes.RESOURCE_LIMIT_EXCEEDED
|
||||||
|
#
|
||||||
|
# 'max_mau_value' is the hard limit of monthly active users above which
|
||||||
|
# the server will start blocking user actions.
|
||||||
|
#
|
||||||
|
# 'mau_trial_days' is a means to add a grace period for active users. It
|
||||||
|
# means that users must be active for this number of days before they
|
||||||
|
# can be considered active and guards against the case where lots of users
|
||||||
|
# sign up in a short space of time never to return after their initial
|
||||||
|
# session.
|
||||||
|
#
|
||||||
#limit_usage_by_mau: False
|
#limit_usage_by_mau: False
|
||||||
#max_mau_value: 50
|
#max_mau_value: 50
|
||||||
#mau_trial_days: 2
|
#mau_trial_days: 2
|
||||||
@ -313,12 +329,12 @@ listeners:
|
|||||||
#
|
#
|
||||||
#tls_private_key_path: "CONFDIR/SERVERNAME.tls.key"
|
#tls_private_key_path: "CONFDIR/SERVERNAME.tls.key"
|
||||||
|
|
||||||
# Whether to verify TLS certificates when sending federation traffic.
|
# Whether to verify TLS server certificates for outbound federation requests.
|
||||||
#
|
#
|
||||||
# This currently defaults to `false`, however this will change in
|
# Defaults to `true`. To disable certificate verification, uncomment the
|
||||||
# Synapse 1.0 when valid federation certificates will be required.
|
# following line.
|
||||||
#
|
#
|
||||||
#federation_verify_certificates: true
|
#federation_verify_certificates: false
|
||||||
|
|
||||||
# Skip federation certificate verification on the following whitelist
|
# Skip federation certificate verification on the following whitelist
|
||||||
# of domains.
|
# of domains.
|
||||||
@ -936,12 +952,43 @@ signing_key_path: "CONFDIR/SERVERNAME.signing.key"
|
|||||||
|
|
||||||
# The trusted servers to download signing keys from.
|
# The trusted servers to download signing keys from.
|
||||||
#
|
#
|
||||||
#perspectives:
|
# When we need to fetch a signing key, each server is tried in parallel.
|
||||||
# servers:
|
#
|
||||||
# "matrix.org":
|
# Normally, the connection to the key server is validated via TLS certificates.
|
||||||
# verify_keys:
|
# Additional security can be provided by configuring a `verify key`, which
|
||||||
# "ed25519:auto":
|
# will make synapse check that the response is signed by that key.
|
||||||
# key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
|
#
|
||||||
|
# This setting supercedes an older setting named `perspectives`. The old format
|
||||||
|
# is still supported for backwards-compatibility, but it is deprecated.
|
||||||
|
#
|
||||||
|
# Options for each entry in the list include:
|
||||||
|
#
|
||||||
|
# server_name: the name of the server. required.
|
||||||
|
#
|
||||||
|
# verify_keys: an optional map from key id to base64-encoded public key.
|
||||||
|
# If specified, we will check that the response is signed by at least
|
||||||
|
# one of the given keys.
|
||||||
|
#
|
||||||
|
# accept_keys_insecurely: a boolean. Normally, if `verify_keys` is unset,
|
||||||
|
# and federation_verify_certificates is not `true`, synapse will refuse
|
||||||
|
# to start, because this would allow anyone who can spoof DNS responses
|
||||||
|
# to masquerade as the trusted key server. If you know what you are doing
|
||||||
|
# and are sure that your network environment provides a secure connection
|
||||||
|
# to the key server, you can set this to `true` to override this
|
||||||
|
# behaviour.
|
||||||
|
#
|
||||||
|
# An example configuration might look like:
|
||||||
|
#
|
||||||
|
#trusted_key_servers:
|
||||||
|
# - server_name: "my_trusted_server.example.com"
|
||||||
|
# verify_keys:
|
||||||
|
# "ed25519:auto": "abcdefghijklmnopqrstuvwxyzabcdefghijklmopqr"
|
||||||
|
# - server_name: "my_other_trusted_server.example.com"
|
||||||
|
#
|
||||||
|
# The default configuration is:
|
||||||
|
#
|
||||||
|
#trusted_key_servers:
|
||||||
|
# - server_name: "matrix.org"
|
||||||
|
|
||||||
|
|
||||||
# Enable SAML2 for registration and login. Uses pysaml2.
|
# Enable SAML2 for registration and login. Uses pysaml2.
|
||||||
@ -1018,10 +1065,8 @@ password_config:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Enable sending emails for notification events or expiry notices
|
# Enable sending emails for password resets, notification events or
|
||||||
# Defining a custom URL for Riot is only needed if email notifications
|
# account expiry notices
|
||||||
# should contain links to a self-hosted installation of Riot; when set
|
|
||||||
# the "app_name" setting is ignored.
|
|
||||||
#
|
#
|
||||||
# If your SMTP server requires authentication, the optional smtp_user &
|
# If your SMTP server requires authentication, the optional smtp_user &
|
||||||
# smtp_pass variables should be used
|
# smtp_pass variables should be used
|
||||||
@ -1029,22 +1074,64 @@ password_config:
|
|||||||
#email:
|
#email:
|
||||||
# enable_notifs: false
|
# enable_notifs: false
|
||||||
# smtp_host: "localhost"
|
# smtp_host: "localhost"
|
||||||
# smtp_port: 25
|
# smtp_port: 25 # SSL: 465, STARTTLS: 587
|
||||||
# smtp_user: "exampleusername"
|
# smtp_user: "exampleusername"
|
||||||
# smtp_pass: "examplepassword"
|
# smtp_pass: "examplepassword"
|
||||||
# require_transport_security: False
|
# require_transport_security: False
|
||||||
# notif_from: "Your Friendly %(app)s Home Server <noreply@example.com>"
|
# notif_from: "Your Friendly %(app)s Home Server <noreply@example.com>"
|
||||||
# app_name: Matrix
|
# app_name: Matrix
|
||||||
# # if template_dir is unset, uses the example templates that are part of
|
#
|
||||||
# # the Synapse distribution.
|
# # Enable email notifications by default
|
||||||
|
# notif_for_new_users: True
|
||||||
|
#
|
||||||
|
# # Defining a custom URL for Riot is only needed if email notifications
|
||||||
|
# # should contain links to a self-hosted installation of Riot; when set
|
||||||
|
# # the "app_name" setting is ignored
|
||||||
|
# riot_base_url: "http://localhost/riot"
|
||||||
|
#
|
||||||
|
# # Enable sending password reset emails via the configured, trusted
|
||||||
|
# # identity servers
|
||||||
|
# #
|
||||||
|
# # IMPORTANT! This will give a malicious or overtaken identity server
|
||||||
|
# # the ability to reset passwords for your users! Make absolutely sure
|
||||||
|
# # that you want to do this! It is strongly recommended that password
|
||||||
|
# # reset emails be sent by the homeserver instead
|
||||||
|
# #
|
||||||
|
# # If this option is set to false and SMTP options have not been
|
||||||
|
# # configured, resetting user passwords via email will be disabled
|
||||||
|
# #trust_identity_server_for_password_resets: false
|
||||||
|
#
|
||||||
|
# # Configure the time that a validation email or text message code
|
||||||
|
# # will expire after sending
|
||||||
|
# #
|
||||||
|
# # This is currently used for password resets
|
||||||
|
# #validation_token_lifetime: 1h
|
||||||
|
#
|
||||||
|
# # Template directory. All template files should be stored within this
|
||||||
|
# # directory
|
||||||
|
# #
|
||||||
# #template_dir: res/templates
|
# #template_dir: res/templates
|
||||||
|
#
|
||||||
|
# # Templates for email notifications
|
||||||
|
# #
|
||||||
# notif_template_html: notif_mail.html
|
# notif_template_html: notif_mail.html
|
||||||
# notif_template_text: notif_mail.txt
|
# notif_template_text: notif_mail.txt
|
||||||
# # Templates for account expiry notices.
|
#
|
||||||
|
# # Templates for account expiry notices
|
||||||
|
# #
|
||||||
# expiry_template_html: notice_expiry.html
|
# expiry_template_html: notice_expiry.html
|
||||||
# expiry_template_text: notice_expiry.txt
|
# expiry_template_text: notice_expiry.txt
|
||||||
# notif_for_new_users: True
|
#
|
||||||
# riot_base_url: "http://localhost/riot"
|
# # Templates for password reset emails sent by the homeserver
|
||||||
|
# #
|
||||||
|
# #password_reset_template_html: password_reset.html
|
||||||
|
# #password_reset_template_text: password_reset.txt
|
||||||
|
#
|
||||||
|
# # Templates for password reset success and failure pages that a user
|
||||||
|
# # will see after attempting to reset their password
|
||||||
|
# #
|
||||||
|
# #password_reset_template_success_html: password_reset_success.html
|
||||||
|
# #password_reset_template_failure_html: password_reset_failure.html
|
||||||
|
|
||||||
|
|
||||||
#password_providers:
|
#password_providers:
|
||||||
|
@ -20,9 +20,7 @@ class CallVisitor(ast.NodeVisitor):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
if name == "client_path_patterns":
|
if name == "client_patterns":
|
||||||
PATTERNS_V1.append(node.args[0].s)
|
|
||||||
elif name == "client_v2_patterns":
|
|
||||||
PATTERNS_V2.append(node.args[0].s)
|
PATTERNS_V2.append(node.args[0].s)
|
||||||
|
|
||||||
|
|
||||||
|
37
scripts/generate_signing_key.py
Executable file
37
scripts/generate_signing_key.py
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from signedjson.key import write_signing_keys, generate_signing_key
|
||||||
|
|
||||||
|
from synapse.util.stringutils import random_string
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-o", "--output_file",
|
||||||
|
|
||||||
|
type=argparse.FileType('w'),
|
||||||
|
default=sys.stdout,
|
||||||
|
help="Where to write the output to",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
key_id = "a_" + random_string(4)
|
||||||
|
key = generate_signing_key(key_id),
|
||||||
|
write_signing_keys(args.output_file, key)
|
@ -54,6 +54,7 @@ BOOLEAN_COLUMNS = {
|
|||||||
"group_roles": ["is_public"],
|
"group_roles": ["is_public"],
|
||||||
"local_group_membership": ["is_publicised", "is_admin"],
|
"local_group_membership": ["is_publicised", "is_admin"],
|
||||||
"e2e_room_keys": ["is_verified"],
|
"e2e_room_keys": ["is_verified"],
|
||||||
|
"account_validity": ["email_sent"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,4 +27,4 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
__version__ = "0.99.5.2"
|
__version__ = "1.0.0rc2"
|
||||||
|
@ -184,11 +184,22 @@ class Auth(object):
|
|||||||
return event_auth.get_public_keys(invite_event)
|
return event_auth.get_public_keys(invite_event)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_user_by_req(self, request, allow_guest=False, rights="access"):
|
def get_user_by_req(
|
||||||
|
self,
|
||||||
|
request,
|
||||||
|
allow_guest=False,
|
||||||
|
rights="access",
|
||||||
|
allow_expired=False,
|
||||||
|
):
|
||||||
""" Get a registered user's ID.
|
""" Get a registered user's ID.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request - An HTTP request with an access_token query parameter.
|
request - An HTTP request with an access_token query parameter.
|
||||||
|
allow_expired - Whether to allow the request through even if the account is
|
||||||
|
expired. If true, Synapse will still require an access token to be
|
||||||
|
provided but won't check if the account it belongs to has expired. This
|
||||||
|
works thanks to /login delivering access tokens regardless of accounts'
|
||||||
|
expiration.
|
||||||
Returns:
|
Returns:
|
||||||
defer.Deferred: resolves to a ``synapse.types.Requester`` object
|
defer.Deferred: resolves to a ``synapse.types.Requester`` object
|
||||||
Raises:
|
Raises:
|
||||||
@ -229,7 +240,7 @@ class Auth(object):
|
|||||||
is_guest = user_info["is_guest"]
|
is_guest = user_info["is_guest"]
|
||||||
|
|
||||||
# Deny the request if the user account has expired.
|
# Deny the request if the user account has expired.
|
||||||
if self._account_validity.enabled:
|
if self._account_validity.enabled and not allow_expired:
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
expiration_ts = yield self.store.get_expiration_ts_for_user(user_id)
|
expiration_ts = yield self.store.get_expiration_ts_for_user(user_id)
|
||||||
if expiration_ts is not None and self.clock.time_msec() >= expiration_ts:
|
if expiration_ts is not None and self.clock.time_msec() >= expiration_ts:
|
||||||
|
@ -339,6 +339,15 @@ class UnsupportedRoomVersionError(SynapseError):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ThreepidValidationError(SynapseError):
|
||||||
|
"""An error raised when there was a problem authorising an event."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if "errcode" not in kwargs:
|
||||||
|
kwargs["errcode"] = Codes.FORBIDDEN
|
||||||
|
super(ThreepidValidationError, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class IncompatibleRoomVersionError(SynapseError):
|
class IncompatibleRoomVersionError(SynapseError):
|
||||||
"""A server is trying to join a room whose version it does not support.
|
"""A server is trying to join a room whose version it does not support.
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ class RoomVersion(object):
|
|||||||
disposition = attr.ib() # str; one of the RoomDispositions
|
disposition = attr.ib() # str; one of the RoomDispositions
|
||||||
event_format = attr.ib() # int; one of the EventFormatVersions
|
event_format = attr.ib() # int; one of the EventFormatVersions
|
||||||
state_res = attr.ib() # int; one of the StateResolutionVersions
|
state_res = attr.ib() # int; one of the StateResolutionVersions
|
||||||
|
enforce_key_validity = attr.ib() # bool
|
||||||
|
|
||||||
|
|
||||||
class RoomVersions(object):
|
class RoomVersions(object):
|
||||||
@ -58,30 +59,35 @@ class RoomVersions(object):
|
|||||||
RoomDisposition.STABLE,
|
RoomDisposition.STABLE,
|
||||||
EventFormatVersions.V1,
|
EventFormatVersions.V1,
|
||||||
StateResolutionVersions.V1,
|
StateResolutionVersions.V1,
|
||||||
)
|
enforce_key_validity=False,
|
||||||
STATE_V2_TEST = RoomVersion(
|
|
||||||
"state-v2-test",
|
|
||||||
RoomDisposition.UNSTABLE,
|
|
||||||
EventFormatVersions.V1,
|
|
||||||
StateResolutionVersions.V2,
|
|
||||||
)
|
)
|
||||||
V2 = RoomVersion(
|
V2 = RoomVersion(
|
||||||
"2",
|
"2",
|
||||||
RoomDisposition.STABLE,
|
RoomDisposition.STABLE,
|
||||||
EventFormatVersions.V1,
|
EventFormatVersions.V1,
|
||||||
StateResolutionVersions.V2,
|
StateResolutionVersions.V2,
|
||||||
|
enforce_key_validity=False,
|
||||||
)
|
)
|
||||||
V3 = RoomVersion(
|
V3 = RoomVersion(
|
||||||
"3",
|
"3",
|
||||||
RoomDisposition.STABLE,
|
RoomDisposition.STABLE,
|
||||||
EventFormatVersions.V2,
|
EventFormatVersions.V2,
|
||||||
StateResolutionVersions.V2,
|
StateResolutionVersions.V2,
|
||||||
|
enforce_key_validity=False,
|
||||||
)
|
)
|
||||||
V4 = RoomVersion(
|
V4 = RoomVersion(
|
||||||
"4",
|
"4",
|
||||||
RoomDisposition.STABLE,
|
RoomDisposition.STABLE,
|
||||||
EventFormatVersions.V3,
|
EventFormatVersions.V3,
|
||||||
StateResolutionVersions.V2,
|
StateResolutionVersions.V2,
|
||||||
|
enforce_key_validity=False,
|
||||||
|
)
|
||||||
|
V5 = RoomVersion(
|
||||||
|
"5",
|
||||||
|
RoomDisposition.STABLE,
|
||||||
|
EventFormatVersions.V3,
|
||||||
|
StateResolutionVersions.V2,
|
||||||
|
enforce_key_validity=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -90,7 +96,7 @@ KNOWN_ROOM_VERSIONS = {
|
|||||||
RoomVersions.V1,
|
RoomVersions.V1,
|
||||||
RoomVersions.V2,
|
RoomVersions.V2,
|
||||||
RoomVersions.V3,
|
RoomVersions.V3,
|
||||||
RoomVersions.STATE_V2_TEST,
|
|
||||||
RoomVersions.V4,
|
RoomVersions.V4,
|
||||||
|
RoomVersions.V5,
|
||||||
)
|
)
|
||||||
} # type: dict[str, RoomVersion]
|
} # type: dict[str, RoomVersion]
|
||||||
|
@ -37,8 +37,7 @@ from synapse.replication.slave.storage.client_ips import SlavedClientIpStore
|
|||||||
from synapse.replication.slave.storage.devices import SlavedDeviceStore
|
from synapse.replication.slave.storage.devices import SlavedDeviceStore
|
||||||
from synapse.replication.slave.storage.registration import SlavedRegistrationStore
|
from synapse.replication.slave.storage.registration import SlavedRegistrationStore
|
||||||
from synapse.replication.tcp.client import ReplicationClientHandler
|
from synapse.replication.tcp.client import ReplicationClientHandler
|
||||||
from synapse.rest.client.v1.base import ClientV1RestServlet, client_path_patterns
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.rest.client.v2_alpha._base import client_v2_patterns
|
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.storage.engines import create_engine
|
from synapse.storage.engines import create_engine
|
||||||
from synapse.util.httpresourcetree import create_resource_tree
|
from synapse.util.httpresourcetree import create_resource_tree
|
||||||
@ -49,11 +48,11 @@ from synapse.util.versionstring import get_version_string
|
|||||||
logger = logging.getLogger("synapse.app.frontend_proxy")
|
logger = logging.getLogger("synapse.app.frontend_proxy")
|
||||||
|
|
||||||
|
|
||||||
class PresenceStatusStubServlet(ClientV1RestServlet):
|
class PresenceStatusStubServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/presence/(?P<user_id>[^/]*)/status")
|
PATTERNS = client_patterns("/presence/(?P<user_id>[^/]*)/status")
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(PresenceStatusStubServlet, self).__init__(hs)
|
super(PresenceStatusStubServlet, self).__init__()
|
||||||
self.http_client = hs.get_simple_http_client()
|
self.http_client = hs.get_simple_http_client()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
self.main_uri = hs.config.worker_main_http_uri
|
self.main_uri = hs.config.worker_main_http_uri
|
||||||
@ -84,7 +83,7 @@ class PresenceStatusStubServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
class KeyUploadServlet(RestServlet):
|
class KeyUploadServlet(RestServlet):
|
||||||
PATTERNS = client_v2_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
|
PATTERNS = client_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
"""
|
"""
|
||||||
|
@ -176,6 +176,7 @@ class SynapseHomeServer(HomeServer):
|
|||||||
|
|
||||||
resources.update({
|
resources.update({
|
||||||
"/_matrix/client/api/v1": client_resource,
|
"/_matrix/client/api/v1": client_resource,
|
||||||
|
"/_synapse/password_reset": client_resource,
|
||||||
"/_matrix/client/r0": client_resource,
|
"/_matrix/client/r0": client_resource,
|
||||||
"/_matrix/client/unstable": client_resource,
|
"/_matrix/client/unstable": client_resource,
|
||||||
"/_matrix/client/v2_alpha": client_resource,
|
"/_matrix/client/v2_alpha": client_resource,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015-2016 OpenMarket Ltd
|
||||||
|
# Copyright 2017-2018 New Vector Ltd
|
||||||
|
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -29,12 +31,76 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class EmailConfig(Config):
|
class EmailConfig(Config):
|
||||||
def read_config(self, config):
|
def read_config(self, config):
|
||||||
|
# TODO: We should separate better the email configuration from the notification
|
||||||
|
# and account validity config.
|
||||||
|
|
||||||
self.email_enable_notifs = False
|
self.email_enable_notifs = False
|
||||||
|
|
||||||
email_config = config.get("email", {})
|
email_config = config.get("email", {})
|
||||||
self.email_enable_notifs = email_config.get("enable_notifs", False)
|
|
||||||
|
|
||||||
if self.email_enable_notifs:
|
self.email_smtp_host = email_config.get("smtp_host", None)
|
||||||
|
self.email_smtp_port = email_config.get("smtp_port", None)
|
||||||
|
self.email_smtp_user = email_config.get("smtp_user", None)
|
||||||
|
self.email_smtp_pass = email_config.get("smtp_pass", None)
|
||||||
|
self.require_transport_security = email_config.get(
|
||||||
|
"require_transport_security", False
|
||||||
|
)
|
||||||
|
if "app_name" in email_config:
|
||||||
|
self.email_app_name = email_config["app_name"]
|
||||||
|
else:
|
||||||
|
self.email_app_name = "Matrix"
|
||||||
|
|
||||||
|
# TODO: Rename notif_from to something more generic, or have a separate
|
||||||
|
# from for password resets, message notifications, etc?
|
||||||
|
# Currently the email section is a bit bogged down with settings for
|
||||||
|
# multiple functions. Would be good to split it out into separate
|
||||||
|
# sections and only put the common ones under email:
|
||||||
|
self.email_notif_from = email_config.get("notif_from", None)
|
||||||
|
if self.email_notif_from is not None:
|
||||||
|
# make sure it's valid
|
||||||
|
parsed = email.utils.parseaddr(self.email_notif_from)
|
||||||
|
if parsed[1] == '':
|
||||||
|
raise RuntimeError("Invalid notif_from address")
|
||||||
|
|
||||||
|
template_dir = email_config.get("template_dir")
|
||||||
|
# we need an absolute path, because we change directory after starting (and
|
||||||
|
# we don't yet know what auxilliary templates like mail.css we will need).
|
||||||
|
# (Note that loading as package_resources with jinja.PackageLoader doesn't
|
||||||
|
# work for the same reason.)
|
||||||
|
if not template_dir:
|
||||||
|
template_dir = pkg_resources.resource_filename(
|
||||||
|
'synapse', 'res/templates'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.email_template_dir = os.path.abspath(template_dir)
|
||||||
|
|
||||||
|
self.email_enable_notifs = email_config.get("enable_notifs", False)
|
||||||
|
account_validity_renewal_enabled = config.get(
|
||||||
|
"account_validity", {},
|
||||||
|
).get("renew_at")
|
||||||
|
|
||||||
|
email_trust_identity_server_for_password_resets = email_config.get(
|
||||||
|
"trust_identity_server_for_password_resets", False,
|
||||||
|
)
|
||||||
|
self.email_password_reset_behaviour = (
|
||||||
|
"remote" if email_trust_identity_server_for_password_resets else "local"
|
||||||
|
)
|
||||||
|
if self.email_password_reset_behaviour == "local" and email_config == {}:
|
||||||
|
logger.warn(
|
||||||
|
"User password resets have been disabled due to lack of email config"
|
||||||
|
)
|
||||||
|
self.email_password_reset_behaviour = "off"
|
||||||
|
|
||||||
|
# Get lifetime of a validation token in milliseconds
|
||||||
|
self.email_validation_token_lifetime = self.parse_duration(
|
||||||
|
email_config.get("validation_token_lifetime", "1h")
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.email_enable_notifs
|
||||||
|
or account_validity_renewal_enabled
|
||||||
|
or self.email_password_reset_behaviour == "local"
|
||||||
|
):
|
||||||
# make sure we can import the required deps
|
# make sure we can import the required deps
|
||||||
import jinja2
|
import jinja2
|
||||||
import bleach
|
import bleach
|
||||||
@ -42,6 +108,68 @@ class EmailConfig(Config):
|
|||||||
jinja2
|
jinja2
|
||||||
bleach
|
bleach
|
||||||
|
|
||||||
|
if self.email_password_reset_behaviour == "local":
|
||||||
|
required = [
|
||||||
|
"smtp_host",
|
||||||
|
"smtp_port",
|
||||||
|
"notif_from",
|
||||||
|
]
|
||||||
|
|
||||||
|
missing = []
|
||||||
|
for k in required:
|
||||||
|
if k not in email_config:
|
||||||
|
missing.append(k)
|
||||||
|
|
||||||
|
if (len(missing) > 0):
|
||||||
|
raise RuntimeError(
|
||||||
|
"email.password_reset_behaviour is set to 'local' "
|
||||||
|
"but required keys are missing: %s" %
|
||||||
|
(", ".join(["email." + k for k in missing]),)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Templates for password reset emails
|
||||||
|
self.email_password_reset_template_html = email_config.get(
|
||||||
|
"password_reset_template_html", "password_reset.html",
|
||||||
|
)
|
||||||
|
self.email_password_reset_template_text = email_config.get(
|
||||||
|
"password_reset_template_text", "password_reset.txt",
|
||||||
|
)
|
||||||
|
self.email_password_reset_failure_template = email_config.get(
|
||||||
|
"password_reset_failure_template", "password_reset_failure.html",
|
||||||
|
)
|
||||||
|
# This template does not support any replaceable variables, so we will
|
||||||
|
# read it from the disk once during setup
|
||||||
|
email_password_reset_success_template = email_config.get(
|
||||||
|
"password_reset_success_template", "password_reset_success.html",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check templates exist
|
||||||
|
for f in [self.email_password_reset_template_html,
|
||||||
|
self.email_password_reset_template_text,
|
||||||
|
self.email_password_reset_failure_template,
|
||||||
|
email_password_reset_success_template]:
|
||||||
|
p = os.path.join(self.email_template_dir, f)
|
||||||
|
if not os.path.isfile(p):
|
||||||
|
raise ConfigError("Unable to find template file %s" % (p, ))
|
||||||
|
|
||||||
|
# Retrieve content of web templates
|
||||||
|
filepath = os.path.join(
|
||||||
|
self.email_template_dir,
|
||||||
|
email_password_reset_success_template,
|
||||||
|
)
|
||||||
|
self.email_password_reset_success_html_content = self.read_file(
|
||||||
|
filepath,
|
||||||
|
"email.password_reset_template_success_html",
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.get("public_baseurl") is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"email.password_reset_behaviour is set to 'local' but no "
|
||||||
|
"public_baseurl is set. This is necessary to generate password "
|
||||||
|
"reset links"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.email_enable_notifs:
|
||||||
required = [
|
required = [
|
||||||
"smtp_host",
|
"smtp_host",
|
||||||
"smtp_port",
|
"smtp_port",
|
||||||
@ -66,34 +194,13 @@ class EmailConfig(Config):
|
|||||||
"email.enable_notifs is True but no public_baseurl is set"
|
"email.enable_notifs is True but no public_baseurl is set"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.email_smtp_host = email_config["smtp_host"]
|
|
||||||
self.email_smtp_port = email_config["smtp_port"]
|
|
||||||
self.email_notif_from = email_config["notif_from"]
|
|
||||||
self.email_notif_template_html = email_config["notif_template_html"]
|
self.email_notif_template_html = email_config["notif_template_html"]
|
||||||
self.email_notif_template_text = email_config["notif_template_text"]
|
self.email_notif_template_text = email_config["notif_template_text"]
|
||||||
self.email_expiry_template_html = email_config.get(
|
|
||||||
"expiry_template_html", "notice_expiry.html",
|
|
||||||
)
|
|
||||||
self.email_expiry_template_text = email_config.get(
|
|
||||||
"expiry_template_text", "notice_expiry.txt",
|
|
||||||
)
|
|
||||||
|
|
||||||
template_dir = email_config.get("template_dir")
|
|
||||||
# we need an absolute path, because we change directory after starting (and
|
|
||||||
# we don't yet know what auxilliary templates like mail.css we will need).
|
|
||||||
# (Note that loading as package_resources with jinja.PackageLoader doesn't
|
|
||||||
# work for the same reason.)
|
|
||||||
if not template_dir:
|
|
||||||
template_dir = pkg_resources.resource_filename(
|
|
||||||
'synapse', 'res/templates'
|
|
||||||
)
|
|
||||||
template_dir = os.path.abspath(template_dir)
|
|
||||||
|
|
||||||
for f in self.email_notif_template_text, self.email_notif_template_html:
|
for f in self.email_notif_template_text, self.email_notif_template_html:
|
||||||
p = os.path.join(template_dir, f)
|
p = os.path.join(self.email_template_dir, f)
|
||||||
if not os.path.isfile(p):
|
if not os.path.isfile(p):
|
||||||
raise ConfigError("Unable to find email template file %s" % (p, ))
|
raise ConfigError("Unable to find email template file %s" % (p, ))
|
||||||
self.email_template_dir = template_dir
|
|
||||||
|
|
||||||
self.email_notif_for_new_users = email_config.get(
|
self.email_notif_for_new_users = email_config.get(
|
||||||
"notif_for_new_users", True
|
"notif_for_new_users", True
|
||||||
@ -101,35 +208,24 @@ class EmailConfig(Config):
|
|||||||
self.email_riot_base_url = email_config.get(
|
self.email_riot_base_url = email_config.get(
|
||||||
"riot_base_url", None
|
"riot_base_url", None
|
||||||
)
|
)
|
||||||
self.email_smtp_user = email_config.get(
|
|
||||||
"smtp_user", None
|
|
||||||
)
|
|
||||||
self.email_smtp_pass = email_config.get(
|
|
||||||
"smtp_pass", None
|
|
||||||
)
|
|
||||||
self.require_transport_security = email_config.get(
|
|
||||||
"require_transport_security", False
|
|
||||||
)
|
|
||||||
if "app_name" in email_config:
|
|
||||||
self.email_app_name = email_config["app_name"]
|
|
||||||
else:
|
|
||||||
self.email_app_name = "Matrix"
|
|
||||||
|
|
||||||
# make sure it's valid
|
if account_validity_renewal_enabled:
|
||||||
parsed = email.utils.parseaddr(self.email_notif_from)
|
self.email_expiry_template_html = email_config.get(
|
||||||
if parsed[1] == '':
|
"expiry_template_html", "notice_expiry.html",
|
||||||
raise RuntimeError("Invalid notif_from address")
|
)
|
||||||
else:
|
self.email_expiry_template_text = email_config.get(
|
||||||
self.email_enable_notifs = False
|
"expiry_template_text", "notice_expiry.txt",
|
||||||
# Not much point setting defaults for the rest: it would be an
|
)
|
||||||
# error for them to be used.
|
|
||||||
|
for f in self.email_expiry_template_text, self.email_expiry_template_html:
|
||||||
|
p = os.path.join(self.email_template_dir, f)
|
||||||
|
if not os.path.isfile(p):
|
||||||
|
raise ConfigError("Unable to find email template file %s" % (p, ))
|
||||||
|
|
||||||
def default_config(self, config_dir_path, server_name, **kwargs):
|
def default_config(self, config_dir_path, server_name, **kwargs):
|
||||||
return """
|
return """
|
||||||
# Enable sending emails for notification events or expiry notices
|
# Enable sending emails for password resets, notification events or
|
||||||
# Defining a custom URL for Riot is only needed if email notifications
|
# account expiry notices
|
||||||
# should contain links to a self-hosted installation of Riot; when set
|
|
||||||
# the "app_name" setting is ignored.
|
|
||||||
#
|
#
|
||||||
# If your SMTP server requires authentication, the optional smtp_user &
|
# If your SMTP server requires authentication, the optional smtp_user &
|
||||||
# smtp_pass variables should be used
|
# smtp_pass variables should be used
|
||||||
@ -137,20 +233,62 @@ class EmailConfig(Config):
|
|||||||
#email:
|
#email:
|
||||||
# enable_notifs: false
|
# enable_notifs: false
|
||||||
# smtp_host: "localhost"
|
# smtp_host: "localhost"
|
||||||
# smtp_port: 25
|
# smtp_port: 25 # SSL: 465, STARTTLS: 587
|
||||||
# smtp_user: "exampleusername"
|
# smtp_user: "exampleusername"
|
||||||
# smtp_pass: "examplepassword"
|
# smtp_pass: "examplepassword"
|
||||||
# require_transport_security: False
|
# require_transport_security: False
|
||||||
# notif_from: "Your Friendly %(app)s Home Server <noreply@example.com>"
|
# notif_from: "Your Friendly %(app)s Home Server <noreply@example.com>"
|
||||||
# app_name: Matrix
|
# app_name: Matrix
|
||||||
# # if template_dir is unset, uses the example templates that are part of
|
#
|
||||||
# # the Synapse distribution.
|
# # Enable email notifications by default
|
||||||
|
# notif_for_new_users: True
|
||||||
|
#
|
||||||
|
# # Defining a custom URL for Riot is only needed if email notifications
|
||||||
|
# # should contain links to a self-hosted installation of Riot; when set
|
||||||
|
# # the "app_name" setting is ignored
|
||||||
|
# riot_base_url: "http://localhost/riot"
|
||||||
|
#
|
||||||
|
# # Enable sending password reset emails via the configured, trusted
|
||||||
|
# # identity servers
|
||||||
|
# #
|
||||||
|
# # IMPORTANT! This will give a malicious or overtaken identity server
|
||||||
|
# # the ability to reset passwords for your users! Make absolutely sure
|
||||||
|
# # that you want to do this! It is strongly recommended that password
|
||||||
|
# # reset emails be sent by the homeserver instead
|
||||||
|
# #
|
||||||
|
# # If this option is set to false and SMTP options have not been
|
||||||
|
# # configured, resetting user passwords via email will be disabled
|
||||||
|
# #trust_identity_server_for_password_resets: false
|
||||||
|
#
|
||||||
|
# # Configure the time that a validation email or text message code
|
||||||
|
# # will expire after sending
|
||||||
|
# #
|
||||||
|
# # This is currently used for password resets
|
||||||
|
# #validation_token_lifetime: 1h
|
||||||
|
#
|
||||||
|
# # Template directory. All template files should be stored within this
|
||||||
|
# # directory
|
||||||
|
# #
|
||||||
# #template_dir: res/templates
|
# #template_dir: res/templates
|
||||||
|
#
|
||||||
|
# # Templates for email notifications
|
||||||
|
# #
|
||||||
# notif_template_html: notif_mail.html
|
# notif_template_html: notif_mail.html
|
||||||
# notif_template_text: notif_mail.txt
|
# notif_template_text: notif_mail.txt
|
||||||
# # Templates for account expiry notices.
|
#
|
||||||
|
# # Templates for account expiry notices
|
||||||
|
# #
|
||||||
# expiry_template_html: notice_expiry.html
|
# expiry_template_html: notice_expiry.html
|
||||||
# expiry_template_text: notice_expiry.txt
|
# expiry_template_text: notice_expiry.txt
|
||||||
# notif_for_new_users: True
|
#
|
||||||
# riot_base_url: "http://localhost/riot"
|
# # Templates for password reset emails sent by the homeserver
|
||||||
|
# #
|
||||||
|
# #password_reset_template_html: password_reset.html
|
||||||
|
# #password_reset_template_text: password_reset.txt
|
||||||
|
#
|
||||||
|
# # Templates for password reset success and failure pages that a user
|
||||||
|
# # will see after attempting to reset their password
|
||||||
|
# #
|
||||||
|
# #password_reset_template_success_html: password_reset_success.html
|
||||||
|
# #password_reset_template_failure_html: password_reset_failure.html
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -17,6 +18,8 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import attr
|
||||||
|
import jsonschema
|
||||||
from signedjson.key import (
|
from signedjson.key import (
|
||||||
NACL_ED25519,
|
NACL_ED25519,
|
||||||
decode_signing_key_base64,
|
decode_signing_key_base64,
|
||||||
@ -32,11 +35,36 @@ from synapse.util.stringutils import random_string, random_string_with_symbols
|
|||||||
|
|
||||||
from ._base import Config, ConfigError
|
from ._base import Config, ConfigError
|
||||||
|
|
||||||
|
INSECURE_NOTARY_ERROR = """\
|
||||||
|
Your server is configured to accept key server responses without signature
|
||||||
|
validation or TLS certificate validation. This is likely to be very insecure. If
|
||||||
|
you are *sure* you want to do this, set 'accept_keys_insecurely' on the
|
||||||
|
keyserver configuration."""
|
||||||
|
|
||||||
|
RELYING_ON_MATRIX_KEY_ERROR = """\
|
||||||
|
Your server is configured to accept key server responses without TLS certificate
|
||||||
|
validation, and which are only signed by the old (possibly compromised)
|
||||||
|
matrix.org signing key 'ed25519:auto'. This likely isn't what you want to do,
|
||||||
|
and you should enable 'federation_verify_certificates' in your configuration.
|
||||||
|
|
||||||
|
If you are *sure* you want to do this, set 'accept_keys_insecurely' on the
|
||||||
|
trusted_key_server configuration."""
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class KeyConfig(Config):
|
@attr.s
|
||||||
|
class TrustedKeyServer(object):
|
||||||
|
# string: name of the server.
|
||||||
|
server_name = attr.ib()
|
||||||
|
|
||||||
|
# dict[str,VerifyKey]|None: map from key id to key object, or None to disable
|
||||||
|
# signature verification.
|
||||||
|
verify_keys = attr.ib(default=None)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyConfig(Config):
|
||||||
def read_config(self, config):
|
def read_config(self, config):
|
||||||
# the signing key can be specified inline or in a separate file
|
# the signing key can be specified inline or in a separate file
|
||||||
if "signing_key" in config:
|
if "signing_key" in config:
|
||||||
@ -49,16 +77,27 @@ class KeyConfig(Config):
|
|||||||
config.get("old_signing_keys", {})
|
config.get("old_signing_keys", {})
|
||||||
)
|
)
|
||||||
self.key_refresh_interval = self.parse_duration(
|
self.key_refresh_interval = self.parse_duration(
|
||||||
config.get("key_refresh_interval", "1d"),
|
config.get("key_refresh_interval", "1d")
|
||||||
)
|
)
|
||||||
self.perspectives = self.read_perspectives(
|
|
||||||
config.get("perspectives", {}).get("servers", {
|
# if neither trusted_key_servers nor perspectives are given, use the default.
|
||||||
"matrix.org": {"verify_keys": {
|
if "perspectives" not in config and "trusted_key_servers" not in config:
|
||||||
"ed25519:auto": {
|
key_servers = [{"server_name": "matrix.org"}]
|
||||||
"key": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
|
else:
|
||||||
}
|
key_servers = config.get("trusted_key_servers", [])
|
||||||
}}
|
|
||||||
})
|
if not isinstance(key_servers, list):
|
||||||
|
raise ConfigError(
|
||||||
|
"trusted_key_servers, if given, must be a list, not a %s"
|
||||||
|
% (type(key_servers).__name__,)
|
||||||
|
)
|
||||||
|
|
||||||
|
# merge the 'perspectives' config into the 'trusted_key_servers' config.
|
||||||
|
key_servers.extend(_perspectives_to_key_servers(config))
|
||||||
|
|
||||||
|
# list of TrustedKeyServer objects
|
||||||
|
self.key_servers = list(
|
||||||
|
_parse_key_servers(key_servers, self.federation_verify_certificates)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.macaroon_secret_key = config.get(
|
self.macaroon_secret_key = config.get(
|
||||||
@ -78,8 +117,9 @@ class KeyConfig(Config):
|
|||||||
# falsification of values
|
# falsification of values
|
||||||
self.form_secret = config.get("form_secret", None)
|
self.form_secret = config.get("form_secret", None)
|
||||||
|
|
||||||
def default_config(self, config_dir_path, server_name, generate_secrets=False,
|
def default_config(
|
||||||
**kwargs):
|
self, config_dir_path, server_name, generate_secrets=False, **kwargs
|
||||||
|
):
|
||||||
base_key_name = os.path.join(config_dir_path, server_name)
|
base_key_name = os.path.join(config_dir_path, server_name)
|
||||||
|
|
||||||
if generate_secrets:
|
if generate_secrets:
|
||||||
@ -91,7 +131,8 @@ class KeyConfig(Config):
|
|||||||
macaroon_secret_key = "# macaroon_secret_key: <PRIVATE STRING>"
|
macaroon_secret_key = "# macaroon_secret_key: <PRIVATE STRING>"
|
||||||
form_secret = "# form_secret: <PRIVATE STRING>"
|
form_secret = "# form_secret: <PRIVATE STRING>"
|
||||||
|
|
||||||
return """\
|
return (
|
||||||
|
"""\
|
||||||
# a secret which is used to sign access tokens. If none is specified,
|
# a secret which is used to sign access tokens. If none is specified,
|
||||||
# the registration_shared_secret is used, if one is given; otherwise,
|
# the registration_shared_secret is used, if one is given; otherwise,
|
||||||
# a secret key is derived from the signing key.
|
# a secret key is derived from the signing key.
|
||||||
@ -133,33 +174,53 @@ class KeyConfig(Config):
|
|||||||
|
|
||||||
# The trusted servers to download signing keys from.
|
# The trusted servers to download signing keys from.
|
||||||
#
|
#
|
||||||
#perspectives:
|
# When we need to fetch a signing key, each server is tried in parallel.
|
||||||
# servers:
|
#
|
||||||
# "matrix.org":
|
# Normally, the connection to the key server is validated via TLS certificates.
|
||||||
# verify_keys:
|
# Additional security can be provided by configuring a `verify key`, which
|
||||||
# "ed25519:auto":
|
# will make synapse check that the response is signed by that key.
|
||||||
# key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
|
#
|
||||||
""" % locals()
|
# This setting supercedes an older setting named `perspectives`. The old format
|
||||||
|
# is still supported for backwards-compatibility, but it is deprecated.
|
||||||
def read_perspectives(self, perspectives_servers):
|
#
|
||||||
servers = {}
|
# Options for each entry in the list include:
|
||||||
for server_name, server_config in perspectives_servers.items():
|
#
|
||||||
for key_id, key_data in server_config["verify_keys"].items():
|
# server_name: the name of the server. required.
|
||||||
if is_signing_algorithm_supported(key_id):
|
#
|
||||||
key_base64 = key_data["key"]
|
# verify_keys: an optional map from key id to base64-encoded public key.
|
||||||
key_bytes = decode_base64(key_base64)
|
# If specified, we will check that the response is signed by at least
|
||||||
verify_key = decode_verify_key_bytes(key_id, key_bytes)
|
# one of the given keys.
|
||||||
servers.setdefault(server_name, {})[key_id] = verify_key
|
#
|
||||||
return servers
|
# accept_keys_insecurely: a boolean. Normally, if `verify_keys` is unset,
|
||||||
|
# and federation_verify_certificates is not `true`, synapse will refuse
|
||||||
|
# to start, because this would allow anyone who can spoof DNS responses
|
||||||
|
# to masquerade as the trusted key server. If you know what you are doing
|
||||||
|
# and are sure that your network environment provides a secure connection
|
||||||
|
# to the key server, you can set this to `true` to override this
|
||||||
|
# behaviour.
|
||||||
|
#
|
||||||
|
# An example configuration might look like:
|
||||||
|
#
|
||||||
|
#trusted_key_servers:
|
||||||
|
# - server_name: "my_trusted_server.example.com"
|
||||||
|
# verify_keys:
|
||||||
|
# "ed25519:auto": "abcdefghijklmnopqrstuvwxyzabcdefghijklmopqr"
|
||||||
|
# - server_name: "my_other_trusted_server.example.com"
|
||||||
|
#
|
||||||
|
# The default configuration is:
|
||||||
|
#
|
||||||
|
#trusted_key_servers:
|
||||||
|
# - server_name: "matrix.org"
|
||||||
|
"""
|
||||||
|
% locals()
|
||||||
|
)
|
||||||
|
|
||||||
def read_signing_key(self, signing_key_path):
|
def read_signing_key(self, signing_key_path):
|
||||||
signing_keys = self.read_file(signing_key_path, "signing_key")
|
signing_keys = self.read_file(signing_key_path, "signing_key")
|
||||||
try:
|
try:
|
||||||
return read_signing_keys(signing_keys.splitlines(True))
|
return read_signing_keys(signing_keys.splitlines(True))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ConfigError(
|
raise ConfigError("Error reading signing_key: %s" % (str(e)))
|
||||||
"Error reading signing_key: %s" % (str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_old_signing_keys(self, old_signing_keys):
|
def read_old_signing_keys(self, old_signing_keys):
|
||||||
keys = {}
|
keys = {}
|
||||||
@ -182,9 +243,7 @@ class KeyConfig(Config):
|
|||||||
if not self.path_exists(signing_key_path):
|
if not self.path_exists(signing_key_path):
|
||||||
with open(signing_key_path, "w") as signing_key_file:
|
with open(signing_key_path, "w") as signing_key_file:
|
||||||
key_id = "a_" + random_string(4)
|
key_id = "a_" + random_string(4)
|
||||||
write_signing_keys(
|
write_signing_keys(signing_key_file, (generate_signing_key(key_id),))
|
||||||
signing_key_file, (generate_signing_key(key_id),),
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
signing_keys = self.read_file(signing_key_path, "signing_key")
|
signing_keys = self.read_file(signing_key_path, "signing_key")
|
||||||
if len(signing_keys.split("\n")[0].split()) == 1:
|
if len(signing_keys.split("\n")[0].split()) == 1:
|
||||||
@ -194,6 +253,116 @@ class KeyConfig(Config):
|
|||||||
NACL_ED25519, key_id, signing_keys.split("\n")[0]
|
NACL_ED25519, key_id, signing_keys.split("\n")[0]
|
||||||
)
|
)
|
||||||
with open(signing_key_path, "w") as signing_key_file:
|
with open(signing_key_path, "w") as signing_key_file:
|
||||||
write_signing_keys(
|
write_signing_keys(signing_key_file, (key,))
|
||||||
signing_key_file, (key,),
|
|
||||||
|
|
||||||
|
def _perspectives_to_key_servers(config):
|
||||||
|
"""Convert old-style 'perspectives' configs into new-style 'trusted_key_servers'
|
||||||
|
|
||||||
|
Returns an iterable of entries to add to trusted_key_servers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 'perspectives' looks like:
|
||||||
|
#
|
||||||
|
# {
|
||||||
|
# "servers": {
|
||||||
|
# "matrix.org": {
|
||||||
|
# "verify_keys": {
|
||||||
|
# "ed25519:auto": {
|
||||||
|
# "key": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# 'trusted_keys' looks like:
|
||||||
|
#
|
||||||
|
# [
|
||||||
|
# {
|
||||||
|
# "server_name": "matrix.org",
|
||||||
|
# "verify_keys": {
|
||||||
|
# "ed25519:auto": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
|
||||||
|
perspectives_servers = config.get("perspectives", {}).get("servers", {})
|
||||||
|
|
||||||
|
for server_name, server_opts in perspectives_servers.items():
|
||||||
|
trusted_key_server_entry = {"server_name": server_name}
|
||||||
|
verify_keys = server_opts.get("verify_keys")
|
||||||
|
if verify_keys is not None:
|
||||||
|
trusted_key_server_entry["verify_keys"] = {
|
||||||
|
key_id: key_data["key"] for key_id, key_data in verify_keys.items()
|
||||||
|
}
|
||||||
|
yield trusted_key_server_entry
|
||||||
|
|
||||||
|
|
||||||
|
TRUSTED_KEY_SERVERS_SCHEMA = {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"description": "schema for the trusted_key_servers setting",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"server_name": {"type": "string"},
|
||||||
|
"verify_keys": {
|
||||||
|
"type": "object",
|
||||||
|
# each key must be a base64 string
|
||||||
|
"additionalProperties": {"type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["server_name"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_key_servers(key_servers, federation_verify_certificates):
|
||||||
|
try:
|
||||||
|
jsonschema.validate(key_servers, TRUSTED_KEY_SERVERS_SCHEMA)
|
||||||
|
except jsonschema.ValidationError as e:
|
||||||
|
raise ConfigError("Unable to parse 'trusted_key_servers': " + e.message)
|
||||||
|
|
||||||
|
for server in key_servers:
|
||||||
|
server_name = server["server_name"]
|
||||||
|
result = TrustedKeyServer(server_name=server_name)
|
||||||
|
|
||||||
|
verify_keys = server.get("verify_keys")
|
||||||
|
if verify_keys is not None:
|
||||||
|
result.verify_keys = {}
|
||||||
|
for key_id, key_base64 in verify_keys.items():
|
||||||
|
if not is_signing_algorithm_supported(key_id):
|
||||||
|
raise ConfigError(
|
||||||
|
"Unsupported signing algorithm on key %s for server %s in "
|
||||||
|
"trusted_key_servers" % (key_id, server_name)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
key_bytes = decode_base64(key_base64)
|
||||||
|
verify_key = decode_verify_key_bytes(key_id, key_bytes)
|
||||||
|
except Exception as e:
|
||||||
|
raise ConfigError(
|
||||||
|
"Unable to parse key %s for server %s in "
|
||||||
|
"trusted_key_servers: %s" % (key_id, server_name, e)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.verify_keys[key_id] = verify_key
|
||||||
|
|
||||||
|
if (
|
||||||
|
not federation_verify_certificates and
|
||||||
|
not server.get("accept_keys_insecurely")
|
||||||
|
):
|
||||||
|
_assert_keyserver_has_verify_keys(result)
|
||||||
|
|
||||||
|
yield result
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_keyserver_has_verify_keys(trusted_key_server):
|
||||||
|
if not trusted_key_server.verify_keys:
|
||||||
|
raise ConfigError(INSECURE_NOTARY_ERROR)
|
||||||
|
|
||||||
|
# also check that they are not blindly checking the old matrix.org key
|
||||||
|
if trusted_key_server.server_name == "matrix.org" and any(
|
||||||
|
key_id == "ed25519:auto" for key_id in trusted_key_server.verify_keys
|
||||||
|
):
|
||||||
|
raise ConfigError(RELYING_ON_MATRIX_KEY_ERROR)
|
||||||
|
@ -36,7 +36,7 @@ logger = logging.Logger(__name__)
|
|||||||
# in the list.
|
# in the list.
|
||||||
DEFAULT_BIND_ADDRESSES = ['::', '0.0.0.0']
|
DEFAULT_BIND_ADDRESSES = ['::', '0.0.0.0']
|
||||||
|
|
||||||
DEFAULT_ROOM_VERSION = "1"
|
DEFAULT_ROOM_VERSION = "4"
|
||||||
|
|
||||||
|
|
||||||
class ServerConfig(Config):
|
class ServerConfig(Config):
|
||||||
@ -585,6 +585,22 @@ class ServerConfig(Config):
|
|||||||
|
|
||||||
# Monthly Active User Blocking
|
# Monthly Active User Blocking
|
||||||
#
|
#
|
||||||
|
# Used in cases where the admin or server owner wants to limit to the
|
||||||
|
# number of monthly active users.
|
||||||
|
#
|
||||||
|
# 'limit_usage_by_mau' disables/enables monthly active user blocking. When
|
||||||
|
# anabled and a limit is reached the server returns a 'ResourceLimitError'
|
||||||
|
# with error type Codes.RESOURCE_LIMIT_EXCEEDED
|
||||||
|
#
|
||||||
|
# 'max_mau_value' is the hard limit of monthly active users above which
|
||||||
|
# the server will start blocking user actions.
|
||||||
|
#
|
||||||
|
# 'mau_trial_days' is a means to add a grace period for active users. It
|
||||||
|
# means that users must be active for this number of days before they
|
||||||
|
# can be considered active and guards against the case where lots of users
|
||||||
|
# sign up in a short space of time never to return after their initial
|
||||||
|
# session.
|
||||||
|
#
|
||||||
#limit_usage_by_mau: False
|
#limit_usage_by_mau: False
|
||||||
#max_mau_value: 50
|
#max_mau_value: 50
|
||||||
#mau_trial_days: 2
|
#mau_trial_days: 2
|
||||||
|
@ -74,7 +74,7 @@ class TlsConfig(Config):
|
|||||||
|
|
||||||
# Whether to verify certificates on outbound federation traffic
|
# Whether to verify certificates on outbound federation traffic
|
||||||
self.federation_verify_certificates = config.get(
|
self.federation_verify_certificates = config.get(
|
||||||
"federation_verify_certificates", False,
|
"federation_verify_certificates", True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Whitelist of domains to not verify certificates for
|
# Whitelist of domains to not verify certificates for
|
||||||
@ -107,7 +107,7 @@ class TlsConfig(Config):
|
|||||||
certs = []
|
certs = []
|
||||||
for ca_file in custom_ca_list:
|
for ca_file in custom_ca_list:
|
||||||
logger.debug("Reading custom CA certificate file: %s", ca_file)
|
logger.debug("Reading custom CA certificate file: %s", ca_file)
|
||||||
content = self.read_file(ca_file)
|
content = self.read_file(ca_file, "federation_custom_ca_list")
|
||||||
|
|
||||||
# Parse the CA certificates
|
# Parse the CA certificates
|
||||||
try:
|
try:
|
||||||
@ -241,12 +241,12 @@ class TlsConfig(Config):
|
|||||||
#
|
#
|
||||||
#tls_private_key_path: "%(tls_private_key_path)s"
|
#tls_private_key_path: "%(tls_private_key_path)s"
|
||||||
|
|
||||||
# Whether to verify TLS certificates when sending federation traffic.
|
# Whether to verify TLS server certificates for outbound federation requests.
|
||||||
#
|
#
|
||||||
# This currently defaults to `false`, however this will change in
|
# Defaults to `true`. To disable certificate verification, uncomment the
|
||||||
# Synapse 1.0 when valid federation certificates will be required.
|
# following line.
|
||||||
#
|
#
|
||||||
#federation_verify_certificates: true
|
#federation_verify_certificates: false
|
||||||
|
|
||||||
# Skip federation certificate verification on the following whitelist
|
# Skip federation certificate verification on the following whitelist
|
||||||
# of domains.
|
# of domains.
|
||||||
|
@ -15,10 +15,13 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import idna
|
||||||
|
from service_identity import VerificationError
|
||||||
|
from service_identity.pyopenssl import verify_hostname, verify_ip_address
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
|
||||||
from OpenSSL import SSL, crypto
|
from OpenSSL import SSL, crypto
|
||||||
from twisted.internet._sslverify import ClientTLSOptions, _defaultCurveName
|
from twisted.internet._sslverify import _defaultCurveName
|
||||||
from twisted.internet.abstract import isIPAddress, isIPv6Address
|
from twisted.internet.abstract import isIPAddress, isIPv6Address
|
||||||
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
||||||
from twisted.internet.ssl import CertificateOptions, ContextFactory, platformTrust
|
from twisted.internet.ssl import CertificateOptions, ContextFactory, platformTrust
|
||||||
@ -56,79 +59,19 @@ class ServerContextFactory(ContextFactory):
|
|||||||
return self._context
|
return self._context
|
||||||
|
|
||||||
|
|
||||||
def _idnaBytes(text):
|
|
||||||
"""
|
|
||||||
Convert some text typed by a human into some ASCII bytes. This is a
|
|
||||||
copy of twisted.internet._idna._idnaBytes. For documentation, see the
|
|
||||||
twisted documentation.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import idna
|
|
||||||
except ImportError:
|
|
||||||
return text.encode("idna")
|
|
||||||
else:
|
|
||||||
return idna.encode(text)
|
|
||||||
|
|
||||||
|
|
||||||
def _tolerateErrors(wrapped):
|
|
||||||
"""
|
|
||||||
Wrap up an info_callback for pyOpenSSL so that if something goes wrong
|
|
||||||
the error is immediately logged and the connection is dropped if possible.
|
|
||||||
This is a copy of twisted.internet._sslverify._tolerateErrors. For
|
|
||||||
documentation, see the twisted documentation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def infoCallback(connection, where, ret):
|
|
||||||
try:
|
|
||||||
return wrapped(connection, where, ret)
|
|
||||||
except: # noqa: E722, taken from the twisted implementation
|
|
||||||
f = Failure()
|
|
||||||
logger.exception("Error during info_callback")
|
|
||||||
connection.get_app_data().failVerification(f)
|
|
||||||
|
|
||||||
return infoCallback
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(IOpenSSLClientConnectionCreator)
|
|
||||||
class ClientTLSOptionsNoVerify(object):
|
|
||||||
"""
|
|
||||||
Client creator for TLS without certificate identity verification. This is a
|
|
||||||
copy of twisted.internet._sslverify.ClientTLSOptions with the identity
|
|
||||||
verification left out. For documentation, see the twisted documentation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, hostname, ctx):
|
|
||||||
self._ctx = ctx
|
|
||||||
|
|
||||||
if isIPAddress(hostname) or isIPv6Address(hostname):
|
|
||||||
self._hostnameBytes = hostname.encode('ascii')
|
|
||||||
self._sendSNI = False
|
|
||||||
else:
|
|
||||||
self._hostnameBytes = _idnaBytes(hostname)
|
|
||||||
self._sendSNI = True
|
|
||||||
|
|
||||||
ctx.set_info_callback(_tolerateErrors(self._identityVerifyingInfoCallback))
|
|
||||||
|
|
||||||
def clientConnectionForTLS(self, tlsProtocol):
|
|
||||||
context = self._ctx
|
|
||||||
connection = SSL.Connection(context, None)
|
|
||||||
connection.set_app_data(tlsProtocol)
|
|
||||||
return connection
|
|
||||||
|
|
||||||
def _identityVerifyingInfoCallback(self, connection, where, ret):
|
|
||||||
# Literal IPv4 and IPv6 addresses are not permitted
|
|
||||||
# as host names according to the RFCs
|
|
||||||
if where & SSL.SSL_CB_HANDSHAKE_START and self._sendSNI:
|
|
||||||
connection.set_tlsext_host_name(self._hostnameBytes)
|
|
||||||
|
|
||||||
|
|
||||||
class ClientTLSOptionsFactory(object):
|
class ClientTLSOptionsFactory(object):
|
||||||
"""Factory for Twisted ClientTLSOptions that are used to make connections
|
"""Factory for Twisted SSLClientConnectionCreators that are used to make connections
|
||||||
to remote servers for federation."""
|
to remote servers for federation.
|
||||||
|
|
||||||
|
Uses one of two OpenSSL context objects for all connections, depending on whether
|
||||||
|
we should do SSL certificate verification.
|
||||||
|
|
||||||
|
get_options decides whether we should do SSL certificate verification and
|
||||||
|
constructs an SSLClientConnectionCreator factory accordingly.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self._config = config
|
self._config = config
|
||||||
self._options_noverify = CertificateOptions()
|
|
||||||
|
|
||||||
# Check if we're using a custom list of a CA certificates
|
# Check if we're using a custom list of a CA certificates
|
||||||
trust_root = config.federation_ca_trust_root
|
trust_root = config.federation_ca_trust_root
|
||||||
@ -136,11 +79,13 @@ class ClientTLSOptionsFactory(object):
|
|||||||
# Use CA root certs provided by OpenSSL
|
# Use CA root certs provided by OpenSSL
|
||||||
trust_root = platformTrust()
|
trust_root = platformTrust()
|
||||||
|
|
||||||
self._options_verify = CertificateOptions(trustRoot=trust_root)
|
self._verify_ssl_context = CertificateOptions(trustRoot=trust_root).getContext()
|
||||||
|
self._verify_ssl_context.set_info_callback(self._context_info_cb)
|
||||||
|
|
||||||
|
self._no_verify_ssl_context = CertificateOptions().getContext()
|
||||||
|
self._no_verify_ssl_context.set_info_callback(self._context_info_cb)
|
||||||
|
|
||||||
def get_options(self, host):
|
def get_options(self, host):
|
||||||
# Use _makeContext so that we get a fresh OpenSSL CTX each time.
|
|
||||||
|
|
||||||
# Check if certificate verification has been enabled
|
# Check if certificate verification has been enabled
|
||||||
should_verify = self._config.federation_verify_certificates
|
should_verify = self._config.federation_verify_certificates
|
||||||
|
|
||||||
@ -151,6 +96,93 @@ class ClientTLSOptionsFactory(object):
|
|||||||
should_verify = False
|
should_verify = False
|
||||||
break
|
break
|
||||||
|
|
||||||
if should_verify:
|
ssl_context = (
|
||||||
return ClientTLSOptions(host, self._options_verify._makeContext())
|
self._verify_ssl_context if should_verify else self._no_verify_ssl_context
|
||||||
return ClientTLSOptionsNoVerify(host, self._options_noverify._makeContext())
|
)
|
||||||
|
|
||||||
|
return SSLClientConnectionCreator(host, ssl_context, should_verify)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _context_info_cb(ssl_connection, where, ret):
|
||||||
|
"""The 'information callback' for our openssl context object."""
|
||||||
|
# we assume that the app_data on the connection object has been set to
|
||||||
|
# a TLSMemoryBIOProtocol object. (This is done by SSLClientConnectionCreator)
|
||||||
|
tls_protocol = ssl_connection.get_app_data()
|
||||||
|
try:
|
||||||
|
# ... we further assume that SSLClientConnectionCreator has set the
|
||||||
|
# '_synapse_tls_verifier' attribute to a ConnectionVerifier object.
|
||||||
|
tls_protocol._synapse_tls_verifier.verify_context_info_cb(
|
||||||
|
ssl_connection, where
|
||||||
|
)
|
||||||
|
except: # noqa: E722, taken from the twisted implementation
|
||||||
|
logger.exception("Error during info_callback")
|
||||||
|
f = Failure()
|
||||||
|
tls_protocol.failVerification(f)
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IOpenSSLClientConnectionCreator)
|
||||||
|
class SSLClientConnectionCreator(object):
|
||||||
|
"""Creates openssl connection objects for client connections.
|
||||||
|
|
||||||
|
Replaces twisted.internet.ssl.ClientTLSOptions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, hostname, ctx, verify_certs):
|
||||||
|
self._ctx = ctx
|
||||||
|
self._verifier = ConnectionVerifier(hostname, verify_certs)
|
||||||
|
|
||||||
|
def clientConnectionForTLS(self, tls_protocol):
|
||||||
|
context = self._ctx
|
||||||
|
connection = SSL.Connection(context, None)
|
||||||
|
|
||||||
|
# as per twisted.internet.ssl.ClientTLSOptions, we set the application
|
||||||
|
# data to our TLSMemoryBIOProtocol...
|
||||||
|
connection.set_app_data(tls_protocol)
|
||||||
|
|
||||||
|
# ... and we also gut-wrench a '_synapse_tls_verifier' attribute into the
|
||||||
|
# tls_protocol so that the SSL context's info callback has something to
|
||||||
|
# call to do the cert verification.
|
||||||
|
setattr(tls_protocol, "_synapse_tls_verifier", self._verifier)
|
||||||
|
return connection
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionVerifier(object):
|
||||||
|
"""Set the SNI, and do cert verification
|
||||||
|
|
||||||
|
This is a thing which is attached to the TLSMemoryBIOProtocol, and is called by
|
||||||
|
the ssl context's info callback.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This code is based on twisted.internet.ssl.ClientTLSOptions.
|
||||||
|
|
||||||
|
def __init__(self, hostname, verify_certs):
|
||||||
|
self._verify_certs = verify_certs
|
||||||
|
|
||||||
|
if isIPAddress(hostname) or isIPv6Address(hostname):
|
||||||
|
self._hostnameBytes = hostname.encode("ascii")
|
||||||
|
self._is_ip_address = True
|
||||||
|
else:
|
||||||
|
# twisted's ClientTLSOptions falls back to the stdlib impl here if
|
||||||
|
# idna is not installed, but points out that lacks support for
|
||||||
|
# IDNA2008 (http://bugs.python.org/issue17305).
|
||||||
|
#
|
||||||
|
# We can rely on having idna.
|
||||||
|
self._hostnameBytes = idna.encode(hostname)
|
||||||
|
self._is_ip_address = False
|
||||||
|
|
||||||
|
self._hostnameASCII = self._hostnameBytes.decode("ascii")
|
||||||
|
|
||||||
|
def verify_context_info_cb(self, ssl_connection, where):
|
||||||
|
if where & SSL.SSL_CB_HANDSHAKE_START and not self._is_ip_address:
|
||||||
|
ssl_connection.set_tlsext_host_name(self._hostnameBytes)
|
||||||
|
|
||||||
|
if where & SSL.SSL_CB_HANDSHAKE_DONE and self._verify_certs:
|
||||||
|
try:
|
||||||
|
if self._is_ip_address:
|
||||||
|
verify_ip_address(ssl_connection, self._hostnameASCII)
|
||||||
|
else:
|
||||||
|
verify_hostname(ssl_connection, self._hostnameASCII)
|
||||||
|
except VerificationError:
|
||||||
|
f = Failure()
|
||||||
|
tls_protocol = ssl_connection.get_app_data()
|
||||||
|
tls_protocol.failVerification(f)
|
||||||
|
@ -31,7 +31,11 @@ logger = logging.getLogger(__name__)
|
|||||||
def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
||||||
"""Check whether the hash for this PDU matches the contents"""
|
"""Check whether the hash for this PDU matches the contents"""
|
||||||
name, expected_hash = compute_content_hash(event.get_pdu_json(), hash_algorithm)
|
name, expected_hash = compute_content_hash(event.get_pdu_json(), hash_algorithm)
|
||||||
logger.debug("Expecting hash: %s", encode_base64(expected_hash))
|
logger.debug(
|
||||||
|
"Verifying content hash on %s (expecting: %s)",
|
||||||
|
event.event_id,
|
||||||
|
encode_base64(expected_hash),
|
||||||
|
)
|
||||||
|
|
||||||
# some malformed events lack a 'hashes'. Protect against it being missing
|
# some malformed events lack a 'hashes'. Protect against it being missing
|
||||||
# or a weird type by basically treating it the same as an unhashed event.
|
# or a weird type by basically treating it the same as an unhashed event.
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from six import raise_from
|
from six import raise_from
|
||||||
@ -45,6 +46,7 @@ from synapse.api.errors import (
|
|||||||
)
|
)
|
||||||
from synapse.storage.keys import FetchKeyResult
|
from synapse.storage.keys import FetchKeyResult
|
||||||
from synapse.util import logcontext, unwrapFirstError
|
from synapse.util import logcontext, unwrapFirstError
|
||||||
|
from synapse.util.async_helpers import yieldable_gather_results
|
||||||
from synapse.util.logcontext import (
|
from synapse.util.logcontext import (
|
||||||
LoggingContext,
|
LoggingContext,
|
||||||
PreserveLoggingContext,
|
PreserveLoggingContext,
|
||||||
@ -58,9 +60,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True, cmp=False)
|
@attr.s(slots=True, cmp=False)
|
||||||
class VerifyKeyRequest(object):
|
class VerifyJsonRequest(object):
|
||||||
"""
|
"""
|
||||||
A request for a verify key to verify a JSON object.
|
A request to verify a JSON object.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
server_name(str): The name of the server to verify against.
|
server_name(str): The name of the server to verify against.
|
||||||
@ -70,7 +72,10 @@ class VerifyKeyRequest(object):
|
|||||||
|
|
||||||
json_object(dict): The JSON object to verify.
|
json_object(dict): The JSON object to verify.
|
||||||
|
|
||||||
deferred(Deferred[str, str, nacl.signing.VerifyKey]):
|
minimum_valid_until_ts (int): time at which we require the signing key to
|
||||||
|
be valid. (0 implies we don't care)
|
||||||
|
|
||||||
|
key_ready (Deferred[str, str, nacl.signing.VerifyKey]):
|
||||||
A deferred (server_name, key_id, verify_key) tuple that resolves when
|
A deferred (server_name, key_id, verify_key) tuple that resolves when
|
||||||
a verify key has been fetched. The deferreds' callbacks are run with no
|
a verify key has been fetched. The deferreds' callbacks are run with no
|
||||||
logcontext.
|
logcontext.
|
||||||
@ -80,9 +85,14 @@ class VerifyKeyRequest(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
server_name = attr.ib()
|
server_name = attr.ib()
|
||||||
key_ids = attr.ib()
|
|
||||||
json_object = attr.ib()
|
json_object = attr.ib()
|
||||||
deferred = attr.ib()
|
minimum_valid_until_ts = attr.ib()
|
||||||
|
request_name = attr.ib()
|
||||||
|
key_ids = attr.ib(init=False)
|
||||||
|
key_ready = attr.ib(default=attr.Factory(defer.Deferred))
|
||||||
|
|
||||||
|
def __attrs_post_init__(self):
|
||||||
|
self.key_ids = signature_ids(self.json_object, self.server_name)
|
||||||
|
|
||||||
|
|
||||||
class KeyLookupError(ValueError):
|
class KeyLookupError(ValueError):
|
||||||
@ -90,14 +100,16 @@ class KeyLookupError(ValueError):
|
|||||||
|
|
||||||
|
|
||||||
class Keyring(object):
|
class Keyring(object):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs, key_fetchers=None):
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
self._key_fetchers = (
|
if key_fetchers is None:
|
||||||
StoreKeyFetcher(hs),
|
key_fetchers = (
|
||||||
PerspectivesKeyFetcher(hs),
|
StoreKeyFetcher(hs),
|
||||||
ServerKeyFetcher(hs),
|
PerspectivesKeyFetcher(hs),
|
||||||
)
|
ServerKeyFetcher(hs),
|
||||||
|
)
|
||||||
|
self._key_fetchers = key_fetchers
|
||||||
|
|
||||||
# map from server name to Deferred. Has an entry for each server with
|
# map from server name to Deferred. Has an entry for each server with
|
||||||
# an ongoing key download; the Deferred completes once the download
|
# an ongoing key download; the Deferred completes once the download
|
||||||
@ -106,51 +118,99 @@ class Keyring(object):
|
|||||||
# These are regular, logcontext-agnostic Deferreds.
|
# These are regular, logcontext-agnostic Deferreds.
|
||||||
self.key_downloads = {}
|
self.key_downloads = {}
|
||||||
|
|
||||||
def verify_json_for_server(self, server_name, json_object):
|
def verify_json_for_server(
|
||||||
return logcontext.make_deferred_yieldable(
|
self, server_name, json_object, validity_time, request_name
|
||||||
self.verify_json_objects_for_server([(server_name, json_object)])[0]
|
):
|
||||||
)
|
"""Verify that a JSON object has been signed by a given server
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_name (str): name of the server which must have signed this object
|
||||||
|
|
||||||
|
json_object (dict): object to be checked
|
||||||
|
|
||||||
|
validity_time (int): timestamp at which we require the signing key to
|
||||||
|
be valid. (0 implies we don't care)
|
||||||
|
|
||||||
|
request_name (str): an identifier for this json object (eg, an event id)
|
||||||
|
for logging.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[None]: completes if the the object was correctly signed, otherwise
|
||||||
|
errbacks with an error
|
||||||
|
"""
|
||||||
|
req = VerifyJsonRequest(server_name, json_object, validity_time, request_name)
|
||||||
|
requests = (req,)
|
||||||
|
return logcontext.make_deferred_yieldable(self._verify_objects(requests)[0])
|
||||||
|
|
||||||
def verify_json_objects_for_server(self, server_and_json):
|
def verify_json_objects_for_server(self, server_and_json):
|
||||||
"""Bulk verifies signatures of json objects, bulk fetching keys as
|
"""Bulk verifies signatures of json objects, bulk fetching keys as
|
||||||
necessary.
|
necessary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
server_and_json (list): List of pairs of (server_name, json_object)
|
server_and_json (iterable[Tuple[str, dict, int, str]):
|
||||||
|
Iterable of (server_name, json_object, validity_time, request_name)
|
||||||
|
tuples.
|
||||||
|
|
||||||
|
validity_time is a timestamp at which the signing key must be
|
||||||
|
valid.
|
||||||
|
|
||||||
|
request_name is an identifier for this json object (eg, an event id)
|
||||||
|
for logging.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List<Deferred>: for each input pair, a deferred indicating success
|
List<Deferred[None]>: for each input triplet, a deferred indicating success
|
||||||
or failure to verify each json object's signature for the given
|
or failure to verify each json object's signature for the given
|
||||||
server_name. The deferreds run their callbacks in the sentinel
|
server_name. The deferreds run their callbacks in the sentinel
|
||||||
logcontext.
|
logcontext.
|
||||||
"""
|
"""
|
||||||
# a list of VerifyKeyRequests
|
return self._verify_objects(
|
||||||
verify_requests = []
|
VerifyJsonRequest(server_name, json_object, validity_time, request_name)
|
||||||
|
for server_name, json_object, validity_time, request_name in server_and_json
|
||||||
|
)
|
||||||
|
|
||||||
|
def _verify_objects(self, verify_requests):
|
||||||
|
"""Does the work of verify_json_[objects_]for_server
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
verify_requests (iterable[VerifyJsonRequest]):
|
||||||
|
Iterable of verification requests.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List<Deferred[None]>: for each input item, a deferred indicating success
|
||||||
|
or failure to verify each json object's signature for the given
|
||||||
|
server_name. The deferreds run their callbacks in the sentinel
|
||||||
|
logcontext.
|
||||||
|
"""
|
||||||
|
# a list of VerifyJsonRequests which are awaiting a key lookup
|
||||||
|
key_lookups = []
|
||||||
handle = preserve_fn(_handle_key_deferred)
|
handle = preserve_fn(_handle_key_deferred)
|
||||||
|
|
||||||
def process(server_name, json_object):
|
def process(verify_request):
|
||||||
"""Process an entry in the request list
|
"""Process an entry in the request list
|
||||||
|
|
||||||
Given a (server_name, json_object) pair from the request list,
|
Adds a key request to key_lookups, and returns a deferred which
|
||||||
adds a key request to verify_requests, and returns a deferred which will
|
will complete or fail (in the sentinel context) when verification completes.
|
||||||
complete or fail (in the sentinel context) when verification completes.
|
|
||||||
"""
|
"""
|
||||||
key_ids = signature_ids(json_object, server_name)
|
if not verify_request.key_ids:
|
||||||
|
|
||||||
if not key_ids:
|
|
||||||
return defer.fail(
|
return defer.fail(
|
||||||
SynapseError(
|
SynapseError(
|
||||||
400, "Not signed by %s" % (server_name,), Codes.UNAUTHORIZED
|
400,
|
||||||
|
"Not signed by %s" % (verify_request.server_name,),
|
||||||
|
Codes.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Verifying for %s with key_ids %s", server_name, key_ids)
|
logger.debug(
|
||||||
|
"Verifying %s for %s with key_ids %s, min_validity %i",
|
||||||
|
verify_request.request_name,
|
||||||
|
verify_request.server_name,
|
||||||
|
verify_request.key_ids,
|
||||||
|
verify_request.minimum_valid_until_ts,
|
||||||
|
)
|
||||||
|
|
||||||
# add the key request to the queue, but don't start it off yet.
|
# add the key request to the queue, but don't start it off yet.
|
||||||
verify_request = VerifyKeyRequest(
|
key_lookups.append(verify_request)
|
||||||
server_name, key_ids, json_object, defer.Deferred()
|
|
||||||
)
|
|
||||||
verify_requests.append(verify_request)
|
|
||||||
|
|
||||||
# now run _handle_key_deferred, which will wait for the key request
|
# now run _handle_key_deferred, which will wait for the key request
|
||||||
# to complete and then do the verification.
|
# to complete and then do the verification.
|
||||||
@ -159,13 +219,10 @@ class Keyring(object):
|
|||||||
# wrap it with preserve_fn (aka run_in_background)
|
# wrap it with preserve_fn (aka run_in_background)
|
||||||
return handle(verify_request)
|
return handle(verify_request)
|
||||||
|
|
||||||
results = [
|
results = [process(r) for r in verify_requests]
|
||||||
process(server_name, json_object)
|
|
||||||
for server_name, json_object in server_and_json
|
|
||||||
]
|
|
||||||
|
|
||||||
if verify_requests:
|
if key_lookups:
|
||||||
run_in_background(self._start_key_lookups, verify_requests)
|
run_in_background(self._start_key_lookups, key_lookups)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@ -173,10 +230,10 @@ class Keyring(object):
|
|||||||
def _start_key_lookups(self, verify_requests):
|
def _start_key_lookups(self, verify_requests):
|
||||||
"""Sets off the key fetches for each verify request
|
"""Sets off the key fetches for each verify request
|
||||||
|
|
||||||
Once each fetch completes, verify_request.deferred will be resolved.
|
Once each fetch completes, verify_request.key_ready will be resolved.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
verify_requests (List[VerifyKeyRequest]):
|
verify_requests (List[VerifyJsonRequest]):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -219,7 +276,7 @@ class Keyring(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
for verify_request in verify_requests:
|
for verify_request in verify_requests:
|
||||||
verify_request.deferred.addBoth(remove_deferreds, verify_request)
|
verify_request.key_ready.addBoth(remove_deferreds, verify_request)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error starting key lookups")
|
logger.exception("Error starting key lookups")
|
||||||
|
|
||||||
@ -272,16 +329,16 @@ class Keyring(object):
|
|||||||
def _get_server_verify_keys(self, verify_requests):
|
def _get_server_verify_keys(self, verify_requests):
|
||||||
"""Tries to find at least one key for each verify request
|
"""Tries to find at least one key for each verify request
|
||||||
|
|
||||||
For each verify_request, verify_request.deferred is called back with
|
For each verify_request, verify_request.key_ready is called back with
|
||||||
params (server_name, key_id, VerifyKey) if a key is found, or errbacked
|
params (server_name, key_id, VerifyKey) if a key is found, or errbacked
|
||||||
with a SynapseError if none of the keys are found.
|
with a SynapseError if none of the keys are found.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
verify_requests (list[VerifyKeyRequest]): list of verify requests
|
verify_requests (list[VerifyJsonRequest]): list of verify requests
|
||||||
"""
|
"""
|
||||||
|
|
||||||
remaining_requests = set(
|
remaining_requests = set(
|
||||||
(rq for rq in verify_requests if not rq.deferred.called)
|
(rq for rq in verify_requests if not rq.key_ready.called)
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@ -295,11 +352,15 @@ class Keyring(object):
|
|||||||
# look for any requests which weren't satisfied
|
# look for any requests which weren't satisfied
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
for verify_request in remaining_requests:
|
for verify_request in remaining_requests:
|
||||||
verify_request.deferred.errback(
|
verify_request.key_ready.errback(
|
||||||
SynapseError(
|
SynapseError(
|
||||||
401,
|
401,
|
||||||
"No key for %s with id %s"
|
"No key for %s with ids in %s (min_validity %i)"
|
||||||
% (verify_request.server_name, verify_request.key_ids),
|
% (
|
||||||
|
verify_request.server_name,
|
||||||
|
verify_request.key_ids,
|
||||||
|
verify_request.minimum_valid_until_ts,
|
||||||
|
),
|
||||||
Codes.UNAUTHORIZED,
|
Codes.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -311,8 +372,8 @@ class Keyring(object):
|
|||||||
logger.error("Unexpected error in _get_server_verify_keys: %s", err)
|
logger.error("Unexpected error in _get_server_verify_keys: %s", err)
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
for verify_request in remaining_requests:
|
for verify_request in remaining_requests:
|
||||||
if not verify_request.deferred.called:
|
if not verify_request.key_ready.called:
|
||||||
verify_request.deferred.errback(err)
|
verify_request.key_ready.errback(err)
|
||||||
|
|
||||||
run_in_background(do_iterations).addErrback(on_err)
|
run_in_background(do_iterations).addErrback(on_err)
|
||||||
|
|
||||||
@ -322,47 +383,66 @@ class Keyring(object):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
fetcher (KeyFetcher): fetcher to use to fetch the keys
|
fetcher (KeyFetcher): fetcher to use to fetch the keys
|
||||||
remaining_requests (set[VerifyKeyRequest]): outstanding key requests.
|
remaining_requests (set[VerifyJsonRequest]): outstanding key requests.
|
||||||
Any successfully-completed requests will be reomved from the list.
|
Any successfully-completed requests will be removed from the list.
|
||||||
"""
|
"""
|
||||||
# dict[str, set(str)]: keys to fetch for each server
|
# dict[str, dict[str, int]]: keys to fetch.
|
||||||
missing_keys = {}
|
# server_name -> key_id -> min_valid_ts
|
||||||
|
missing_keys = defaultdict(dict)
|
||||||
|
|
||||||
for verify_request in remaining_requests:
|
for verify_request in remaining_requests:
|
||||||
# any completed requests should already have been removed
|
# any completed requests should already have been removed
|
||||||
assert not verify_request.deferred.called
|
assert not verify_request.key_ready.called
|
||||||
missing_keys.setdefault(verify_request.server_name, set()).update(
|
keys_for_server = missing_keys[verify_request.server_name]
|
||||||
verify_request.key_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
results = yield fetcher.get_keys(missing_keys.items())
|
for key_id in verify_request.key_ids:
|
||||||
|
# If we have several requests for the same key, then we only need to
|
||||||
|
# request that key once, but we should do so with the greatest
|
||||||
|
# min_valid_until_ts of the requests, so that we can satisfy all of
|
||||||
|
# the requests.
|
||||||
|
keys_for_server[key_id] = max(
|
||||||
|
keys_for_server.get(key_id, -1),
|
||||||
|
verify_request.minimum_valid_until_ts,
|
||||||
|
)
|
||||||
|
|
||||||
|
results = yield fetcher.get_keys(missing_keys)
|
||||||
|
|
||||||
completed = list()
|
completed = list()
|
||||||
for verify_request in remaining_requests:
|
for verify_request in remaining_requests:
|
||||||
server_name = verify_request.server_name
|
server_name = verify_request.server_name
|
||||||
|
|
||||||
# see if any of the keys we got this time are sufficient to
|
# see if any of the keys we got this time are sufficient to
|
||||||
# complete this VerifyKeyRequest.
|
# complete this VerifyJsonRequest.
|
||||||
result_keys = results.get(server_name, {})
|
result_keys = results.get(server_name, {})
|
||||||
for key_id in verify_request.key_ids:
|
for key_id in verify_request.key_ids:
|
||||||
key = result_keys.get(key_id)
|
fetch_key_result = result_keys.get(key_id)
|
||||||
if key:
|
if not fetch_key_result:
|
||||||
with PreserveLoggingContext():
|
# we didn't get a result for this key
|
||||||
verify_request.deferred.callback(
|
continue
|
||||||
(server_name, key_id, key.verify_key)
|
|
||||||
)
|
if (
|
||||||
completed.append(verify_request)
|
fetch_key_result.valid_until_ts
|
||||||
break
|
< verify_request.minimum_valid_until_ts
|
||||||
|
):
|
||||||
|
# key was not valid at this point
|
||||||
|
continue
|
||||||
|
|
||||||
|
with PreserveLoggingContext():
|
||||||
|
verify_request.key_ready.callback(
|
||||||
|
(server_name, key_id, fetch_key_result.verify_key)
|
||||||
|
)
|
||||||
|
completed.append(verify_request)
|
||||||
|
break
|
||||||
|
|
||||||
remaining_requests.difference_update(completed)
|
remaining_requests.difference_update(completed)
|
||||||
|
|
||||||
|
|
||||||
class KeyFetcher(object):
|
class KeyFetcher(object):
|
||||||
def get_keys(self, server_name_and_key_ids):
|
def get_keys(self, keys_to_fetch):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
server_name_and_key_ids (iterable[Tuple[str, iterable[str]]]):
|
keys_to_fetch (dict[str, dict[str, int]]):
|
||||||
list of (server_name, iterable[key_id]) tuples to fetch keys for
|
the keys to be fetched. server_name -> key_id -> min_valid_ts
|
||||||
Note that the iterables may be iterated more than once.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult|None]]]:
|
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult|None]]]:
|
||||||
@ -378,13 +458,15 @@ class StoreKeyFetcher(KeyFetcher):
|
|||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_keys(self, server_name_and_key_ids):
|
def get_keys(self, keys_to_fetch):
|
||||||
"""see KeyFetcher.get_keys"""
|
"""see KeyFetcher.get_keys"""
|
||||||
|
|
||||||
keys_to_fetch = (
|
keys_to_fetch = (
|
||||||
(server_name, key_id)
|
(server_name, key_id)
|
||||||
for server_name, key_ids in server_name_and_key_ids
|
for server_name, keys_for_server in keys_to_fetch.items()
|
||||||
for key_id in key_ids
|
for key_id in keys_for_server.keys()
|
||||||
)
|
)
|
||||||
|
|
||||||
res = yield self.store.get_server_verify_keys(keys_to_fetch)
|
res = yield self.store.get_server_verify_keys(keys_to_fetch)
|
||||||
keys = {}
|
keys = {}
|
||||||
for (server_name, key_id), key in res.items():
|
for (server_name, key_id), key in res.items():
|
||||||
@ -398,9 +480,7 @@ class BaseV2KeyFetcher(object):
|
|||||||
self.config = hs.get_config()
|
self.config = hs.get_config()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def process_v2_response(
|
def process_v2_response(self, from_server, response_json, time_added_ms):
|
||||||
self, from_server, response_json, time_added_ms, requested_ids=[]
|
|
||||||
):
|
|
||||||
"""Parse a 'Server Keys' structure from the result of a /key request
|
"""Parse a 'Server Keys' structure from the result of a /key request
|
||||||
|
|
||||||
This is used to parse either the entirety of the response from
|
This is used to parse either the entirety of the response from
|
||||||
@ -422,10 +502,6 @@ class BaseV2KeyFetcher(object):
|
|||||||
|
|
||||||
time_added_ms (int): the timestamp to record in server_keys_json
|
time_added_ms (int): the timestamp to record in server_keys_json
|
||||||
|
|
||||||
requested_ids (iterable[str]): a list of the key IDs that were requested.
|
|
||||||
We will store the json for these key ids as well as any that are
|
|
||||||
actually in the response
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[dict[str, FetchKeyResult]]: map from key_id to result object
|
Deferred[dict[str, FetchKeyResult]]: map from key_id to result object
|
||||||
"""
|
"""
|
||||||
@ -481,11 +557,6 @@ class BaseV2KeyFetcher(object):
|
|||||||
|
|
||||||
signed_key_json_bytes = encode_canonical_json(signed_key_json)
|
signed_key_json_bytes = encode_canonical_json(signed_key_json)
|
||||||
|
|
||||||
# for reasons I don't quite understand, we store this json for the key ids we
|
|
||||||
# requested, as well as those we got.
|
|
||||||
updated_key_ids = set(requested_ids)
|
|
||||||
updated_key_ids.update(verify_keys)
|
|
||||||
|
|
||||||
yield logcontext.make_deferred_yieldable(
|
yield logcontext.make_deferred_yieldable(
|
||||||
defer.gatherResults(
|
defer.gatherResults(
|
||||||
[
|
[
|
||||||
@ -498,7 +569,7 @@ class BaseV2KeyFetcher(object):
|
|||||||
ts_expires_ms=ts_valid_until_ms,
|
ts_expires_ms=ts_valid_until_ms,
|
||||||
key_json_bytes=signed_key_json_bytes,
|
key_json_bytes=signed_key_json_bytes,
|
||||||
)
|
)
|
||||||
for key_id in updated_key_ids
|
for key_id in verify_keys
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
).addErrback(unwrapFirstError)
|
).addErrback(unwrapFirstError)
|
||||||
@ -514,25 +585,27 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
super(PerspectivesKeyFetcher, self).__init__(hs)
|
super(PerspectivesKeyFetcher, self).__init__(hs)
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.client = hs.get_http_client()
|
self.client = hs.get_http_client()
|
||||||
self.perspective_servers = self.config.perspectives
|
self.key_servers = self.config.key_servers
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_keys(self, server_name_and_key_ids):
|
def get_keys(self, keys_to_fetch):
|
||||||
"""see KeyFetcher.get_keys"""
|
"""see KeyFetcher.get_keys"""
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_key(perspective_name, perspective_keys):
|
def get_key(key_server):
|
||||||
try:
|
try:
|
||||||
result = yield self.get_server_verify_key_v2_indirect(
|
result = yield self.get_server_verify_key_v2_indirect(
|
||||||
server_name_and_key_ids, perspective_name, perspective_keys
|
keys_to_fetch, key_server
|
||||||
)
|
)
|
||||||
defer.returnValue(result)
|
defer.returnValue(result)
|
||||||
except KeyLookupError as e:
|
except KeyLookupError as e:
|
||||||
logger.warning("Key lookup failed from %r: %s", perspective_name, e)
|
logger.warning(
|
||||||
|
"Key lookup failed from %r: %s", key_server.server_name, e
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Unable to get key from %r: %s %s",
|
"Unable to get key from %r: %s %s",
|
||||||
perspective_name,
|
key_server.server_name,
|
||||||
type(e).__name__,
|
type(e).__name__,
|
||||||
str(e),
|
str(e),
|
||||||
)
|
)
|
||||||
@ -542,8 +615,8 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
results = yield logcontext.make_deferred_yieldable(
|
results = yield logcontext.make_deferred_yieldable(
|
||||||
defer.gatherResults(
|
defer.gatherResults(
|
||||||
[
|
[
|
||||||
run_in_background(get_key, p_name, p_keys)
|
run_in_background(get_key, server)
|
||||||
for p_name, p_keys in self.perspective_servers.items()
|
for server in self.key_servers
|
||||||
],
|
],
|
||||||
consumeErrors=True,
|
consumeErrors=True,
|
||||||
).addErrback(unwrapFirstError)
|
).addErrback(unwrapFirstError)
|
||||||
@ -558,15 +631,15 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_server_verify_key_v2_indirect(
|
def get_server_verify_key_v2_indirect(
|
||||||
self, server_names_and_key_ids, perspective_name, perspective_keys
|
self, keys_to_fetch, key_server
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
server_names_and_key_ids (iterable[Tuple[str, iterable[str]]]):
|
keys_to_fetch (dict[str, dict[str, int]]):
|
||||||
list of (server_name, iterable[key_id]) tuples to fetch keys for
|
the keys to be fetched. server_name -> key_id -> min_valid_ts
|
||||||
perspective_name (str): name of the notary server to query for the keys
|
|
||||||
perspective_keys (dict[str, VerifyKey]): map of key_id->key for the
|
key_server (synapse.config.key.TrustedKeyServer): notary server to query for
|
||||||
notary server
|
the keys
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult]]]: map
|
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult]]]: map
|
||||||
@ -576,14 +649,13 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
KeyLookupError if there was an error processing the entire response from
|
KeyLookupError if there was an error processing the entire response from
|
||||||
the server
|
the server
|
||||||
"""
|
"""
|
||||||
|
perspective_name = key_server.server_name
|
||||||
logger.info(
|
logger.info(
|
||||||
"Requesting keys %s from notary server %s",
|
"Requesting keys %s from notary server %s",
|
||||||
server_names_and_key_ids,
|
keys_to_fetch.items(),
|
||||||
perspective_name,
|
perspective_name,
|
||||||
)
|
)
|
||||||
# TODO(mark): Set the minimum_valid_until_ts to that needed by
|
|
||||||
# the events being validated or the current time if validating
|
|
||||||
# an incoming request.
|
|
||||||
try:
|
try:
|
||||||
query_response = yield self.client.post_json(
|
query_response = yield self.client.post_json(
|
||||||
destination=perspective_name,
|
destination=perspective_name,
|
||||||
@ -591,12 +663,12 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
data={
|
data={
|
||||||
u"server_keys": {
|
u"server_keys": {
|
||||||
server_name: {
|
server_name: {
|
||||||
key_id: {u"minimum_valid_until_ts": 0} for key_id in key_ids
|
key_id: {u"minimum_valid_until_ts": min_valid_ts}
|
||||||
|
for key_id, min_valid_ts in server_keys.items()
|
||||||
}
|
}
|
||||||
for server_name, key_ids in server_names_and_key_ids
|
for server_name, server_keys in keys_to_fetch.items()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
long_retries=True,
|
|
||||||
)
|
)
|
||||||
except (NotRetryingDestination, RequestSendFailed) as e:
|
except (NotRetryingDestination, RequestSendFailed) as e:
|
||||||
raise_from(KeyLookupError("Failed to connect to remote server"), e)
|
raise_from(KeyLookupError("Failed to connect to remote server"), e)
|
||||||
@ -618,11 +690,13 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
processed_response = yield self._process_perspectives_response(
|
self._validate_perspectives_response(
|
||||||
perspective_name,
|
key_server,
|
||||||
perspective_keys,
|
|
||||||
response,
|
response,
|
||||||
time_added_ms=time_now_ms,
|
)
|
||||||
|
|
||||||
|
processed_response = yield self.process_v2_response(
|
||||||
|
perspective_name, response, time_added_ms=time_now_ms
|
||||||
)
|
)
|
||||||
except KeyLookupError as e:
|
except KeyLookupError as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@ -646,28 +720,24 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
|
|
||||||
defer.returnValue(keys)
|
defer.returnValue(keys)
|
||||||
|
|
||||||
def _process_perspectives_response(
|
def _validate_perspectives_response(
|
||||||
self, perspective_name, perspective_keys, response, time_added_ms
|
self, key_server, response,
|
||||||
):
|
):
|
||||||
"""Parse a 'Server Keys' structure from the result of a /key/query request
|
"""Optionally check the signature on the result of a /key/query request
|
||||||
|
|
||||||
Checks that the entry is correctly signed by the perspectives server, and then
|
|
||||||
passes over to process_v2_response
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
perspective_name (str): the name of the notary server that produced this
|
key_server (synapse.config.key.TrustedKeyServer): the notary server that
|
||||||
result
|
produced this result
|
||||||
|
|
||||||
perspective_keys (dict[str, VerifyKey]): map of key_id->key for the
|
|
||||||
notary server
|
|
||||||
|
|
||||||
response (dict): the json-decoded Server Keys response object
|
response (dict): the json-decoded Server Keys response object
|
||||||
|
|
||||||
time_added_ms (int): the timestamp to record in server_keys_json
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred[dict[str, FetchKeyResult]]: map from key_id to result object
|
|
||||||
"""
|
"""
|
||||||
|
perspective_name = key_server.server_name
|
||||||
|
perspective_keys = key_server.verify_keys
|
||||||
|
|
||||||
|
if perspective_keys is None:
|
||||||
|
# signature checking is disabled on this server
|
||||||
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
u"signatures" not in response
|
u"signatures" not in response
|
||||||
or perspective_name not in response[u"signatures"]
|
or perspective_name not in response[u"signatures"]
|
||||||
@ -689,10 +759,6 @@ class PerspectivesKeyFetcher(BaseV2KeyFetcher):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.process_v2_response(
|
|
||||||
perspective_name, response, time_added_ms=time_added_ms
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ServerKeyFetcher(BaseV2KeyFetcher):
|
class ServerKeyFetcher(BaseV2KeyFetcher):
|
||||||
"""KeyFetcher impl which fetches keys from the origin servers"""
|
"""KeyFetcher impl which fetches keys from the origin servers"""
|
||||||
@ -702,34 +768,54 @@ class ServerKeyFetcher(BaseV2KeyFetcher):
|
|||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.client = hs.get_http_client()
|
self.client = hs.get_http_client()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
def get_keys(self, keys_to_fetch):
|
||||||
def get_keys(self, server_name_and_key_ids):
|
"""
|
||||||
"""see KeyFetcher.get_keys"""
|
Args:
|
||||||
results = yield logcontext.make_deferred_yieldable(
|
keys_to_fetch (dict[str, iterable[str]]):
|
||||||
defer.gatherResults(
|
the keys to be fetched. server_name -> key_ids
|
||||||
[
|
|
||||||
run_in_background(
|
|
||||||
self.get_server_verify_key_v2_direct, server_name, key_ids
|
|
||||||
)
|
|
||||||
for server_name, key_ids in server_name_and_key_ids
|
|
||||||
],
|
|
||||||
consumeErrors=True,
|
|
||||||
).addErrback(unwrapFirstError)
|
|
||||||
)
|
|
||||||
|
|
||||||
merged = {}
|
Returns:
|
||||||
for result in results:
|
Deferred[dict[str, dict[str, synapse.storage.keys.FetchKeyResult|None]]]:
|
||||||
merged.update(result)
|
map from server_name -> key_id -> FetchKeyResult
|
||||||
|
"""
|
||||||
|
|
||||||
defer.returnValue(
|
results = {}
|
||||||
{server_name: keys for server_name, keys in merged.items() if keys}
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_key(key_to_fetch_item):
|
||||||
|
server_name, key_ids = key_to_fetch_item
|
||||||
|
try:
|
||||||
|
keys = yield self.get_server_verify_key_v2_direct(server_name, key_ids)
|
||||||
|
results[server_name] = keys
|
||||||
|
except KeyLookupError as e:
|
||||||
|
logger.warning(
|
||||||
|
"Error looking up keys %s from %s: %s", key_ids, server_name, e
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Error getting keys %s from %s", key_ids, server_name)
|
||||||
|
|
||||||
|
return yieldable_gather_results(get_key, keys_to_fetch.items()).addCallback(
|
||||||
|
lambda _: results
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_server_verify_key_v2_direct(self, server_name, key_ids):
|
def get_server_verify_key_v2_direct(self, server_name, key_ids):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_name (str):
|
||||||
|
key_ids (iterable[str]):
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[dict[str, FetchKeyResult]]: map from key ID to lookup result
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeyLookupError if there was a problem making the lookup
|
||||||
|
"""
|
||||||
keys = {} # type: dict[str, FetchKeyResult]
|
keys = {} # type: dict[str, FetchKeyResult]
|
||||||
|
|
||||||
for requested_key_id in key_ids:
|
for requested_key_id in key_ids:
|
||||||
|
# we may have found this key as a side-effect of asking for another.
|
||||||
if requested_key_id in keys:
|
if requested_key_id in keys:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -740,6 +826,19 @@ class ServerKeyFetcher(BaseV2KeyFetcher):
|
|||||||
path="/_matrix/key/v2/server/"
|
path="/_matrix/key/v2/server/"
|
||||||
+ urllib.parse.quote(requested_key_id),
|
+ urllib.parse.quote(requested_key_id),
|
||||||
ignore_backoff=True,
|
ignore_backoff=True,
|
||||||
|
|
||||||
|
# we only give the remote server 10s to respond. It should be an
|
||||||
|
# easy request to handle, so if it doesn't reply within 10s, it's
|
||||||
|
# probably not going to.
|
||||||
|
#
|
||||||
|
# Furthermore, when we are acting as a notary server, we cannot
|
||||||
|
# wait all day for all of the origin servers, as the requesting
|
||||||
|
# server will otherwise time out before we can respond.
|
||||||
|
#
|
||||||
|
# (Note that get_json may make 4 attempts, so this can still take
|
||||||
|
# almost 45 seconds to fetch the headers, plus up to another 60s to
|
||||||
|
# read the response).
|
||||||
|
timeout=10000,
|
||||||
)
|
)
|
||||||
except (NotRetryingDestination, RequestSendFailed) as e:
|
except (NotRetryingDestination, RequestSendFailed) as e:
|
||||||
raise_from(KeyLookupError("Failed to connect to remote server"), e)
|
raise_from(KeyLookupError("Failed to connect to remote server"), e)
|
||||||
@ -754,7 +853,6 @@ class ServerKeyFetcher(BaseV2KeyFetcher):
|
|||||||
|
|
||||||
response_keys = yield self.process_v2_response(
|
response_keys = yield self.process_v2_response(
|
||||||
from_server=server_name,
|
from_server=server_name,
|
||||||
requested_ids=[requested_key_id],
|
|
||||||
response_json=response,
|
response_json=response,
|
||||||
time_added_ms=time_now_ms,
|
time_added_ms=time_now_ms,
|
||||||
)
|
)
|
||||||
@ -765,7 +863,7 @@ class ServerKeyFetcher(BaseV2KeyFetcher):
|
|||||||
)
|
)
|
||||||
keys.update(response_keys)
|
keys.update(response_keys)
|
||||||
|
|
||||||
defer.returnValue({server_name: keys})
|
defer.returnValue(keys)
|
||||||
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@ -773,7 +871,7 @@ def _handle_key_deferred(verify_request):
|
|||||||
"""Waits for the key to become available, and then performs a verification
|
"""Waits for the key to become available, and then performs a verification
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
verify_request (VerifyKeyRequest):
|
verify_request (VerifyJsonRequest):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[None]
|
Deferred[None]
|
||||||
@ -783,14 +881,10 @@ def _handle_key_deferred(verify_request):
|
|||||||
"""
|
"""
|
||||||
server_name = verify_request.server_name
|
server_name = verify_request.server_name
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
_, key_id, verify_key = yield verify_request.deferred
|
_, key_id, verify_key = yield verify_request.key_ready
|
||||||
|
|
||||||
json_object = verify_request.json_object
|
json_object = verify_request.json_object
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"Got key %s %s:%s for server %s, verifying"
|
|
||||||
% (key_id, verify_key.alg, verify_key.version, server_name)
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
verify_signed_json(json_object, server_name, verify_key)
|
verify_signed_json(json_object, server_name, verify_key)
|
||||||
except SignatureVerifyException as e:
|
except SignatureVerifyException as e:
|
||||||
|
@ -223,9 +223,6 @@ def _check_sigs_on_pdus(keyring, room_version, pdus):
|
|||||||
the signatures are valid, or fail (with a SynapseError) if not.
|
the signatures are valid, or fail (with a SynapseError) if not.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# (currently this is written assuming the v1 room structure; we'll probably want a
|
|
||||||
# separate function for checking v2 rooms)
|
|
||||||
|
|
||||||
# we want to check that the event is signed by:
|
# we want to check that the event is signed by:
|
||||||
#
|
#
|
||||||
# (a) the sender's server
|
# (a) the sender's server
|
||||||
@ -257,6 +254,10 @@ def _check_sigs_on_pdus(keyring, room_version, pdus):
|
|||||||
for p in pdus
|
for p in pdus
|
||||||
]
|
]
|
||||||
|
|
||||||
|
v = KNOWN_ROOM_VERSIONS.get(room_version)
|
||||||
|
if not v:
|
||||||
|
raise RuntimeError("Unrecognized room version %s" % (room_version,))
|
||||||
|
|
||||||
# First we check that the sender event is signed by the sender's domain
|
# First we check that the sender event is signed by the sender's domain
|
||||||
# (except if its a 3pid invite, in which case it may be sent by any server)
|
# (except if its a 3pid invite, in which case it may be sent by any server)
|
||||||
pdus_to_check_sender = [
|
pdus_to_check_sender = [
|
||||||
@ -264,10 +265,17 @@ def _check_sigs_on_pdus(keyring, room_version, pdus):
|
|||||||
if not _is_invite_via_3pid(p.pdu)
|
if not _is_invite_via_3pid(p.pdu)
|
||||||
]
|
]
|
||||||
|
|
||||||
more_deferreds = keyring.verify_json_objects_for_server([
|
more_deferreds = keyring.verify_json_objects_for_server(
|
||||||
(p.sender_domain, p.redacted_pdu_json)
|
[
|
||||||
for p in pdus_to_check_sender
|
(
|
||||||
])
|
p.sender_domain,
|
||||||
|
p.redacted_pdu_json,
|
||||||
|
p.pdu.origin_server_ts if v.enforce_key_validity else 0,
|
||||||
|
p.pdu.event_id,
|
||||||
|
)
|
||||||
|
for p in pdus_to_check_sender
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def sender_err(e, pdu_to_check):
|
def sender_err(e, pdu_to_check):
|
||||||
errmsg = "event id %s: unable to verify signature for sender %s: %s" % (
|
errmsg = "event id %s: unable to verify signature for sender %s: %s" % (
|
||||||
@ -287,20 +295,23 @@ def _check_sigs_on_pdus(keyring, room_version, pdus):
|
|||||||
# event id's domain (normally only the case for joins/leaves), and add additional
|
# event id's domain (normally only the case for joins/leaves), and add additional
|
||||||
# checks. Only do this if the room version has a concept of event ID domain
|
# checks. Only do this if the room version has a concept of event ID domain
|
||||||
# (ie, the room version uses old-style non-hash event IDs).
|
# (ie, the room version uses old-style non-hash event IDs).
|
||||||
v = KNOWN_ROOM_VERSIONS.get(room_version)
|
|
||||||
if not v:
|
|
||||||
raise RuntimeError("Unrecognized room version %s" % (room_version,))
|
|
||||||
|
|
||||||
if v.event_format == EventFormatVersions.V1:
|
if v.event_format == EventFormatVersions.V1:
|
||||||
pdus_to_check_event_id = [
|
pdus_to_check_event_id = [
|
||||||
p for p in pdus_to_check
|
p for p in pdus_to_check
|
||||||
if p.sender_domain != get_domain_from_id(p.pdu.event_id)
|
if p.sender_domain != get_domain_from_id(p.pdu.event_id)
|
||||||
]
|
]
|
||||||
|
|
||||||
more_deferreds = keyring.verify_json_objects_for_server([
|
more_deferreds = keyring.verify_json_objects_for_server(
|
||||||
(get_domain_from_id(p.pdu.event_id), p.redacted_pdu_json)
|
[
|
||||||
for p in pdus_to_check_event_id
|
(
|
||||||
])
|
get_domain_from_id(p.pdu.event_id),
|
||||||
|
p.redacted_pdu_json,
|
||||||
|
p.pdu.origin_server_ts if v.enforce_key_validity else 0,
|
||||||
|
p.pdu.event_id,
|
||||||
|
)
|
||||||
|
for p in pdus_to_check_event_id
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def event_err(e, pdu_to_check):
|
def event_err(e, pdu_to_check):
|
||||||
errmsg = (
|
errmsg = (
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
|
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
|
|
||||||
@ -233,7 +232,8 @@ class FederationClient(FederationBase):
|
|||||||
moving to the next destination. None indicates no timeout.
|
moving to the next destination. None indicates no timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred: Results in the requested PDU.
|
Deferred: Results in the requested PDU, or None if we were unable to find
|
||||||
|
it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: Rate limit the number of times we try and get the same event.
|
# TODO: Rate limit the number of times we try and get the same event.
|
||||||
@ -258,7 +258,12 @@ class FederationClient(FederationBase):
|
|||||||
destination, event_id, timeout=timeout,
|
destination, event_id, timeout=timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("transaction_data %r", transaction_data)
|
logger.debug(
|
||||||
|
"retrieved event id %s from %s: %r",
|
||||||
|
event_id,
|
||||||
|
destination,
|
||||||
|
transaction_data,
|
||||||
|
)
|
||||||
|
|
||||||
pdu_list = [
|
pdu_list = [
|
||||||
event_from_pdu_json(p, format_ver, outlier=outlier)
|
event_from_pdu_json(p, format_ver, outlier=outlier)
|
||||||
@ -280,6 +285,7 @@ class FederationClient(FederationBase):
|
|||||||
"Failed to get PDU %s from %s because %s",
|
"Failed to get PDU %s from %s because %s",
|
||||||
event_id, destination, e,
|
event_id, destination, e,
|
||||||
)
|
)
|
||||||
|
continue
|
||||||
except NotRetryingDestination as e:
|
except NotRetryingDestination as e:
|
||||||
logger.info(str(e))
|
logger.info(str(e))
|
||||||
continue
|
continue
|
||||||
@ -326,12 +332,16 @@ class FederationClient(FederationBase):
|
|||||||
state_event_ids = result["pdu_ids"]
|
state_event_ids = result["pdu_ids"]
|
||||||
auth_event_ids = result.get("auth_chain_ids", [])
|
auth_event_ids = result.get("auth_chain_ids", [])
|
||||||
|
|
||||||
fetched_events, failed_to_fetch = yield self.get_events(
|
fetched_events, failed_to_fetch = yield self.get_events_from_store_or_dest(
|
||||||
[destination], room_id, set(state_event_ids + auth_event_ids)
|
destination, room_id, set(state_event_ids + auth_event_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
if failed_to_fetch:
|
if failed_to_fetch:
|
||||||
logger.warn("Failed to get %r", failed_to_fetch)
|
logger.warning(
|
||||||
|
"Failed to fetch missing state/auth events for %s: %s",
|
||||||
|
room_id,
|
||||||
|
failed_to_fetch
|
||||||
|
)
|
||||||
|
|
||||||
event_map = {
|
event_map = {
|
||||||
ev.event_id: ev for ev in fetched_events
|
ev.event_id: ev for ev in fetched_events
|
||||||
@ -397,27 +407,20 @@ class FederationClient(FederationBase):
|
|||||||
defer.returnValue((signed_pdus, signed_auth))
|
defer.returnValue((signed_pdus, signed_auth))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_events(self, destinations, room_id, event_ids, return_local=True):
|
def get_events_from_store_or_dest(self, destination, room_id, event_ids):
|
||||||
"""Fetch events from some remote destinations, checking if we already
|
"""Fetch events from a remote destination, checking if we already have them.
|
||||||
have them.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
destinations (list)
|
destination (str)
|
||||||
room_id (str)
|
room_id (str)
|
||||||
event_ids (list)
|
event_ids (list)
|
||||||
return_local (bool): Whether to include events we already have in
|
|
||||||
the DB in the returned list of events
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred: A deferred resolving to a 2-tuple where the first is a list of
|
Deferred: A deferred resolving to a 2-tuple where the first is a list of
|
||||||
events and the second is a list of event ids that we failed to fetch.
|
events and the second is a list of event ids that we failed to fetch.
|
||||||
"""
|
"""
|
||||||
if return_local:
|
seen_events = yield self.store.get_events(event_ids, allow_rejected=True)
|
||||||
seen_events = yield self.store.get_events(event_ids, allow_rejected=True)
|
signed_events = list(seen_events.values())
|
||||||
signed_events = list(seen_events.values())
|
|
||||||
else:
|
|
||||||
seen_events = yield self.store.have_seen_events(event_ids)
|
|
||||||
signed_events = []
|
|
||||||
|
|
||||||
failed_to_fetch = set()
|
failed_to_fetch = set()
|
||||||
|
|
||||||
@ -428,10 +431,11 @@ class FederationClient(FederationBase):
|
|||||||
if not missing_events:
|
if not missing_events:
|
||||||
defer.returnValue((signed_events, failed_to_fetch))
|
defer.returnValue((signed_events, failed_to_fetch))
|
||||||
|
|
||||||
def random_server_list():
|
logger.debug(
|
||||||
srvs = list(destinations)
|
"Fetching unknown state/auth events %s for room %s",
|
||||||
random.shuffle(srvs)
|
missing_events,
|
||||||
return srvs
|
event_ids,
|
||||||
|
)
|
||||||
|
|
||||||
room_version = yield self.store.get_room_version(room_id)
|
room_version = yield self.store.get_room_version(room_id)
|
||||||
|
|
||||||
@ -443,7 +447,7 @@ class FederationClient(FederationBase):
|
|||||||
deferreds = [
|
deferreds = [
|
||||||
run_in_background(
|
run_in_background(
|
||||||
self.get_pdu,
|
self.get_pdu,
|
||||||
destinations=random_server_list(),
|
destinations=[destination],
|
||||||
event_id=e_id,
|
event_id=e_id,
|
||||||
room_version=room_version,
|
room_version=room_version,
|
||||||
)
|
)
|
||||||
|
@ -349,9 +349,10 @@ class PerDestinationQueue(object):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _get_new_device_messages(self, limit):
|
def _get_new_device_messages(self, limit):
|
||||||
last_device_list = self._last_device_list_stream_id
|
last_device_list = self._last_device_list_stream_id
|
||||||
# Will return at most 20 entries
|
|
||||||
|
# Retrieve list of new device updates to send to the destination
|
||||||
now_stream_id, results = yield self._store.get_devices_by_remote(
|
now_stream_id, results = yield self._store.get_devices_by_remote(
|
||||||
self._destination, last_device_list
|
self._destination, last_device_list, limit=limit,
|
||||||
)
|
)
|
||||||
edus = [
|
edus = [
|
||||||
Edu(
|
Edu(
|
||||||
|
@ -94,6 +94,7 @@ class NoAuthenticationError(AuthenticationError):
|
|||||||
|
|
||||||
class Authenticator(object):
|
class Authenticator(object):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
self._clock = hs.get_clock()
|
||||||
self.keyring = hs.get_keyring()
|
self.keyring = hs.get_keyring()
|
||||||
self.server_name = hs.hostname
|
self.server_name = hs.hostname
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
@ -102,6 +103,7 @@ class Authenticator(object):
|
|||||||
# A method just so we can pass 'self' as the authenticator to the Servlets
|
# A method just so we can pass 'self' as the authenticator to the Servlets
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def authenticate_request(self, request, content):
|
def authenticate_request(self, request, content):
|
||||||
|
now = self._clock.time_msec()
|
||||||
json_request = {
|
json_request = {
|
||||||
"method": request.method.decode('ascii'),
|
"method": request.method.decode('ascii'),
|
||||||
"uri": request.uri.decode('ascii'),
|
"uri": request.uri.decode('ascii'),
|
||||||
@ -138,7 +140,9 @@ class Authenticator(object):
|
|||||||
401, "Missing Authorization headers", Codes.UNAUTHORIZED,
|
401, "Missing Authorization headers", Codes.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
yield self.keyring.verify_json_for_server(origin, json_request)
|
yield self.keyring.verify_json_for_server(
|
||||||
|
origin, json_request, now, "Incoming request"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("Request from %s", origin)
|
logger.info("Request from %s", origin)
|
||||||
request.authenticated_entity = origin
|
request.authenticated_entity = origin
|
||||||
|
@ -97,10 +97,13 @@ class GroupAttestationSigning(object):
|
|||||||
|
|
||||||
# TODO: We also want to check that *new* attestations that people give
|
# TODO: We also want to check that *new* attestations that people give
|
||||||
# us to store are valid for at least a little while.
|
# us to store are valid for at least a little while.
|
||||||
if valid_until_ms < self.clock.time_msec():
|
now = self.clock.time_msec()
|
||||||
|
if valid_until_ms < now:
|
||||||
raise SynapseError(400, "Attestation expired")
|
raise SynapseError(400, "Attestation expired")
|
||||||
|
|
||||||
yield self.keyring.verify_json_for_server(server_name, attestation)
|
yield self.keyring.verify_json_for_server(
|
||||||
|
server_name, attestation, now, "Group attestation"
|
||||||
|
)
|
||||||
|
|
||||||
def create_attestation(self, group_id, user_id):
|
def create_attestation(self, group_id, user_id):
|
||||||
"""Create an attestation for the group_id and user_id with default
|
"""Create an attestation for the group_id and user_id with default
|
||||||
|
@ -162,7 +162,7 @@ class AuthHandler(BaseHandler):
|
|||||||
defer.returnValue(params)
|
defer.returnValue(params)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_auth(self, flows, clientdict, clientip):
|
def check_auth(self, flows, clientdict, clientip, password_servlet=False):
|
||||||
"""
|
"""
|
||||||
Takes a dictionary sent by the client in the login / registration
|
Takes a dictionary sent by the client in the login / registration
|
||||||
protocol and handles the User-Interactive Auth flow.
|
protocol and handles the User-Interactive Auth flow.
|
||||||
@ -186,6 +186,16 @@ class AuthHandler(BaseHandler):
|
|||||||
|
|
||||||
clientip (str): The IP address of the client.
|
clientip (str): The IP address of the client.
|
||||||
|
|
||||||
|
password_servlet (bool): Whether the request originated from
|
||||||
|
PasswordRestServlet.
|
||||||
|
XXX: This is a temporary hack to distinguish between checking
|
||||||
|
for threepid validations locally (in the case of password
|
||||||
|
resets) and using the identity server (in the case of binding
|
||||||
|
a 3PID during registration). Once we start using the
|
||||||
|
homeserver for both tasks, this distinction will no longer be
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
defer.Deferred[dict, dict, str]: a deferred tuple of
|
defer.Deferred[dict, dict, str]: a deferred tuple of
|
||||||
(creds, params, session_id).
|
(creds, params, session_id).
|
||||||
@ -241,7 +251,9 @@ class AuthHandler(BaseHandler):
|
|||||||
if 'type' in authdict:
|
if 'type' in authdict:
|
||||||
login_type = authdict['type']
|
login_type = authdict['type']
|
||||||
try:
|
try:
|
||||||
result = yield self._check_auth_dict(authdict, clientip)
|
result = yield self._check_auth_dict(
|
||||||
|
authdict, clientip, password_servlet=password_servlet,
|
||||||
|
)
|
||||||
if result:
|
if result:
|
||||||
creds[login_type] = result
|
creds[login_type] = result
|
||||||
self._save_session(session)
|
self._save_session(session)
|
||||||
@ -351,7 +363,7 @@ class AuthHandler(BaseHandler):
|
|||||||
return sess.setdefault('serverdict', {}).get(key, default)
|
return sess.setdefault('serverdict', {}).get(key, default)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _check_auth_dict(self, authdict, clientip):
|
def _check_auth_dict(self, authdict, clientip, password_servlet=False):
|
||||||
"""Attempt to validate the auth dict provided by a client
|
"""Attempt to validate the auth dict provided by a client
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -369,7 +381,13 @@ class AuthHandler(BaseHandler):
|
|||||||
login_type = authdict['type']
|
login_type = authdict['type']
|
||||||
checker = self.checkers.get(login_type)
|
checker = self.checkers.get(login_type)
|
||||||
if checker is not None:
|
if checker is not None:
|
||||||
res = yield checker(authdict, clientip)
|
# XXX: Temporary workaround for having Synapse handle password resets
|
||||||
|
# See AuthHandler.check_auth for further details
|
||||||
|
res = yield checker(
|
||||||
|
authdict,
|
||||||
|
clientip=clientip,
|
||||||
|
password_servlet=password_servlet,
|
||||||
|
)
|
||||||
defer.returnValue(res)
|
defer.returnValue(res)
|
||||||
|
|
||||||
# build a v1-login-style dict out of the authdict and fall back to the
|
# build a v1-login-style dict out of the authdict and fall back to the
|
||||||
@ -383,7 +401,7 @@ class AuthHandler(BaseHandler):
|
|||||||
defer.returnValue(canonical_id)
|
defer.returnValue(canonical_id)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _check_recaptcha(self, authdict, clientip):
|
def _check_recaptcha(self, authdict, clientip, **kwargs):
|
||||||
try:
|
try:
|
||||||
user_response = authdict["response"]
|
user_response = authdict["response"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -429,20 +447,20 @@ class AuthHandler(BaseHandler):
|
|||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)
|
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)
|
||||||
|
|
||||||
def _check_email_identity(self, authdict, _):
|
def _check_email_identity(self, authdict, **kwargs):
|
||||||
return self._check_threepid('email', authdict)
|
return self._check_threepid('email', authdict, **kwargs)
|
||||||
|
|
||||||
def _check_msisdn(self, authdict, _):
|
def _check_msisdn(self, authdict, **kwargs):
|
||||||
return self._check_threepid('msisdn', authdict)
|
return self._check_threepid('msisdn', authdict)
|
||||||
|
|
||||||
def _check_dummy_auth(self, authdict, _):
|
def _check_dummy_auth(self, authdict, **kwargs):
|
||||||
return defer.succeed(True)
|
return defer.succeed(True)
|
||||||
|
|
||||||
def _check_terms_auth(self, authdict, _):
|
def _check_terms_auth(self, authdict, **kwargs):
|
||||||
return defer.succeed(True)
|
return defer.succeed(True)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _check_threepid(self, medium, authdict):
|
def _check_threepid(self, medium, authdict, password_servlet=False, **kwargs):
|
||||||
if 'threepid_creds' not in authdict:
|
if 'threepid_creds' not in authdict:
|
||||||
raise LoginError(400, "Missing threepid_creds", Codes.MISSING_PARAM)
|
raise LoginError(400, "Missing threepid_creds", Codes.MISSING_PARAM)
|
||||||
|
|
||||||
@ -451,7 +469,29 @@ class AuthHandler(BaseHandler):
|
|||||||
identity_handler = self.hs.get_handlers().identity_handler
|
identity_handler = self.hs.get_handlers().identity_handler
|
||||||
|
|
||||||
logger.info("Getting validated threepid. threepidcreds: %r", (threepid_creds,))
|
logger.info("Getting validated threepid. threepidcreds: %r", (threepid_creds,))
|
||||||
threepid = yield identity_handler.threepid_from_creds(threepid_creds)
|
if (
|
||||||
|
not password_servlet
|
||||||
|
or self.hs.config.email_password_reset_behaviour == "remote"
|
||||||
|
):
|
||||||
|
threepid = yield identity_handler.threepid_from_creds(threepid_creds)
|
||||||
|
elif self.hs.config.email_password_reset_behaviour == "local":
|
||||||
|
row = yield self.store.get_threepid_validation_session(
|
||||||
|
medium,
|
||||||
|
threepid_creds["client_secret"],
|
||||||
|
sid=threepid_creds["sid"],
|
||||||
|
)
|
||||||
|
|
||||||
|
threepid = {
|
||||||
|
"medium": row["medium"],
|
||||||
|
"address": row["address"],
|
||||||
|
"validated_at": row["validated_at"],
|
||||||
|
} if row else None
|
||||||
|
|
||||||
|
if row:
|
||||||
|
# Valid threepid returned, delete from the db
|
||||||
|
yield self.store.delete_threepid_session(threepid_creds["sid"])
|
||||||
|
else:
|
||||||
|
raise SynapseError(400, "Password resets are not enabled on this homeserver")
|
||||||
|
|
||||||
if not threepid:
|
if not threepid:
|
||||||
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)
|
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)
|
||||||
|
@ -35,6 +35,7 @@ from synapse.api.errors import (
|
|||||||
CodeMessageException,
|
CodeMessageException,
|
||||||
FederationDeniedError,
|
FederationDeniedError,
|
||||||
FederationError,
|
FederationError,
|
||||||
|
RequestSendFailed,
|
||||||
StoreError,
|
StoreError,
|
||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
@ -2027,9 +2028,21 @@ class FederationHandler(BaseHandler):
|
|||||||
"""
|
"""
|
||||||
room_version = yield self.store.get_room_version(event.room_id)
|
room_version = yield self.store.get_room_version(event.room_id)
|
||||||
|
|
||||||
yield self._update_auth_events_and_context_for_auth(
|
try:
|
||||||
origin, event, context, auth_events
|
yield self._update_auth_events_and_context_for_auth(
|
||||||
)
|
origin, event, context, auth_events
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# We don't really mind if the above fails, so lets not fail
|
||||||
|
# processing if it does. However, it really shouldn't fail so
|
||||||
|
# let's still log as an exception since we'll still want to fix
|
||||||
|
# any bugs.
|
||||||
|
logger.exception(
|
||||||
|
"Failed to double check auth events for %s with remote. "
|
||||||
|
"Ignoring failure and continuing processing of event.",
|
||||||
|
event.event_id,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.auth.check(room_version, event, auth_events=auth_events)
|
self.auth.check(room_version, event, auth_events=auth_events)
|
||||||
except AuthError as e:
|
except AuthError as e:
|
||||||
@ -2042,6 +2055,15 @@ class FederationHandler(BaseHandler):
|
|||||||
):
|
):
|
||||||
"""Helper for do_auth. See there for docs.
|
"""Helper for do_auth. See there for docs.
|
||||||
|
|
||||||
|
Checks whether a given event has the expected auth events. If it
|
||||||
|
doesn't then we talk to the remote server to compare state to see if
|
||||||
|
we can come to a consensus (e.g. if one server missed some valid
|
||||||
|
state).
|
||||||
|
|
||||||
|
This attempts to resovle any potential divergence of state between
|
||||||
|
servers, but is not essential and so failures should not block further
|
||||||
|
processing of the event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
origin (str):
|
origin (str):
|
||||||
event (synapse.events.EventBase):
|
event (synapse.events.EventBase):
|
||||||
@ -2088,9 +2110,15 @@ class FederationHandler(BaseHandler):
|
|||||||
missing_auth,
|
missing_auth,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
remote_auth_chain = yield self.federation_client.get_event_auth(
|
try:
|
||||||
origin, event.room_id, event.event_id
|
remote_auth_chain = yield self.federation_client.get_event_auth(
|
||||||
)
|
origin, event.room_id, event.event_id
|
||||||
|
)
|
||||||
|
except RequestSendFailed as e:
|
||||||
|
# The other side isn't around or doesn't implement the
|
||||||
|
# endpoint, so lets just bail out.
|
||||||
|
logger.info("Failed to get event auth from remote: %s", e)
|
||||||
|
return
|
||||||
|
|
||||||
seen_remotes = yield self.store.have_seen_events(
|
seen_remotes = yield self.store.have_seen_events(
|
||||||
[e.event_id for e in remote_auth_chain]
|
[e.event_id for e in remote_auth_chain]
|
||||||
@ -2236,12 +2264,18 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# 2. Get remote difference.
|
# 2. Get remote difference.
|
||||||
result = yield self.federation_client.query_auth(
|
try:
|
||||||
origin,
|
result = yield self.federation_client.query_auth(
|
||||||
event.room_id,
|
origin,
|
||||||
event.event_id,
|
event.room_id,
|
||||||
local_auth_chain,
|
event.event_id,
|
||||||
)
|
local_auth_chain,
|
||||||
|
)
|
||||||
|
except RequestSendFailed as e:
|
||||||
|
# The other side isn't around or doesn't implement the
|
||||||
|
# endpoint, so lets just bail out.
|
||||||
|
logger.info("Failed to query auth from remote: %s", e)
|
||||||
|
return
|
||||||
|
|
||||||
seen_remotes = yield self.store.have_seen_events(
|
seen_remotes = yield self.store.have_seen_events(
|
||||||
[e.event_id for e in result["auth_chain"]]
|
[e.event_id for e in result["auth_chain"]]
|
||||||
|
@ -247,7 +247,14 @@ class IdentityHandler(BaseHandler):
|
|||||||
defer.returnValue(changed)
|
defer.returnValue(changed)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def requestEmailToken(self, id_server, email, client_secret, send_attempt, **kwargs):
|
def requestEmailToken(
|
||||||
|
self,
|
||||||
|
id_server,
|
||||||
|
email,
|
||||||
|
client_secret,
|
||||||
|
send_attempt,
|
||||||
|
next_link=None,
|
||||||
|
):
|
||||||
if not self._should_trust_id_server(id_server):
|
if not self._should_trust_id_server(id_server):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400, "Untrusted ID server '%s'" % id_server,
|
400, "Untrusted ID server '%s'" % id_server,
|
||||||
@ -259,7 +266,9 @@ class IdentityHandler(BaseHandler):
|
|||||||
'client_secret': client_secret,
|
'client_secret': client_secret,
|
||||||
'send_attempt': send_attempt,
|
'send_attempt': send_attempt,
|
||||||
}
|
}
|
||||||
params.update(kwargs)
|
|
||||||
|
if next_link:
|
||||||
|
params.update({'next_link': next_link})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = yield self.http_client.post_json_get_json(
|
data = yield self.http_client.post_json_get_json(
|
||||||
|
@ -158,7 +158,13 @@ class PresenceHandler(object):
|
|||||||
# have not yet been persisted
|
# have not yet been persisted
|
||||||
self.unpersisted_users_changes = set()
|
self.unpersisted_users_changes = set()
|
||||||
|
|
||||||
hs.get_reactor().addSystemEventTrigger("before", "shutdown", self._on_shutdown)
|
hs.get_reactor().addSystemEventTrigger(
|
||||||
|
"before",
|
||||||
|
"shutdown",
|
||||||
|
run_as_background_process,
|
||||||
|
"presence.on_shutdown",
|
||||||
|
self._on_shutdown,
|
||||||
|
)
|
||||||
|
|
||||||
self.serial_to_user = {}
|
self.serial_to_user = {}
|
||||||
self._next_serial = 1
|
self._next_serial = 1
|
||||||
@ -828,14 +834,17 @@ class PresenceHandler(object):
|
|||||||
# joins.
|
# joins.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
event = yield self.store.get_event(event_id)
|
event = yield self.store.get_event(event_id, allow_none=True)
|
||||||
if event.content.get("membership") != Membership.JOIN:
|
if not event or event.content.get("membership") != Membership.JOIN:
|
||||||
# We only care about joins
|
# We only care about joins
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if prev_event_id:
|
if prev_event_id:
|
||||||
prev_event = yield self.store.get_event(prev_event_id)
|
prev_event = yield self.store.get_event(prev_event_id, allow_none=True)
|
||||||
if prev_event.content.get("membership") == Membership.JOIN:
|
if (
|
||||||
|
prev_event
|
||||||
|
and prev_event.content.get("membership") == Membership.JOIN
|
||||||
|
):
|
||||||
# Ignore changes to join events.
|
# Ignore changes to join events.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -115,6 +115,7 @@ class StatsHandler(StateDeltasHandler):
|
|||||||
event_id = delta["event_id"]
|
event_id = delta["event_id"]
|
||||||
stream_id = delta["stream_id"]
|
stream_id = delta["stream_id"]
|
||||||
prev_event_id = delta["prev_event_id"]
|
prev_event_id = delta["prev_event_id"]
|
||||||
|
stream_pos = delta["stream_id"]
|
||||||
|
|
||||||
logger.debug("Handling: %r %r, %s", typ, state_key, event_id)
|
logger.debug("Handling: %r %r, %s", typ, state_key, event_id)
|
||||||
|
|
||||||
@ -136,10 +137,15 @@ class StatsHandler(StateDeltasHandler):
|
|||||||
event_content = {}
|
event_content = {}
|
||||||
|
|
||||||
if event_id is not None:
|
if event_id is not None:
|
||||||
event_content = (yield self.store.get_event(event_id)).content or {}
|
event = yield self.store.get_event(event_id, allow_none=True)
|
||||||
|
if event:
|
||||||
|
event_content = event.content or {}
|
||||||
|
|
||||||
|
# We use stream_pos here rather than fetch by event_id as event_id
|
||||||
|
# may be None
|
||||||
|
now = yield self.store.get_received_ts_by_stream_pos(stream_pos)
|
||||||
|
|
||||||
# quantise time to the nearest bucket
|
# quantise time to the nearest bucket
|
||||||
now = yield self.store.get_received_ts(event_id)
|
|
||||||
now = (now // 1000 // self.stats_bucket_size) * self.stats_bucket_size
|
now = (now // 1000 // self.stats_bucket_size) * self.stats_bucket_size
|
||||||
|
|
||||||
if typ == EventTypes.Member:
|
if typ == EventTypes.Member:
|
||||||
@ -149,9 +155,11 @@ class StatsHandler(StateDeltasHandler):
|
|||||||
# compare them.
|
# compare them.
|
||||||
prev_event_content = {}
|
prev_event_content = {}
|
||||||
if prev_event_id is not None:
|
if prev_event_id is not None:
|
||||||
prev_event_content = (
|
prev_event = yield self.store.get_event(
|
||||||
yield self.store.get_event(prev_event_id)
|
prev_event_id, allow_none=True,
|
||||||
).content
|
)
|
||||||
|
if prev_event:
|
||||||
|
prev_event_content = prev_event.content
|
||||||
|
|
||||||
membership = event_content.get("membership", Membership.LEAVE)
|
membership = event_content.get("membership", Membership.LEAVE)
|
||||||
prev_membership = prev_event_content.get("membership", Membership.LEAVE)
|
prev_membership = prev_event_content.get("membership", Membership.LEAVE)
|
||||||
|
@ -583,30 +583,42 @@ class SyncHandler(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# if the room has a name or canonical_alias set, we can skip
|
# if the room has a name or canonical_alias set, we can skip
|
||||||
# calculating heroes. we assume that if the event has contents, it'll
|
# calculating heroes. Empty strings are falsey, so we check
|
||||||
# be a valid name or canonical_alias - i.e. we're checking that they
|
# for the "name" value and default to an empty string.
|
||||||
# haven't been "deleted" by blatting {} over the top.
|
|
||||||
if name_id:
|
if name_id:
|
||||||
name = yield self.store.get_event(name_id, allow_none=True)
|
name = yield self.store.get_event(name_id, allow_none=True)
|
||||||
if name and name.content:
|
if name and name.content.get("name"):
|
||||||
defer.returnValue(summary)
|
defer.returnValue(summary)
|
||||||
|
|
||||||
if canonical_alias_id:
|
if canonical_alias_id:
|
||||||
canonical_alias = yield self.store.get_event(
|
canonical_alias = yield self.store.get_event(
|
||||||
canonical_alias_id, allow_none=True,
|
canonical_alias_id, allow_none=True,
|
||||||
)
|
)
|
||||||
if canonical_alias and canonical_alias.content:
|
if canonical_alias and canonical_alias.content.get("alias"):
|
||||||
defer.returnValue(summary)
|
defer.returnValue(summary)
|
||||||
|
|
||||||
|
me = sync_config.user.to_string()
|
||||||
|
|
||||||
joined_user_ids = [
|
joined_user_ids = [
|
||||||
r[0] for r in details.get(Membership.JOIN, empty_ms).members
|
r[0]
|
||||||
|
for r in details.get(Membership.JOIN, empty_ms).members
|
||||||
|
if r[0] != me
|
||||||
]
|
]
|
||||||
invited_user_ids = [
|
invited_user_ids = [
|
||||||
r[0] for r in details.get(Membership.INVITE, empty_ms).members
|
r[0]
|
||||||
|
for r in details.get(Membership.INVITE, empty_ms).members
|
||||||
|
if r[0] != me
|
||||||
]
|
]
|
||||||
gone_user_ids = (
|
gone_user_ids = (
|
||||||
[r[0] for r in details.get(Membership.LEAVE, empty_ms).members] +
|
[
|
||||||
[r[0] for r in details.get(Membership.BAN, empty_ms).members]
|
r[0]
|
||||||
|
for r in details.get(Membership.LEAVE, empty_ms).members
|
||||||
|
if r[0] != me
|
||||||
|
] + [
|
||||||
|
r[0]
|
||||||
|
for r in details.get(Membership.BAN, empty_ms).members
|
||||||
|
if r[0] != me
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# FIXME: only build up a member_ids list for our heroes
|
# FIXME: only build up a member_ids list for our heroes
|
||||||
@ -621,22 +633,13 @@ class SyncHandler(object):
|
|||||||
member_ids[user_id] = event_id
|
member_ids[user_id] = event_id
|
||||||
|
|
||||||
# FIXME: order by stream ordering rather than as returned by SQL
|
# FIXME: order by stream ordering rather than as returned by SQL
|
||||||
me = sync_config.user.to_string()
|
|
||||||
if (joined_user_ids or invited_user_ids):
|
if (joined_user_ids or invited_user_ids):
|
||||||
summary['m.heroes'] = sorted(
|
summary['m.heroes'] = sorted(
|
||||||
[
|
[user_id for user_id in (joined_user_ids + invited_user_ids)]
|
||||||
user_id
|
|
||||||
for user_id in (joined_user_ids + invited_user_ids)
|
|
||||||
if user_id != me
|
|
||||||
]
|
|
||||||
)[0:5]
|
)[0:5]
|
||||||
else:
|
else:
|
||||||
summary['m.heroes'] = sorted(
|
summary['m.heroes'] = sorted(
|
||||||
[
|
[user_id for user_id in gone_user_ids]
|
||||||
user_id
|
|
||||||
for user_id in gone_user_ids
|
|
||||||
if user_id != me
|
|
||||||
]
|
|
||||||
)[0:5]
|
)[0:5]
|
||||||
|
|
||||||
if not sync_config.filter_collection.lazy_load_members():
|
if not sync_config.filter_collection.lazy_load_members():
|
||||||
|
@ -285,7 +285,24 @@ class MatrixFederationHttpClient(object):
|
|||||||
request (MatrixFederationRequest): details of request to be sent
|
request (MatrixFederationRequest): details of request to be sent
|
||||||
|
|
||||||
timeout (int|None): number of milliseconds to wait for the response headers
|
timeout (int|None): number of milliseconds to wait for the response headers
|
||||||
(including connecting to the server). 60s by default.
|
(including connecting to the server), *for each attempt*.
|
||||||
|
60s by default.
|
||||||
|
|
||||||
|
long_retries (bool): whether to use the long retry algorithm.
|
||||||
|
|
||||||
|
The regular retry algorithm makes 4 attempts, with intervals
|
||||||
|
[0.5s, 1s, 2s].
|
||||||
|
|
||||||
|
The long retry algorithm makes 11 attempts, with intervals
|
||||||
|
[4s, 16s, 60s, 60s, ...]
|
||||||
|
|
||||||
|
Both algorithms add -20%/+40% jitter to the retry intervals.
|
||||||
|
|
||||||
|
Note that the above intervals are *in addition* to the time spent
|
||||||
|
waiting for the request to complete (up to `timeout` ms).
|
||||||
|
|
||||||
|
NB: the long retry algorithm takes over 20 minutes to complete, with
|
||||||
|
a default timeout of 60s!
|
||||||
|
|
||||||
ignore_backoff (bool): true to ignore the historical backoff data
|
ignore_backoff (bool): true to ignore the historical backoff data
|
||||||
and try the request anyway.
|
and try the request anyway.
|
||||||
@ -566,10 +583,14 @@ class MatrixFederationHttpClient(object):
|
|||||||
the request body. This will be encoded as JSON.
|
the request body. This will be encoded as JSON.
|
||||||
json_data_callback (callable): A callable returning the dict to
|
json_data_callback (callable): A callable returning the dict to
|
||||||
use as the request body.
|
use as the request body.
|
||||||
long_retries (bool): A boolean that indicates whether we should
|
|
||||||
retry for a short or long time.
|
long_retries (bool): whether to use the long retry algorithm. See
|
||||||
timeout(int): How long to try (in ms) the destination for before
|
docs on _send_request for details.
|
||||||
giving up. None indicates no timeout.
|
|
||||||
|
timeout (int|None): number of milliseconds to wait for the response headers
|
||||||
|
(including connecting to the server), *for each attempt*.
|
||||||
|
self._default_timeout (60s) by default.
|
||||||
|
|
||||||
ignore_backoff (bool): true to ignore the historical backoff data
|
ignore_backoff (bool): true to ignore the historical backoff data
|
||||||
and try the request anyway.
|
and try the request anyway.
|
||||||
backoff_on_404 (bool): True if we should count a 404 response as
|
backoff_on_404 (bool): True if we should count a 404 response as
|
||||||
@ -627,15 +648,22 @@ class MatrixFederationHttpClient(object):
|
|||||||
Args:
|
Args:
|
||||||
destination (str): The remote server to send the HTTP request
|
destination (str): The remote server to send the HTTP request
|
||||||
to.
|
to.
|
||||||
|
|
||||||
path (str): The HTTP path.
|
path (str): The HTTP path.
|
||||||
|
|
||||||
data (dict): A dict containing the data that will be used as
|
data (dict): A dict containing the data that will be used as
|
||||||
the request body. This will be encoded as JSON.
|
the request body. This will be encoded as JSON.
|
||||||
long_retries (bool): A boolean that indicates whether we should
|
|
||||||
retry for a short or long time.
|
long_retries (bool): whether to use the long retry algorithm. See
|
||||||
timeout(int): How long to try (in ms) the destination for before
|
docs on _send_request for details.
|
||||||
giving up. None indicates no timeout.
|
|
||||||
|
timeout (int|None): number of milliseconds to wait for the response headers
|
||||||
|
(including connecting to the server), *for each attempt*.
|
||||||
|
self._default_timeout (60s) by default.
|
||||||
|
|
||||||
ignore_backoff (bool): true to ignore the historical backoff data and
|
ignore_backoff (bool): true to ignore the historical backoff data and
|
||||||
try the request anyway.
|
try the request anyway.
|
||||||
|
|
||||||
args (dict): query params
|
args (dict): query params
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[dict|list]: Succeeds when we get a 2xx HTTP response. The
|
Deferred[dict|list]: Succeeds when we get a 2xx HTTP response. The
|
||||||
@ -686,14 +714,19 @@ class MatrixFederationHttpClient(object):
|
|||||||
Args:
|
Args:
|
||||||
destination (str): The remote server to send the HTTP request
|
destination (str): The remote server to send the HTTP request
|
||||||
to.
|
to.
|
||||||
|
|
||||||
path (str): The HTTP path.
|
path (str): The HTTP path.
|
||||||
|
|
||||||
args (dict|None): A dictionary used to create query strings, defaults to
|
args (dict|None): A dictionary used to create query strings, defaults to
|
||||||
None.
|
None.
|
||||||
timeout (int): How long to try (in ms) the destination for before
|
|
||||||
giving up. None indicates no timeout and that the request will
|
timeout (int|None): number of milliseconds to wait for the response headers
|
||||||
be retried.
|
(including connecting to the server), *for each attempt*.
|
||||||
|
self._default_timeout (60s) by default.
|
||||||
|
|
||||||
ignore_backoff (bool): true to ignore the historical backoff data
|
ignore_backoff (bool): true to ignore the historical backoff data
|
||||||
and try the request anyway.
|
and try the request anyway.
|
||||||
|
|
||||||
try_trailing_slash_on_400 (bool): True if on a 400 M_UNRECOGNIZED
|
try_trailing_slash_on_400 (bool): True if on a 400 M_UNRECOGNIZED
|
||||||
response we should try appending a trailing slash to the end of
|
response we should try appending a trailing slash to the end of
|
||||||
the request. Workaround for #3622 in Synapse <= v0.99.3.
|
the request. Workaround for #3622 in Synapse <= v0.99.3.
|
||||||
@ -742,12 +775,18 @@ class MatrixFederationHttpClient(object):
|
|||||||
destination (str): The remote server to send the HTTP request
|
destination (str): The remote server to send the HTTP request
|
||||||
to.
|
to.
|
||||||
path (str): The HTTP path.
|
path (str): The HTTP path.
|
||||||
long_retries (bool): A boolean that indicates whether we should
|
|
||||||
retry for a short or long time.
|
long_retries (bool): whether to use the long retry algorithm. See
|
||||||
timeout(int): How long to try (in ms) the destination for before
|
docs on _send_request for details.
|
||||||
giving up. None indicates no timeout.
|
|
||||||
|
timeout (int|None): number of milliseconds to wait for the response headers
|
||||||
|
(including connecting to the server), *for each attempt*.
|
||||||
|
self._default_timeout (60s) by default.
|
||||||
|
|
||||||
ignore_backoff (bool): true to ignore the historical backoff data and
|
ignore_backoff (bool): true to ignore the historical backoff data and
|
||||||
try the request anyway.
|
try the request anyway.
|
||||||
|
|
||||||
|
args (dict): query params
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[dict|list]: Succeeds when we get a 2xx HTTP response. The
|
Deferred[dict|list]: Succeeds when we get a 2xx HTTP response. The
|
||||||
result will be the decoded JSON body.
|
result will be the decoded JSON body.
|
||||||
|
@ -80,10 +80,10 @@ ALLOWED_ATTRS = {
|
|||||||
|
|
||||||
|
|
||||||
class Mailer(object):
|
class Mailer(object):
|
||||||
def __init__(self, hs, app_name, notif_template_html, notif_template_text):
|
def __init__(self, hs, app_name, template_html, template_text):
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.notif_template_html = notif_template_html
|
self.template_html = template_html
|
||||||
self.notif_template_text = notif_template_text
|
self.template_text = template_text
|
||||||
|
|
||||||
self.sendmail = self.hs.get_sendmail()
|
self.sendmail = self.hs.get_sendmail()
|
||||||
self.store = self.hs.get_datastore()
|
self.store = self.hs.get_datastore()
|
||||||
@ -93,22 +93,49 @@ class Mailer(object):
|
|||||||
|
|
||||||
logger.info("Created Mailer for app_name %s" % app_name)
|
logger.info("Created Mailer for app_name %s" % app_name)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def send_password_reset_mail(
|
||||||
|
self,
|
||||||
|
email_address,
|
||||||
|
token,
|
||||||
|
client_secret,
|
||||||
|
sid,
|
||||||
|
):
|
||||||
|
"""Send an email with a password reset link to a user
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email_address (str): Email address we're sending the password
|
||||||
|
reset to
|
||||||
|
token (str): Unique token generated by the server to verify
|
||||||
|
password reset email was received
|
||||||
|
client_secret (str): Unique token generated by the client to
|
||||||
|
group together multiple email sending attempts
|
||||||
|
sid (str): The generated session ID
|
||||||
|
"""
|
||||||
|
if email.utils.parseaddr(email_address)[1] == '':
|
||||||
|
raise RuntimeError("Invalid 'to' email address")
|
||||||
|
|
||||||
|
link = (
|
||||||
|
self.hs.config.public_baseurl +
|
||||||
|
"_synapse/password_reset/email/submit_token"
|
||||||
|
"?token=%s&client_secret=%s&sid=%s" %
|
||||||
|
(token, client_secret, sid)
|
||||||
|
)
|
||||||
|
|
||||||
|
template_vars = {
|
||||||
|
"link": link,
|
||||||
|
}
|
||||||
|
|
||||||
|
yield self.send_email(
|
||||||
|
email_address,
|
||||||
|
"[%s] Password Reset Email" % self.hs.config.server_name,
|
||||||
|
template_vars,
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def send_notification_mail(self, app_id, user_id, email_address,
|
def send_notification_mail(self, app_id, user_id, email_address,
|
||||||
push_actions, reason):
|
push_actions, reason):
|
||||||
try:
|
"""Send email regarding a user's room notifications"""
|
||||||
from_string = self.hs.config.email_notif_from % {
|
|
||||||
"app": self.app_name
|
|
||||||
}
|
|
||||||
except TypeError:
|
|
||||||
from_string = self.hs.config.email_notif_from
|
|
||||||
|
|
||||||
raw_from = email.utils.parseaddr(from_string)[1]
|
|
||||||
raw_to = email.utils.parseaddr(email_address)[1]
|
|
||||||
|
|
||||||
if raw_to == '':
|
|
||||||
raise RuntimeError("Invalid 'to' address")
|
|
||||||
|
|
||||||
rooms_in_order = deduped_ordered_list(
|
rooms_in_order = deduped_ordered_list(
|
||||||
[pa['room_id'] for pa in push_actions]
|
[pa['room_id'] for pa in push_actions]
|
||||||
)
|
)
|
||||||
@ -176,14 +203,36 @@ class Mailer(object):
|
|||||||
"reason": reason,
|
"reason": reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
html_text = self.notif_template_html.render(**template_vars)
|
yield self.send_email(
|
||||||
|
email_address,
|
||||||
|
"[%s] %s" % (self.app_name, summary_text),
|
||||||
|
template_vars,
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def send_email(self, email_address, subject, template_vars):
|
||||||
|
"""Send an email with the given information and template text"""
|
||||||
|
try:
|
||||||
|
from_string = self.hs.config.email_notif_from % {
|
||||||
|
"app": self.app_name
|
||||||
|
}
|
||||||
|
except TypeError:
|
||||||
|
from_string = self.hs.config.email_notif_from
|
||||||
|
|
||||||
|
raw_from = email.utils.parseaddr(from_string)[1]
|
||||||
|
raw_to = email.utils.parseaddr(email_address)[1]
|
||||||
|
|
||||||
|
if raw_to == '':
|
||||||
|
raise RuntimeError("Invalid 'to' address")
|
||||||
|
|
||||||
|
html_text = self.template_html.render(**template_vars)
|
||||||
html_part = MIMEText(html_text, "html", "utf8")
|
html_part = MIMEText(html_text, "html", "utf8")
|
||||||
|
|
||||||
plain_text = self.notif_template_text.render(**template_vars)
|
plain_text = self.template_text.render(**template_vars)
|
||||||
text_part = MIMEText(plain_text, "plain", "utf8")
|
text_part = MIMEText(plain_text, "plain", "utf8")
|
||||||
|
|
||||||
multipart_msg = MIMEMultipart('alternative')
|
multipart_msg = MIMEMultipart('alternative')
|
||||||
multipart_msg['Subject'] = "[%s] %s" % (self.app_name, summary_text)
|
multipart_msg['Subject'] = subject
|
||||||
multipart_msg['From'] = from_string
|
multipart_msg['From'] = from_string
|
||||||
multipart_msg['To'] = email_address
|
multipart_msg['To'] = email_address
|
||||||
multipart_msg['Date'] = email.utils.formatdate()
|
multipart_msg['Date'] = email.utils.formatdate()
|
||||||
|
@ -70,8 +70,8 @@ class PusherFactory(object):
|
|||||||
mailer = Mailer(
|
mailer = Mailer(
|
||||||
hs=self.hs,
|
hs=self.hs,
|
||||||
app_name=app_name,
|
app_name=app_name,
|
||||||
notif_template_html=self.notif_template_html,
|
template_html=self.notif_template_html,
|
||||||
notif_template_text=self.notif_template_text,
|
template_text=self.notif_template_text,
|
||||||
)
|
)
|
||||||
self.mailers[app_name] = mailer
|
self.mailers[app_name] = mailer
|
||||||
return EmailPusher(self.hs, pusherdict, mailer)
|
return EmailPusher(self.hs, pusherdict, mailer)
|
||||||
|
@ -44,7 +44,10 @@ REQUIREMENTS = [
|
|||||||
"canonicaljson>=1.1.3",
|
"canonicaljson>=1.1.3",
|
||||||
"signedjson>=1.0.0",
|
"signedjson>=1.0.0",
|
||||||
"pynacl>=1.2.1",
|
"pynacl>=1.2.1",
|
||||||
"service_identity>=16.0.0",
|
"idna>=2",
|
||||||
|
|
||||||
|
# validating SSL certs for IP addresses requires service_identity 18.1.
|
||||||
|
"service_identity>=18.1.0",
|
||||||
|
|
||||||
# our logcontext handling relies on the ability to cancel inlineCallbacks
|
# our logcontext handling relies on the ability to cancel inlineCallbacks
|
||||||
# (https://twistedmatrix.com/trac/ticket/4632) which landed in Twisted 18.7.
|
# (https://twistedmatrix.com/trac/ticket/4632) which landed in Twisted 18.7.
|
||||||
@ -77,7 +80,7 @@ REQUIREMENTS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
CONDITIONAL_REQUIREMENTS = {
|
CONDITIONAL_REQUIREMENTS = {
|
||||||
"email.enable_notifs": ["Jinja2>=2.9", "bleach>=1.4.2"],
|
"email": ["Jinja2>=2.9", "bleach>=1.4.2"],
|
||||||
"matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
|
"matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
|
||||||
|
|
||||||
# we use execute_batch, which arrived in psycopg 2.7.
|
# we use execute_batch, which arrived in psycopg 2.7.
|
||||||
|
9
synapse/res/templates/password_reset.html
Normal file
9
synapse/res/templates/password_reset.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>A password reset request has been received for your Matrix account. If this was you, please click the link below to confirm resetting your password:</p>
|
||||||
|
|
||||||
|
<a href="{{ link }}">{{ link }}</a>
|
||||||
|
|
||||||
|
<p>If this was not you, please disregard this email and contact your server administrator. Thank you.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
7
synapse/res/templates/password_reset.txt
Normal file
7
synapse/res/templates/password_reset.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
A password reset request has been received for your Matrix account. If this
|
||||||
|
was you, please click the link below to confirm resetting your password:
|
||||||
|
|
||||||
|
{{ link }}
|
||||||
|
|
||||||
|
If this was not you, please disregard this email and contact your server
|
||||||
|
administrator. Thank you.
|
6
synapse/res/templates/password_reset_failure.html
Normal file
6
synapse/res/templates/password_reset_failure.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<p>{{ failure_reason }}. Your password has not been reset.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
6
synapse/res/templates/password_reset_success.html
Normal file
6
synapse/res/templates/password_reset_success.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<p>Your password was successfully reset. You may now close this window.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,65 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright 2014-2016 OpenMarket Ltd
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""This module contains base REST classes for constructing client v1 servlets.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
|
|
||||||
from synapse.api.urls import CLIENT_API_PREFIX
|
|
||||||
from synapse.http.servlet import RestServlet
|
|
||||||
from synapse.rest.client.transactions import HttpTransactionCache
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def client_path_patterns(path_regex, releases=(0,), include_in_unstable=True):
|
|
||||||
"""Creates a regex compiled client path with the correct client path
|
|
||||||
prefix.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path_regex (str): The regex string to match. This should NOT have a ^
|
|
||||||
as this will be prefixed.
|
|
||||||
Returns:
|
|
||||||
SRE_Pattern
|
|
||||||
"""
|
|
||||||
patterns = [re.compile("^" + CLIENT_API_PREFIX + "/api/v1" + path_regex)]
|
|
||||||
if include_in_unstable:
|
|
||||||
unstable_prefix = CLIENT_API_PREFIX + "/unstable"
|
|
||||||
patterns.append(re.compile("^" + unstable_prefix + path_regex))
|
|
||||||
for release in releases:
|
|
||||||
new_prefix = CLIENT_API_PREFIX + "/r%d" % (release,)
|
|
||||||
patterns.append(re.compile("^" + new_prefix + path_regex))
|
|
||||||
return patterns
|
|
||||||
|
|
||||||
|
|
||||||
class ClientV1RestServlet(RestServlet):
|
|
||||||
"""A base Synapse REST Servlet for the client version 1 API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# This subclass was presumably created to allow the auth for the v1
|
|
||||||
# protocol version to be different, however this behaviour was removed.
|
|
||||||
# it may no longer be necessary
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
hs (synapse.server.HomeServer):
|
|
||||||
"""
|
|
||||||
self.hs = hs
|
|
||||||
self.builder_factory = hs.get_event_builder_factory()
|
|
||||||
self.auth = hs.get_auth()
|
|
||||||
self.txns = HttpTransactionCache(hs)
|
|
@ -19,11 +19,10 @@ import logging
|
|||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
|
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
|
||||||
from synapse.http.servlet import parse_json_object_from_request
|
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.types import RoomAlias
|
from synapse.types import RoomAlias
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -33,13 +32,14 @@ def register_servlets(hs, http_server):
|
|||||||
ClientAppserviceDirectoryListServer(hs).register(http_server)
|
ClientAppserviceDirectoryListServer(hs).register(http_server)
|
||||||
|
|
||||||
|
|
||||||
class ClientDirectoryServer(ClientV1RestServlet):
|
class ClientDirectoryServer(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/directory/room/(?P<room_alias>[^/]*)$")
|
PATTERNS = client_patterns("/directory/room/(?P<room_alias>[^/]*)$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(ClientDirectoryServer, self).__init__(hs)
|
super(ClientDirectoryServer, self).__init__()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_alias):
|
def on_GET(self, request, room_alias):
|
||||||
@ -120,13 +120,14 @@ class ClientDirectoryServer(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
class ClientDirectoryListServer(ClientV1RestServlet):
|
class ClientDirectoryListServer(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/directory/list/room/(?P<room_id>[^/]*)$")
|
PATTERNS = client_patterns("/directory/list/room/(?P<room_id>[^/]*)$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(ClientDirectoryListServer, self).__init__(hs)
|
super(ClientDirectoryListServer, self).__init__()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
@ -162,15 +163,16 @@ class ClientDirectoryListServer(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
class ClientAppserviceDirectoryListServer(ClientV1RestServlet):
|
class ClientAppserviceDirectoryListServer(RestServlet):
|
||||||
PATTERNS = client_path_patterns(
|
PATTERNS = client_patterns(
|
||||||
"/directory/list/appservice/(?P<network_id>[^/]*)/(?P<room_id>[^/]*)$"
|
"/directory/list/appservice/(?P<network_id>[^/]*)/(?P<room_id>[^/]*)$", v1=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(ClientAppserviceDirectoryListServer, self).__init__(hs)
|
super(ClientAppserviceDirectoryListServer, self).__init__()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def on_PUT(self, request, network_id, room_id):
|
def on_PUT(self, request, network_id, room_id):
|
||||||
content = parse_json_object_from_request(request)
|
content = parse_json_object_from_request(request)
|
||||||
|
@ -19,21 +19,22 @@ import logging
|
|||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
|
from synapse.http.servlet import RestServlet
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EventStreamRestServlet(ClientV1RestServlet):
|
class EventStreamRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/events$")
|
PATTERNS = client_patterns("/events$", v1=True)
|
||||||
|
|
||||||
DEFAULT_LONGPOLL_TIME_MS = 30000
|
DEFAULT_LONGPOLL_TIME_MS = 30000
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(EventStreamRestServlet, self).__init__(hs)
|
super(EventStreamRestServlet, self).__init__()
|
||||||
self.event_stream_handler = hs.get_event_stream_handler()
|
self.event_stream_handler = hs.get_event_stream_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
@ -76,11 +77,11 @@ class EventStreamRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Unit test gets, with and without auth, with different kinds of events.
|
# TODO: Unit test gets, with and without auth, with different kinds of events.
|
||||||
class EventRestServlet(ClientV1RestServlet):
|
class EventRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/events/(?P<event_id>[^/]*)$")
|
PATTERNS = client_patterns("/events/(?P<event_id>[^/]*)$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(EventRestServlet, self).__init__(hs)
|
super(EventRestServlet, self).__init__()
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
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()
|
||||||
|
@ -15,19 +15,19 @@
|
|||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.http.servlet import parse_boolean
|
from synapse.http.servlet import RestServlet, parse_boolean
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class InitialSyncRestServlet(ClientV1RestServlet):
|
class InitialSyncRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/initialSync$")
|
PATTERNS = client_patterns("/initialSync$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(InitialSyncRestServlet, self).__init__(hs)
|
super(InitialSyncRestServlet, self).__init__()
|
||||||
self.initial_sync_handler = hs.get_initial_sync_handler()
|
self.initial_sync_handler = hs.get_initial_sync_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
|
@ -29,6 +29,7 @@ from synapse.http.servlet import (
|
|||||||
parse_json_object_from_request,
|
parse_json_object_from_request,
|
||||||
parse_string,
|
parse_string,
|
||||||
)
|
)
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.rest.well_known import WellKnownBuilder
|
from synapse.rest.well_known import WellKnownBuilder
|
||||||
from synapse.types import UserID, map_username_to_mxid_localpart
|
from synapse.types import UserID, map_username_to_mxid_localpart
|
||||||
from synapse.util.msisdn import phone_number_to_msisdn
|
from synapse.util.msisdn import phone_number_to_msisdn
|
||||||
@ -36,7 +37,6 @@ from synapse.util.msisdn import phone_number_to_msisdn
|
|||||||
import saml2
|
import saml2
|
||||||
from saml2.client import Saml2Client
|
from saml2.client import Saml2Client
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -84,15 +84,16 @@ def login_id_thirdparty_from_phone(identifier):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LoginRestServlet(ClientV1RestServlet):
|
class LoginRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/login$")
|
PATTERNS = client_patterns("/login$", v1=True)
|
||||||
CAS_TYPE = "m.login.cas"
|
CAS_TYPE = "m.login.cas"
|
||||||
SSO_TYPE = "m.login.sso"
|
SSO_TYPE = "m.login.sso"
|
||||||
TOKEN_TYPE = "m.login.token"
|
TOKEN_TYPE = "m.login.token"
|
||||||
JWT_TYPE = "m.login.jwt"
|
JWT_TYPE = "m.login.jwt"
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(LoginRestServlet, self).__init__(hs)
|
super(LoginRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
self.jwt_enabled = hs.config.jwt_enabled
|
self.jwt_enabled = hs.config.jwt_enabled
|
||||||
self.jwt_secret = hs.config.jwt_secret
|
self.jwt_secret = hs.config.jwt_secret
|
||||||
self.jwt_algorithm = hs.config.jwt_algorithm
|
self.jwt_algorithm = hs.config.jwt_algorithm
|
||||||
@ -378,7 +379,7 @@ class LoginRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
class CasRedirectServlet(RestServlet):
|
class CasRedirectServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/login/(cas|sso)/redirect")
|
PATTERNS = client_patterns("/login/(cas|sso)/redirect", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(CasRedirectServlet, self).__init__()
|
super(CasRedirectServlet, self).__init__()
|
||||||
@ -401,27 +402,27 @@ class CasRedirectServlet(RestServlet):
|
|||||||
finish_request(request)
|
finish_request(request)
|
||||||
|
|
||||||
|
|
||||||
class CasTicketServlet(ClientV1RestServlet):
|
class CasTicketServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/login/cas/ticket")
|
PATTERNS = client_patterns("/login/cas/ticket", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(CasTicketServlet, self).__init__(hs)
|
super(CasTicketServlet, self).__init__()
|
||||||
self.cas_server_url = hs.config.cas_server_url
|
self.cas_server_url = hs.config.cas_server_url
|
||||||
self.cas_service_url = hs.config.cas_service_url
|
self.cas_service_url = hs.config.cas_service_url
|
||||||
self.cas_required_attributes = hs.config.cas_required_attributes
|
self.cas_required_attributes = hs.config.cas_required_attributes
|
||||||
self._sso_auth_handler = SSOAuthHandler(hs)
|
self._sso_auth_handler = SSOAuthHandler(hs)
|
||||||
|
self._http_client = hs.get_simple_http_client()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
client_redirect_url = parse_string(request, "redirectUrl", required=True)
|
client_redirect_url = parse_string(request, "redirectUrl", required=True)
|
||||||
http_client = self.hs.get_simple_http_client()
|
|
||||||
uri = self.cas_server_url + "/proxyValidate"
|
uri = self.cas_server_url + "/proxyValidate"
|
||||||
args = {
|
args = {
|
||||||
"ticket": parse_string(request, "ticket", required=True),
|
"ticket": parse_string(request, "ticket", required=True),
|
||||||
"service": self.cas_service_url
|
"service": self.cas_service_url
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
body = yield http_client.get_raw(uri, args)
|
body = yield self._http_client.get_raw(uri, args)
|
||||||
except PartialDownloadError as pde:
|
except PartialDownloadError as pde:
|
||||||
# Twisted raises this error if the connection is closed,
|
# Twisted raises this error if the connection is closed,
|
||||||
# even if that's being used old-http style to signal end-of-data
|
# even if that's being used old-http style to signal end-of-data
|
||||||
@ -482,7 +483,7 @@ class CasTicketServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
class SSORedirectServlet(RestServlet):
|
class SSORedirectServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/login/sso/redirect")
|
PATTERNS = client_patterns("/login/sso/redirect", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(SSORedirectServlet, self).__init__()
|
super(SSORedirectServlet, self).__init__()
|
||||||
|
@ -17,17 +17,18 @@ import logging
|
|||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
from synapse.http.servlet import RestServlet
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LogoutRestServlet(ClientV1RestServlet):
|
class LogoutRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/logout$")
|
PATTERNS = client_patterns("/logout$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(LogoutRestServlet, self).__init__(hs)
|
super(LogoutRestServlet, self).__init__()
|
||||||
self._auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
self._auth_handler = hs.get_auth_handler()
|
self._auth_handler = hs.get_auth_handler()
|
||||||
self._device_handler = hs.get_device_handler()
|
self._device_handler = hs.get_device_handler()
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ class LogoutRestServlet(ClientV1RestServlet):
|
|||||||
if requester.device_id is None:
|
if requester.device_id is None:
|
||||||
# the acccess token wasn't associated with a device.
|
# the acccess token wasn't associated with a device.
|
||||||
# Just delete the access token
|
# Just delete the access token
|
||||||
access_token = self._auth.get_access_token_from_request(request)
|
access_token = self.auth.get_access_token_from_request(request)
|
||||||
yield self._auth_handler.delete_access_token(access_token)
|
yield self._auth_handler.delete_access_token(access_token)
|
||||||
else:
|
else:
|
||||||
yield self._device_handler.delete_device(
|
yield self._device_handler.delete_device(
|
||||||
@ -50,11 +51,11 @@ class LogoutRestServlet(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
class LogoutAllRestServlet(ClientV1RestServlet):
|
class LogoutAllRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/logout/all$")
|
PATTERNS = client_patterns("/logout/all$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(LogoutAllRestServlet, self).__init__(hs)
|
super(LogoutAllRestServlet, self).__init__()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
self._auth_handler = hs.get_auth_handler()
|
self._auth_handler = hs.get_auth_handler()
|
||||||
self._device_handler = hs.get_device_handler()
|
self._device_handler = hs.get_device_handler()
|
||||||
|
@ -23,21 +23,22 @@ from twisted.internet import defer
|
|||||||
|
|
||||||
from synapse.api.errors import AuthError, SynapseError
|
from synapse.api.errors import AuthError, SynapseError
|
||||||
from synapse.handlers.presence import format_user_presence_state
|
from synapse.handlers.presence import format_user_presence_state
|
||||||
from synapse.http.servlet import parse_json_object_from_request
|
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PresenceStatusRestServlet(ClientV1RestServlet):
|
class PresenceStatusRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/presence/(?P<user_id>[^/]*)/status")
|
PATTERNS = client_patterns("/presence/(?P<user_id>[^/]*)/status", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(PresenceStatusRestServlet, self).__init__(hs)
|
super(PresenceStatusRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
self.presence_handler = hs.get_presence_handler()
|
self.presence_handler = hs.get_presence_handler()
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
@ -16,18 +16,19 @@
|
|||||||
""" This module contains REST servlets to do with profile: /profile/<paths> """
|
""" This module contains REST servlets to do with profile: /profile/<paths> """
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.http.servlet import parse_json_object_from_request
|
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
|
class ProfileDisplaynameRestServlet(RestServlet):
|
||||||
class ProfileDisplaynameRestServlet(ClientV1RestServlet):
|
PATTERNS = client_patterns("/profile/(?P<user_id>[^/]*)/displayname", v1=True)
|
||||||
PATTERNS = client_path_patterns("/profile/(?P<user_id>[^/]*)/displayname")
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(ProfileDisplaynameRestServlet, self).__init__(hs)
|
super(ProfileDisplaynameRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
self.profile_handler = hs.get_profile_handler()
|
self.profile_handler = hs.get_profile_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
@ -71,12 +72,14 @@ class ProfileDisplaynameRestServlet(ClientV1RestServlet):
|
|||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
class ProfileAvatarURLRestServlet(ClientV1RestServlet):
|
class ProfileAvatarURLRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/profile/(?P<user_id>[^/]*)/avatar_url")
|
PATTERNS = client_patterns("/profile/(?P<user_id>[^/]*)/avatar_url", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(ProfileAvatarURLRestServlet, self).__init__(hs)
|
super(ProfileAvatarURLRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
self.profile_handler = hs.get_profile_handler()
|
self.profile_handler = hs.get_profile_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
@ -119,12 +122,14 @@ class ProfileAvatarURLRestServlet(ClientV1RestServlet):
|
|||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
class ProfileRestServlet(ClientV1RestServlet):
|
class ProfileRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/profile/(?P<user_id>[^/]*)")
|
PATTERNS = client_patterns("/profile/(?P<user_id>[^/]*)", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(ProfileRestServlet, self).__init__(hs)
|
super(ProfileRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
self.profile_handler = hs.get_profile_handler()
|
self.profile_handler = hs.get_profile_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
@ -21,22 +21,22 @@ from synapse.api.errors import (
|
|||||||
SynapseError,
|
SynapseError,
|
||||||
UnrecognizedRequestError,
|
UnrecognizedRequestError,
|
||||||
)
|
)
|
||||||
from synapse.http.servlet import parse_json_value_from_request, parse_string
|
from synapse.http.servlet import RestServlet, parse_json_value_from_request, parse_string
|
||||||
from synapse.push.baserules import BASE_RULE_IDS
|
from synapse.push.baserules import BASE_RULE_IDS
|
||||||
from synapse.push.clientformat import format_push_rules_for_user
|
from synapse.push.clientformat import format_push_rules_for_user
|
||||||
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
|
from synapse.push.rulekinds import PRIORITY_CLASS_MAP
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.storage.push_rule import InconsistentRuleException, RuleNotFoundException
|
from synapse.storage.push_rule import InconsistentRuleException, RuleNotFoundException
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
|
class PushRuleRestServlet(RestServlet):
|
||||||
class PushRuleRestServlet(ClientV1RestServlet):
|
PATTERNS = client_patterns("/(?P<path>pushrules/.*)$", v1=True)
|
||||||
PATTERNS = client_path_patterns("/(?P<path>pushrules/.*)$")
|
|
||||||
SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR = (
|
SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR = (
|
||||||
"Unrecognised request: You probably wanted a trailing slash")
|
"Unrecognised request: You probably wanted a trailing slash")
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(PushRuleRestServlet, self).__init__(hs)
|
super(PushRuleRestServlet, self).__init__()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
self.notifier = hs.get_notifier()
|
self.notifier = hs.get_notifier()
|
||||||
self._is_worker = hs.config.worker_app is not None
|
self._is_worker = hs.config.worker_app is not None
|
||||||
|
@ -26,17 +26,18 @@ from synapse.http.servlet import (
|
|||||||
parse_string,
|
parse_string,
|
||||||
)
|
)
|
||||||
from synapse.push import PusherConfigException
|
from synapse.push import PusherConfigException
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PushersRestServlet(ClientV1RestServlet):
|
class PushersRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/pushers$")
|
PATTERNS = client_patterns("/pushers$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(PushersRestServlet, self).__init__(hs)
|
super(PushersRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
@ -69,11 +70,13 @@ class PushersRestServlet(ClientV1RestServlet):
|
|||||||
return 200, {}
|
return 200, {}
|
||||||
|
|
||||||
|
|
||||||
class PushersSetRestServlet(ClientV1RestServlet):
|
class PushersSetRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/pushers/set$")
|
PATTERNS = client_patterns("/pushers/set$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(PushersSetRestServlet, self).__init__(hs)
|
super(PushersSetRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
|
self.auth = hs.get_auth()
|
||||||
self.notifier = hs.get_notifier()
|
self.notifier = hs.get_notifier()
|
||||||
self.pusher_pool = self.hs.get_pusherpool()
|
self.pusher_pool = self.hs.get_pusherpool()
|
||||||
|
|
||||||
@ -141,7 +144,7 @@ class PushersRemoveRestServlet(RestServlet):
|
|||||||
"""
|
"""
|
||||||
To allow pusher to be delete by clicking a link (ie. GET request)
|
To allow pusher to be delete by clicking a link (ie. GET request)
|
||||||
"""
|
"""
|
||||||
PATTERNS = client_path_patterns("/pushers/remove$")
|
PATTERNS = client_patterns("/pushers/remove$", v1=True)
|
||||||
SUCCESS_HTML = b"<html><body>You have been unsubscribed</body><html>"
|
SUCCESS_HTML = b"<html><body>You have been unsubscribed</body><html>"
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
@ -28,37 +28,45 @@ from synapse.api.errors import AuthError, Codes, SynapseError
|
|||||||
from synapse.api.filtering import Filter
|
from synapse.api.filtering import Filter
|
||||||
from synapse.events.utils import format_event_for_client_v2
|
from synapse.events.utils import format_event_for_client_v2
|
||||||
from synapse.http.servlet import (
|
from synapse.http.servlet import (
|
||||||
|
RestServlet,
|
||||||
assert_params_in_dict,
|
assert_params_in_dict,
|
||||||
parse_integer,
|
parse_integer,
|
||||||
parse_json_object_from_request,
|
parse_json_object_from_request,
|
||||||
parse_string,
|
parse_string,
|
||||||
)
|
)
|
||||||
|
from synapse.rest.client.transactions import HttpTransactionCache
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
from synapse.storage.state import StateFilter
|
from synapse.storage.state import StateFilter
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
from synapse.types import RoomAlias, RoomID, StreamToken, ThirdPartyInstanceID, UserID
|
from synapse.types import RoomAlias, RoomID, StreamToken, ThirdPartyInstanceID, UserID
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RoomCreateRestServlet(ClientV1RestServlet):
|
class TransactionRestServlet(RestServlet):
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(TransactionRestServlet, self).__init__()
|
||||||
|
self.txns = HttpTransactionCache(hs)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomCreateRestServlet(TransactionRestServlet):
|
||||||
# No PATTERN; we have custom dispatch rules here
|
# No PATTERN; we have custom dispatch rules here
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomCreateRestServlet, self).__init__(hs)
|
super(RoomCreateRestServlet, self).__init__(hs)
|
||||||
self._room_creation_handler = hs.get_room_creation_handler()
|
self._room_creation_handler = hs.get_room_creation_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
PATTERNS = "/createRoom"
|
PATTERNS = "/createRoom"
|
||||||
register_txn_path(self, PATTERNS, http_server)
|
register_txn_path(self, PATTERNS, http_server)
|
||||||
# define CORS for all of /rooms in RoomCreateRestServlet for simplicity
|
# define CORS for all of /rooms in RoomCreateRestServlet for simplicity
|
||||||
http_server.register_paths("OPTIONS",
|
http_server.register_paths("OPTIONS",
|
||||||
client_path_patterns("/rooms(?:/.*)?$"),
|
client_patterns("/rooms(?:/.*)?$", v1=True),
|
||||||
self.on_OPTIONS)
|
self.on_OPTIONS)
|
||||||
# define CORS for /createRoom[/txnid]
|
# define CORS for /createRoom[/txnid]
|
||||||
http_server.register_paths("OPTIONS",
|
http_server.register_paths("OPTIONS",
|
||||||
client_path_patterns("/createRoom(?:/.*)?$"),
|
client_patterns("/createRoom(?:/.*)?$", v1=True),
|
||||||
self.on_OPTIONS)
|
self.on_OPTIONS)
|
||||||
|
|
||||||
def on_PUT(self, request, txn_id):
|
def on_PUT(self, request, txn_id):
|
||||||
@ -85,13 +93,14 @@ class RoomCreateRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for generic events
|
# TODO: Needs unit testing for generic events
|
||||||
class RoomStateEventRestServlet(ClientV1RestServlet):
|
class RoomStateEventRestServlet(TransactionRestServlet):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomStateEventRestServlet, self).__init__(hs)
|
super(RoomStateEventRestServlet, self).__init__(hs)
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
self.message_handler = hs.get_message_handler()
|
self.message_handler = hs.get_message_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /room/$roomid/state/$eventtype
|
# /room/$roomid/state/$eventtype
|
||||||
@ -102,16 +111,16 @@ class RoomStateEventRestServlet(ClientV1RestServlet):
|
|||||||
"(?P<event_type>[^/]*)/(?P<state_key>[^/]*)$")
|
"(?P<event_type>[^/]*)/(?P<state_key>[^/]*)$")
|
||||||
|
|
||||||
http_server.register_paths("GET",
|
http_server.register_paths("GET",
|
||||||
client_path_patterns(state_key),
|
client_patterns(state_key, v1=True),
|
||||||
self.on_GET)
|
self.on_GET)
|
||||||
http_server.register_paths("PUT",
|
http_server.register_paths("PUT",
|
||||||
client_path_patterns(state_key),
|
client_patterns(state_key, v1=True),
|
||||||
self.on_PUT)
|
self.on_PUT)
|
||||||
http_server.register_paths("GET",
|
http_server.register_paths("GET",
|
||||||
client_path_patterns(no_state_key),
|
client_patterns(no_state_key, v1=True),
|
||||||
self.on_GET_no_state_key)
|
self.on_GET_no_state_key)
|
||||||
http_server.register_paths("PUT",
|
http_server.register_paths("PUT",
|
||||||
client_path_patterns(no_state_key),
|
client_patterns(no_state_key, v1=True),
|
||||||
self.on_PUT_no_state_key)
|
self.on_PUT_no_state_key)
|
||||||
|
|
||||||
def on_GET_no_state_key(self, request, room_id, event_type):
|
def on_GET_no_state_key(self, request, room_id, event_type):
|
||||||
@ -185,11 +194,12 @@ class RoomStateEventRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for generic events + feedback
|
# TODO: Needs unit testing for generic events + feedback
|
||||||
class RoomSendEventRestServlet(ClientV1RestServlet):
|
class RoomSendEventRestServlet(TransactionRestServlet):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomSendEventRestServlet, self).__init__(hs)
|
super(RoomSendEventRestServlet, self).__init__(hs)
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms/$roomid/send/$event_type[/$txn_id]
|
# /rooms/$roomid/send/$event_type[/$txn_id]
|
||||||
@ -229,10 +239,11 @@ class RoomSendEventRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing for room ID + alias joins
|
# TODO: Needs unit testing for room ID + alias joins
|
||||||
class JoinRoomAliasServlet(ClientV1RestServlet):
|
class JoinRoomAliasServlet(TransactionRestServlet):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(JoinRoomAliasServlet, self).__init__(hs)
|
super(JoinRoomAliasServlet, self).__init__(hs)
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /join/$room_identifier[/$txn_id]
|
# /join/$room_identifier[/$txn_id]
|
||||||
@ -291,8 +302,13 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class PublicRoomListRestServlet(ClientV1RestServlet):
|
class PublicRoomListRestServlet(TransactionRestServlet):
|
||||||
PATTERNS = client_path_patterns("/publicRooms$")
|
PATTERNS = client_patterns("/publicRooms$", v1=True)
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(PublicRoomListRestServlet, self).__init__(hs)
|
||||||
|
self.hs = hs
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
@ -382,12 +398,13 @@ class PublicRoomListRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomMemberListRestServlet(ClientV1RestServlet):
|
class RoomMemberListRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/members$")
|
PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/members$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomMemberListRestServlet, self).__init__(hs)
|
super(RoomMemberListRestServlet, self).__init__()
|
||||||
self.message_handler = hs.get_message_handler()
|
self.message_handler = hs.get_message_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
@ -436,12 +453,13 @@ class RoomMemberListRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
# deprecated in favour of /members?membership=join?
|
# deprecated in favour of /members?membership=join?
|
||||||
# except it does custom AS logic and has a simpler return format
|
# except it does custom AS logic and has a simpler return format
|
||||||
class JoinedRoomMemberListRestServlet(ClientV1RestServlet):
|
class JoinedRoomMemberListRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/joined_members$")
|
PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/joined_members$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(JoinedRoomMemberListRestServlet, self).__init__(hs)
|
super(JoinedRoomMemberListRestServlet, self).__init__()
|
||||||
self.message_handler = hs.get_message_handler()
|
self.message_handler = hs.get_message_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
@ -457,12 +475,13 @@ class JoinedRoomMemberListRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs better unit testing
|
# TODO: Needs better unit testing
|
||||||
class RoomMessageListRestServlet(ClientV1RestServlet):
|
class RoomMessageListRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/messages$")
|
PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/messages$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomMessageListRestServlet, self).__init__(hs)
|
super(RoomMessageListRestServlet, self).__init__()
|
||||||
self.pagination_handler = hs.get_pagination_handler()
|
self.pagination_handler = hs.get_pagination_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
@ -491,12 +510,13 @@ class RoomMessageListRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomStateRestServlet(ClientV1RestServlet):
|
class RoomStateRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/state$")
|
PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/state$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomStateRestServlet, self).__init__(hs)
|
super(RoomStateRestServlet, self).__init__()
|
||||||
self.message_handler = hs.get_message_handler()
|
self.message_handler = hs.get_message_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
@ -511,12 +531,13 @@ class RoomStateRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomInitialSyncRestServlet(ClientV1RestServlet):
|
class RoomInitialSyncRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/initialSync$")
|
PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/initialSync$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomInitialSyncRestServlet, self).__init__(hs)
|
super(RoomInitialSyncRestServlet, self).__init__()
|
||||||
self.initial_sync_handler = hs.get_initial_sync_handler()
|
self.initial_sync_handler = hs.get_initial_sync_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id):
|
def on_GET(self, request, room_id):
|
||||||
@ -530,16 +551,17 @@ class RoomInitialSyncRestServlet(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, content))
|
defer.returnValue((200, content))
|
||||||
|
|
||||||
|
|
||||||
class RoomEventServlet(ClientV1RestServlet):
|
class RoomEventServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns(
|
PATTERNS = client_patterns(
|
||||||
"/rooms/(?P<room_id>[^/]*)/event/(?P<event_id>[^/]*)$"
|
"/rooms/(?P<room_id>[^/]*)/event/(?P<event_id>[^/]*)$", v1=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomEventServlet, self).__init__(hs)
|
super(RoomEventServlet, self).__init__()
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
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.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id, event_id):
|
def on_GET(self, request, room_id, event_id):
|
||||||
@ -554,16 +576,17 @@ class RoomEventServlet(ClientV1RestServlet):
|
|||||||
defer.returnValue((404, "Event not found."))
|
defer.returnValue((404, "Event not found."))
|
||||||
|
|
||||||
|
|
||||||
class RoomEventContextServlet(ClientV1RestServlet):
|
class RoomEventContextServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns(
|
PATTERNS = client_patterns(
|
||||||
"/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$"
|
"/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$", v1=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomEventContextServlet, self).__init__(hs)
|
super(RoomEventContextServlet, self).__init__()
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.room_context_handler = hs.get_room_context_handler()
|
self.room_context_handler = hs.get_room_context_handler()
|
||||||
self._event_serializer = hs.get_event_client_serializer()
|
self._event_serializer = hs.get_event_client_serializer()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, room_id, event_id):
|
def on_GET(self, request, room_id, event_id):
|
||||||
@ -609,10 +632,11 @@ class RoomEventContextServlet(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, results))
|
defer.returnValue((200, results))
|
||||||
|
|
||||||
|
|
||||||
class RoomForgetRestServlet(ClientV1RestServlet):
|
class RoomForgetRestServlet(TransactionRestServlet):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomForgetRestServlet, self).__init__(hs)
|
super(RoomForgetRestServlet, self).__init__(hs)
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
PATTERNS = ("/rooms/(?P<room_id>[^/]*)/forget")
|
PATTERNS = ("/rooms/(?P<room_id>[^/]*)/forget")
|
||||||
@ -639,11 +663,12 @@ class RoomForgetRestServlet(ClientV1RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
class RoomMembershipRestServlet(ClientV1RestServlet):
|
class RoomMembershipRestServlet(TransactionRestServlet):
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomMembershipRestServlet, self).__init__(hs)
|
super(RoomMembershipRestServlet, self).__init__(hs)
|
||||||
self.room_member_handler = hs.get_room_member_handler()
|
self.room_member_handler = hs.get_room_member_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms/$roomid/[invite|join|leave]
|
# /rooms/$roomid/[invite|join|leave]
|
||||||
@ -722,11 +747,12 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RoomRedactEventRestServlet(ClientV1RestServlet):
|
class RoomRedactEventRestServlet(TransactionRestServlet):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomRedactEventRestServlet, self).__init__(hs)
|
super(RoomRedactEventRestServlet, self).__init__(hs)
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
self.event_creation_handler = hs.get_event_creation_handler()
|
self.event_creation_handler = hs.get_event_creation_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
PATTERNS = ("/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)")
|
PATTERNS = ("/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)")
|
||||||
@ -757,15 +783,16 @@ class RoomRedactEventRestServlet(ClientV1RestServlet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RoomTypingRestServlet(ClientV1RestServlet):
|
class RoomTypingRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns(
|
PATTERNS = client_patterns(
|
||||||
"/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$"
|
"/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$", v1=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(RoomTypingRestServlet, self).__init__(hs)
|
super(RoomTypingRestServlet, self).__init__()
|
||||||
self.presence_handler = hs.get_presence_handler()
|
self.presence_handler = hs.get_presence_handler()
|
||||||
self.typing_handler = hs.get_typing_handler()
|
self.typing_handler = hs.get_typing_handler()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_id, user_id):
|
def on_PUT(self, request, room_id, user_id):
|
||||||
@ -798,14 +825,13 @@ class RoomTypingRestServlet(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
class SearchRestServlet(ClientV1RestServlet):
|
class SearchRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns(
|
PATTERNS = client_patterns("/search$", v1=True)
|
||||||
"/search$"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(SearchRestServlet, self).__init__(hs)
|
super(SearchRestServlet, self).__init__()
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request):
|
def on_POST(self, request):
|
||||||
@ -823,12 +849,13 @@ class SearchRestServlet(ClientV1RestServlet):
|
|||||||
defer.returnValue((200, results))
|
defer.returnValue((200, results))
|
||||||
|
|
||||||
|
|
||||||
class JoinedRoomsRestServlet(ClientV1RestServlet):
|
class JoinedRoomsRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/joined_rooms$")
|
PATTERNS = client_patterns("/joined_rooms$", v1=True)
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
super(JoinedRoomsRestServlet, self).__init__(hs)
|
super(JoinedRoomsRestServlet, self).__init__()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
@ -853,18 +880,18 @@ def register_txn_path(servlet, regex_string, http_server, with_get=False):
|
|||||||
"""
|
"""
|
||||||
http_server.register_paths(
|
http_server.register_paths(
|
||||||
"POST",
|
"POST",
|
||||||
client_path_patterns(regex_string + "$"),
|
client_patterns(regex_string + "$", v1=True),
|
||||||
servlet.on_POST
|
servlet.on_POST
|
||||||
)
|
)
|
||||||
http_server.register_paths(
|
http_server.register_paths(
|
||||||
"PUT",
|
"PUT",
|
||||||
client_path_patterns(regex_string + "/(?P<txn_id>[^/]*)$"),
|
client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
|
||||||
servlet.on_PUT
|
servlet.on_PUT
|
||||||
)
|
)
|
||||||
if with_get:
|
if with_get:
|
||||||
http_server.register_paths(
|
http_server.register_paths(
|
||||||
"GET",
|
"GET",
|
||||||
client_path_patterns(regex_string + "/(?P<txn_id>[^/]*)$"),
|
client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
|
||||||
servlet.on_GET
|
servlet.on_GET
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,11 +19,17 @@ import hmac
|
|||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from .base import ClientV1RestServlet, client_path_patterns
|
from synapse.http.servlet import RestServlet
|
||||||
|
from synapse.rest.client.v2_alpha._base import client_patterns
|
||||||
|
|
||||||
|
|
||||||
class VoipRestServlet(ClientV1RestServlet):
|
class VoipRestServlet(RestServlet):
|
||||||
PATTERNS = client_path_patterns("/voip/turnServer$")
|
PATTERNS = client_patterns("/voip/turnServer$", v1=True)
|
||||||
|
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(VoipRestServlet, self).__init__()
|
||||||
|
self.hs = hs
|
||||||
|
self.auth = hs.get_auth()
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user