Merge remote-tracking branch 'origin/develop' into clokep/ip-blacklists

pull/8821/head
Patrick Cloke 2020-11-30 15:24:34 -05:00
commit d5ba7a927f
57 changed files with 773 additions and 286 deletions

View File

@ -105,6 +105,28 @@ shown below:
return {"localpart": localpart} return {"localpart": localpart}
Removal historical Synapse Admin API
------------------------------------
Historically, the Synapse Admin API has been accessible under:
* ``/_matrix/client/api/v1/admin``
* ``/_matrix/client/unstable/admin``
* ``/_matrix/client/r0/admin``
* ``/_synapse/admin/v1``
The endpoints with ``/_matrix/client/*`` prefixes have been removed as of v1.24.0.
The Admin API is now only accessible under:
* ``/_synapse/admin/v1``
The only exception is the `/admin/whois` endpoint, which is
`also available via the client-server API <https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_.
The deprecation of the old endpoints was announced with Synapse 1.20.0 (released
on 2020-09-22) and makes it easier for homeserver admins to lock down external
access to the Admin API endpoints.
Upgrading to v1.23.0 Upgrading to v1.23.0
==================== ====================

1
changelog.d/8565.misc Normal file
View File

@ -0,0 +1 @@
Simplify the way the `HomeServer` object caches its internal attributes.

1
changelog.d/8785.removal Normal file
View File

@ -0,0 +1 @@
Remove old `/_matrix/client/*/admin` endpoints which was deprecated since Synapse 1.20.0.

1
changelog.d/8799.bugfix Normal file
View File

@ -0,0 +1 @@
Allow per-room profiles to be used for the server notice user.

1
changelog.d/8809.misc Normal file
View File

@ -0,0 +1 @@
Remove unnecessary function arguments and add typing to several membership replication classes.

1
changelog.d/8815.misc Normal file
View File

@ -0,0 +1 @@
Optimise the lookup for an invite from another homeserver when trying to reject it.

1
changelog.d/8817.bugfix Normal file
View File

@ -0,0 +1 @@
Fix bug where logging could break after a call to SIGHUP.

1
changelog.d/8818.doc Normal file
View File

@ -0,0 +1 @@
Update the formatting of the `push` section of the homeserver config file to better align with the [code style guidelines](https://github.com/matrix-org/synapse/blob/develop/docs/code_style.md#configuration-file-format).

1
changelog.d/8820.feature Normal file
View File

@ -0,0 +1 @@
Add a config option, `push.group_by_unread_count`, which controls whether unread message counts in push notifications are defined as "the number of rooms with unread messages" or "total unread messages".

1
changelog.d/8822.doc Normal file
View File

@ -0,0 +1 @@
Improve documentation how to configure prometheus for workers.

1
changelog.d/8823.bugfix Normal file
View File

@ -0,0 +1 @@
Fix `register_new_matrix_user` failing with "Bad Request" when trailing slash is included in server URL. Contributed by @angdraug.

1
changelog.d/8824.doc Normal file
View File

@ -0,0 +1 @@
Update example prometheus console.

1
changelog.d/8833.removal Normal file
View File

@ -0,0 +1 @@
Disable pretty printing JSON responses for curl. Users who want pretty-printed output should use [jq](https://stedolan.github.io/jq/) in combination with curl. Contributed by @tulir.

1
changelog.d/8843.feature Normal file
View File

@ -0,0 +1 @@
Add `force_purge` option to delete-room admin api.

1
changelog.d/8845.misc Normal file
View File

@ -0,0 +1 @@
Drop redundant database index on `event_json`.

1
changelog.d/8847.misc Normal file
View File

@ -0,0 +1 @@
Simplify `uk.half-shot.msc2778.login.application_service` login handler.

View File

@ -20,6 +20,7 @@ Add a new job to the main prometheus.conf file:
``` ```
### for Prometheus v2 ### for Prometheus v2
Add a new job to the main prometheus.yml file: Add a new job to the main prometheus.yml file:
```yaml ```yaml
@ -29,14 +30,17 @@ Add a new job to the main prometheus.yml file:
scheme: "https" scheme: "https"
static_configs: static_configs:
- targets: ['SERVER.LOCATION:PORT'] - targets: ["my.server.here:port"]
``` ```
An example of a Prometheus configuration with workers can be found in
[metrics-howto.md](https://github.com/matrix-org/synapse/blob/master/docs/metrics-howto.md).
To use `synapse.rules` add To use `synapse.rules` add
```yaml ```yaml
rule_files: rule_files:
- "/PATH/TO/synapse-v2.rules" - "/PATH/TO/synapse-v2.rules"
``` ```
Metrics are disabled by default when running synapse; they must be enabled Metrics are disabled by default when running synapse; they must be enabled

View File

@ -9,7 +9,7 @@
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#process_resource_utime"), node: document.querySelector("#process_resource_utime"),
expr: "rate(process_cpu_seconds_total[2m]) * 100", expr: "rate(process_cpu_seconds_total[2m]) * 100",
name: "[[job]]", name: "[[job]]-[[index]]",
min: 0, min: 0,
max: 100, max: 100,
renderer: "line", renderer: "line",
@ -22,12 +22,12 @@ new PromConsole.Graph({
</script> </script>
<h3>Memory</h3> <h3>Memory</h3>
<div id="process_resource_maxrss"></div> <div id="process_resident_memory_bytes"></div>
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#process_resource_maxrss"), node: document.querySelector("#process_resident_memory_bytes"),
expr: "process_psutil_rss:max", expr: "process_resident_memory_bytes",
name: "Maxrss", name: "[[job]]-[[index]]",
min: 0, min: 0,
renderer: "line", renderer: "line",
height: 150, height: 150,
@ -43,8 +43,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#process_fds"), node: document.querySelector("#process_fds"),
expr: "process_open_fds{job='synapse'}", expr: "process_open_fds",
name: "FDs", name: "[[job]]-[[index]]",
min: 0, min: 0,
renderer: "line", renderer: "line",
height: 150, height: 150,
@ -62,8 +62,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#reactor_total_time"), node: document.querySelector("#reactor_total_time"),
expr: "rate(python_twisted_reactor_tick_time:total[2m]) / 1000", expr: "rate(python_twisted_reactor_tick_time_sum[2m])",
name: "time", name: "[[job]]-[[index]]",
max: 1, max: 1,
min: 0, min: 0,
renderer: "area", renderer: "area",
@ -80,8 +80,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#reactor_average_time"), node: document.querySelector("#reactor_average_time"),
expr: "rate(python_twisted_reactor_tick_time:total[2m]) / rate(python_twisted_reactor_tick_time:count[2m]) / 1000", expr: "rate(python_twisted_reactor_tick_time_sum[2m]) / rate(python_twisted_reactor_tick_time_count[2m])",
name: "time", name: "[[job]]-[[index]]",
min: 0, min: 0,
renderer: "line", renderer: "line",
height: 150, height: 150,
@ -97,14 +97,14 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#reactor_pending_calls"), node: document.querySelector("#reactor_pending_calls"),
expr: "rate(python_twisted_reactor_pending_calls:total[30s])/rate(python_twisted_reactor_pending_calls:count[30s])", expr: "rate(python_twisted_reactor_pending_calls_sum[30s]) / rate(python_twisted_reactor_pending_calls_count[30s])",
name: "calls", name: "[[job]]-[[index]]",
min: 0, min: 0,
renderer: "line", renderer: "line",
height: 150, height: 150,
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yTitle: "Pending Cals" yTitle: "Pending Calls"
}) })
</script> </script>
@ -115,7 +115,7 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_storage_query_time"), node: document.querySelector("#synapse_storage_query_time"),
expr: "rate(synapse_storage_query_time:count[2m])", expr: "sum(rate(synapse_storage_query_time_count[2m])) by (verb)",
name: "[[verb]]", name: "[[verb]]",
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
@ -129,8 +129,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_storage_transaction_time"), node: document.querySelector("#synapse_storage_transaction_time"),
expr: "rate(synapse_storage_transaction_time:count[2m])", expr: "topk(10, rate(synapse_storage_transaction_time_count[2m]))",
name: "[[desc]]", name: "[[job]]-[[index]] [[desc]]",
min: 0, min: 0,
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
@ -140,12 +140,12 @@ new PromConsole.Graph({
</script> </script>
<h3>Transaction execution time</h3> <h3>Transaction execution time</h3>
<div id="synapse_storage_transactions_time_msec"></div> <div id="synapse_storage_transactions_time_sec"></div>
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_storage_transactions_time_msec"), node: document.querySelector("#synapse_storage_transactions_time_sec"),
expr: "rate(synapse_storage_transaction_time:total[2m]) / 1000", expr: "rate(synapse_storage_transaction_time_sum[2m])",
name: "[[desc]]", name: "[[job]]-[[index]] [[desc]]",
min: 0, min: 0,
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
@ -154,34 +154,33 @@ new PromConsole.Graph({
}) })
</script> </script>
<h3>Database scheduling latency</h3> <h3>Average time waiting for database connection</h3>
<div id="synapse_storage_schedule_time"></div> <div id="synapse_storage_avg_waiting_time"></div>
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_storage_schedule_time"), node: document.querySelector("#synapse_storage_avg_waiting_time"),
expr: "rate(synapse_storage_schedule_time:total[2m]) / 1000", expr: "rate(synapse_storage_schedule_time_sum[2m]) / rate(synapse_storage_schedule_time_count[2m])",
name: "Total latency", name: "[[job]]-[[index]]",
min: 0, min: 0,
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "s/s", yUnits: "s",
yTitle: "Usage" yTitle: "Time"
}) })
</script> </script>
<h3>Cache hit ratio</h3> <h3>Cache request rate</h3>
<div id="synapse_cache_ratio"></div> <div id="synapse_cache_request_rate"></div>
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_cache_ratio"), node: document.querySelector("#synapse_cache_request_rate"),
expr: "rate(synapse_util_caches_cache:total[2m]) * 100", expr: "rate(synapse_util_caches_cache:total[2m])",
name: "[[name]]", name: "[[job]]-[[index]] [[name]]",
min: 0, min: 0,
max: 100,
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yUnits: "%", yUnits: "rps",
yTitle: "Percentage" yTitle: "Cache request rate"
}) })
</script> </script>
@ -191,7 +190,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_cache_size"), node: document.querySelector("#synapse_cache_size"),
expr: "synapse_util_caches_cache:size", expr: "synapse_util_caches_cache:size",
name: "[[name]]", name: "[[job]]-[[index]] [[name]]",
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yUnits: "", yUnits: "",
@ -206,8 +205,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_http_server_request_count_servlet"), node: document.querySelector("#synapse_http_server_request_count_servlet"),
expr: "rate(synapse_http_server_request_count:servlet[2m])", expr: "rate(synapse_http_server_in_flight_requests_count[2m])",
name: "[[servlet]]", name: "[[job]]-[[index]] [[method]] [[servlet]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "req/s", yUnits: "req/s",
@ -219,8 +218,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_http_server_request_count_servlet_minus_events"), node: document.querySelector("#synapse_http_server_request_count_servlet_minus_events"),
expr: "rate(synapse_http_server_request_count:servlet{servlet!=\"EventStreamRestServlet\", servlet!=\"SyncRestServlet\"}[2m])", expr: "rate(synapse_http_server_in_flight_requests_count{servlet!=\"EventStreamRestServlet\", servlet!=\"SyncRestServlet\"}[2m])",
name: "[[servlet]]", name: "[[job]]-[[index]] [[method]] [[servlet]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "req/s", yUnits: "req/s",
@ -233,8 +232,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_http_server_response_time_avg"), node: document.querySelector("#synapse_http_server_response_time_avg"),
expr: "rate(synapse_http_server_response_time_seconds[2m]) / rate(synapse_http_server_response_count[2m]) / 1000", expr: "rate(synapse_http_server_response_time_seconds_sum[2m]) / rate(synapse_http_server_response_count[2m])",
name: "[[servlet]]", name: "[[job]]-[[index]] [[servlet]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "s/req", yUnits: "s/req",
@ -277,7 +276,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_http_server_response_ru_utime"), node: document.querySelector("#synapse_http_server_response_ru_utime"),
expr: "rate(synapse_http_server_response_ru_utime_seconds[2m])", expr: "rate(synapse_http_server_response_ru_utime_seconds[2m])",
name: "[[servlet]]", name: "[[job]]-[[index]] [[servlet]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "s/s", yUnits: "s/s",
@ -292,7 +291,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_http_server_response_db_txn_duration"), node: document.querySelector("#synapse_http_server_response_db_txn_duration"),
expr: "rate(synapse_http_server_response_db_txn_duration_seconds[2m])", expr: "rate(synapse_http_server_response_db_txn_duration_seconds[2m])",
name: "[[servlet]]", name: "[[job]]-[[index]] [[servlet]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "s/s", yUnits: "s/s",
@ -306,8 +305,8 @@ new PromConsole.Graph({
<script> <script>
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_http_server_send_time_avg"), node: document.querySelector("#synapse_http_server_send_time_avg"),
expr: "rate(synapse_http_server_response_time_second{servlet='RoomSendEventRestServlet'}[2m]) / rate(synapse_http_server_response_count{servlet='RoomSendEventRestServlet'}[2m]) / 1000", expr: "rate(synapse_http_server_response_time_seconds_sum{servlet='RoomSendEventRestServlet'}[2m]) / rate(synapse_http_server_response_count{servlet='RoomSendEventRestServlet'}[2m])",
name: "[[servlet]]", name: "[[job]]-[[index]] [[servlet]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "s/req", yUnits: "s/req",
@ -323,7 +322,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_federation_client_sent"), node: document.querySelector("#synapse_federation_client_sent"),
expr: "rate(synapse_federation_client_sent[2m])", expr: "rate(synapse_federation_client_sent[2m])",
name: "[[type]]", name: "[[job]]-[[index]] [[type]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "req/s", yUnits: "req/s",
@ -337,7 +336,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_federation_server_received"), node: document.querySelector("#synapse_federation_server_received"),
expr: "rate(synapse_federation_server_received[2m])", expr: "rate(synapse_federation_server_received[2m])",
name: "[[type]]", name: "[[job]]-[[index]] [[type]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "req/s", yUnits: "req/s",
@ -367,7 +366,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_notifier_listeners"), node: document.querySelector("#synapse_notifier_listeners"),
expr: "synapse_notifier_listeners", expr: "synapse_notifier_listeners",
name: "listeners", name: "[[job]]-[[index]]",
min: 0, min: 0,
yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yAxisFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix, yHoverFormatter: PromConsole.NumberFormatter.humanizeNoSmallPrefix,
@ -382,7 +381,7 @@ new PromConsole.Graph({
new PromConsole.Graph({ new PromConsole.Graph({
node: document.querySelector("#synapse_notifier_notified_events"), node: document.querySelector("#synapse_notifier_notified_events"),
expr: "rate(synapse_notifier_notified_events[2m])", expr: "rate(synapse_notifier_notified_events[2m])",
name: "events", name: "[[job]]-[[index]]",
yAxisFormatter: PromConsole.NumberFormatter.humanize, yAxisFormatter: PromConsole.NumberFormatter.humanize,
yHoverFormatter: PromConsole.NumberFormatter.humanize, yHoverFormatter: PromConsole.NumberFormatter.humanize,
yUnits: "events/s", yUnits: "events/s",

View File

@ -382,7 +382,7 @@ the new room. Users on other servers will be unaffected.
The API is: The API is:
```json ```
POST /_synapse/admin/v1/rooms/<room_id>/delete POST /_synapse/admin/v1/rooms/<room_id>/delete
``` ```
@ -439,6 +439,10 @@ The following JSON body parameters are available:
future attempts to join the room. Defaults to `false`. future attempts to join the room. Defaults to `false`.
* `purge` - Optional. If set to `true`, it will remove all traces of the room from your database. * `purge` - Optional. If set to `true`, it will remove all traces of the room from your database.
Defaults to `true`. Defaults to `true`.
* `force_purge` - Optional, and ignored unless `purge` is `true`. If set to `true`, it
will force a purge to go ahead even if there are local users still in the room. Do not
use this unless a regular `purge` operation fails, as it could leave those users'
clients in a confused state.
The JSON body must not be empty. The body must be at least `{}`. The JSON body must not be empty. The body must be at least `{}`.

View File

@ -176,6 +176,13 @@ The api is::
GET /_synapse/admin/v1/whois/<user_id> GET /_synapse/admin/v1/whois/<user_id>
and::
GET /_matrix/client/r0/admin/whois/<userId>
See also: `Client Server API Whois
<https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_
To use it, you will need to authenticate by providing an ``access_token`` for a To use it, you will need to authenticate by providing an ``access_token`` for a
server admin: see `README.rst <README.rst>`_. server admin: see `README.rst <README.rst>`_.

View File

@ -13,10 +13,12 @@
can be enabled by adding the \"metrics\" resource to the existing can be enabled by adding the \"metrics\" resource to the existing
listener as such: listener as such:
resources: ```yaml
- names: resources:
- client - names:
- metrics - client
- metrics
```
This provides a simple way of adding metrics to your Synapse This provides a simple way of adding metrics to your Synapse
installation, and serves under `/_synapse/metrics`. If you do not installation, and serves under `/_synapse/metrics`. If you do not
@ -31,11 +33,13 @@
Add a new listener to homeserver.yaml: Add a new listener to homeserver.yaml:
listeners: ```yaml
- type: metrics listeners:
port: 9000 - type: metrics
bind_addresses: port: 9000
- '0.0.0.0' bind_addresses:
- '0.0.0.0'
```
For both options, you will need to ensure that `enable_metrics` is For both options, you will need to ensure that `enable_metrics` is
set to `True`. set to `True`.
@ -47,10 +51,13 @@
It needs to set the `metrics_path` to a non-default value (under It needs to set the `metrics_path` to a non-default value (under
`scrape_configs`): `scrape_configs`):
- job_name: "synapse" ```yaml
metrics_path: "/_synapse/metrics" - job_name: "synapse"
static_configs: scrape_interval: 15s
- targets: ["my.server.here:port"] metrics_path: "/_synapse/metrics"
static_configs:
- targets: ["my.server.here:port"]
```
where `my.server.here` is the IP address of Synapse, and `port` is where `my.server.here` is the IP address of Synapse, and `port` is
the listener port configured with the `metrics` resource. the listener port configured with the `metrics` resource.
@ -60,7 +67,8 @@
1. Restart Prometheus. 1. Restart Prometheus.
1. Consider using the [grafana dashboard](https://github.com/matrix-org/synapse/tree/master/contrib/grafana/) and required [recording rules](https://github.com/matrix-org/synapse/tree/master/contrib/prometheus/) 1. Consider using the [grafana dashboard](https://github.com/matrix-org/synapse/tree/master/contrib/grafana/)
and required [recording rules](https://github.com/matrix-org/synapse/tree/master/contrib/prometheus/)
## Monitoring workers ## Monitoring workers
@ -76,9 +84,9 @@ To allow collecting metrics from a worker, you need to add a
under `worker_listeners`: under `worker_listeners`:
```yaml ```yaml
- type: metrics - type: metrics
bind_address: '' bind_address: ''
port: 9101 port: 9101
``` ```
The `bind_address` and `port` parameters should be set so that The `bind_address` and `port` parameters should be set so that
@ -87,6 +95,38 @@ don't clash with an existing worker.
With this example, the worker's metrics would then be available With this example, the worker's metrics would then be available
on `http://127.0.0.1:9101`. on `http://127.0.0.1:9101`.
Example Prometheus target for Synapse with workers:
```yaml
- job_name: "synapse"
scrape_interval: 15s
metrics_path: "/_synapse/metrics"
static_configs:
- targets: ["my.server.here:port"]
labels:
instance: "my.server"
job: "master"
index: 1
- targets: ["my.workerserver.here:port"]
labels:
instance: "my.server"
job: "generic_worker"
index: 1
- targets: ["my.workerserver.here:port"]
labels:
instance: "my.server"
job: "generic_worker"
index: 2
- targets: ["my.workerserver.here:port"]
labels:
instance: "my.server"
job: "media_repository"
index: 1
```
Labels (`instance`, `job`, `index`) can be defined as anything.
The labels are used to group graphs in grafana.
## Renaming of metrics & deprecation of old names in 1.2 ## Renaming of metrics & deprecation of old names in 1.2
Synapse 1.2 updates the Prometheus metrics to match the naming Synapse 1.2 updates the Prometheus metrics to match the naming

View File

@ -2253,20 +2253,35 @@ password_providers:
# Clients requesting push notifications can either have the body of ## Push ##
# the message sent in the notification poke along with other details
# like the sender, or just the event ID and room ID (`event_id_only`). push:
# If clients choose the former, this option controls whether the # Clients requesting push notifications can either have the body of
# notification request includes the content of the event (other details # the message sent in the notification poke along with other details
# like the sender are still included). For `event_id_only` push, it # like the sender, or just the event ID and room ID (`event_id_only`).
# has no effect. # If clients choose the former, this option controls whether the
# # notification request includes the content of the event (other details
# For modern android devices the notification content will still appear # like the sender are still included). For `event_id_only` push, it
# because it is loaded by the app. iPhone, however will send a # has no effect.
# notification saying only that a message arrived and who it came from. #
# # For modern android devices the notification content will still appear
#push: # because it is loaded by the app. iPhone, however will send a
# include_content: true # notification saying only that a message arrived and who it came from.
#
# The default value is "true" to include message details. Uncomment to only
# include the event ID and room ID in push notification payloads.
#
#include_content: false
# When a push notification is received, an unread count is also sent.
# This number can either be calculated as the number of unread messages
# for the user, or the number of *rooms* the user has unread messages in.
#
# The default value is "true", meaning push clients will see the number of
# rooms with unread messages in them. Uncomment to instead send the number
# of unread messages.
#
#group_unread_count_by_room: false
# Spam checkers are third-party modules that can block specific actions # Spam checkers are third-party modules that can block specific actions

View File

@ -37,7 +37,7 @@ def request_registration(
exit=sys.exit, exit=sys.exit,
): ):
url = "%s/_matrix/client/r0/admin/register" % (server_location,) url = "%s/_synapse/admin/v1/register" % (server_location.rstrip("/"),)
# Get the nonce # Get the nonce
r = requests.get(url, verify=False) r = requests.get(url, verify=False)

View File

@ -32,6 +32,7 @@ from synapse.app.phone_stats_home import start_phone_stats_home
from synapse.config.server import ListenerConfig from synapse.config.server import ListenerConfig
from synapse.crypto import context_factory from synapse.crypto import context_factory
from synapse.logging.context import PreserveLoggingContext from synapse.logging.context import PreserveLoggingContext
from synapse.metrics.background_process_metrics import wrap_as_background_process
from synapse.util.async_helpers import Linearizer from synapse.util.async_helpers import Linearizer
from synapse.util.daemonize import daemonize_process from synapse.util.daemonize import daemonize_process
from synapse.util.rlimit import change_resource_limit from synapse.util.rlimit import change_resource_limit
@ -244,6 +245,7 @@ def start(hs: "synapse.server.HomeServer", listeners: Iterable[ListenerConfig]):
# Set up the SIGHUP machinery. # Set up the SIGHUP machinery.
if hasattr(signal, "SIGHUP"): if hasattr(signal, "SIGHUP"):
@wrap_as_background_process("sighup")
def handle_sighup(*args, **kwargs): def handle_sighup(*args, **kwargs):
# Tell systemd our state, if we're using it. This will silently fail if # Tell systemd our state, if we're using it. This will silently fail if
# we're not using systemd. # we're not using systemd.
@ -254,7 +256,13 @@ def start(hs: "synapse.server.HomeServer", listeners: Iterable[ListenerConfig]):
sdnotify(b"READY=1") sdnotify(b"READY=1")
signal.signal(signal.SIGHUP, handle_sighup) # We defer running the sighup handlers until next reactor tick. This
# is so that we're in a sane state, e.g. flushing the logs may fail
# if the sighup happens in the middle of writing a log entry.
def run_sighup(*args, **kwargs):
hs.get_clock().call_later(0, handle_sighup, *args, **kwargs)
signal.signal(signal.SIGHUP, run_sighup)
register_sighup(refresh_certificate, hs) register_sighup(refresh_certificate, hs)

View File

@ -21,8 +21,11 @@ class PushConfig(Config):
section = "push" section = "push"
def read_config(self, config, **kwargs): def read_config(self, config, **kwargs):
push_config = config.get("push", {}) push_config = config.get("push") or {}
self.push_include_content = push_config.get("include_content", True) self.push_include_content = push_config.get("include_content", True)
self.push_group_unread_count_by_room = push_config.get(
"group_unread_count_by_room", True
)
pusher_instances = config.get("pusher_instances") or [] pusher_instances = config.get("pusher_instances") or []
self.pusher_shard_config = ShardedWorkerHandlingConfig(pusher_instances) self.pusher_shard_config = ShardedWorkerHandlingConfig(pusher_instances)
@ -49,18 +52,33 @@ class PushConfig(Config):
def generate_config_section(self, config_dir_path, server_name, **kwargs): def generate_config_section(self, config_dir_path, server_name, **kwargs):
return """ return """
# Clients requesting push notifications can either have the body of ## Push ##
# the message sent in the notification poke along with other details
# like the sender, or just the event ID and room ID (`event_id_only`). push:
# If clients choose the former, this option controls whether the # Clients requesting push notifications can either have the body of
# notification request includes the content of the event (other details # the message sent in the notification poke along with other details
# like the sender are still included). For `event_id_only` push, it # like the sender, or just the event ID and room ID (`event_id_only`).
# has no effect. # If clients choose the former, this option controls whether the
# # notification request includes the content of the event (other details
# For modern android devices the notification content will still appear # like the sender are still included). For `event_id_only` push, it
# because it is loaded by the app. iPhone, however will send a # has no effect.
# notification saying only that a message arrived and who it came from. #
# # For modern android devices the notification content will still appear
#push: # because it is loaded by the app. iPhone, however will send a
# include_content: true # notification saying only that a message arrived and who it came from.
#
# The default value is "true" to include message details. Uncomment to only
# include the event ID and room ID in push notification payloads.
#
#include_content: false
# When a push notification is received, an unread count is also sent.
# This number can either be calculated as the number of unread messages
# for the user, or the number of *rooms* the user has unread messages in.
#
# The default value is "true", meaning push clients will see the number of
# rooms with unread messages in them. Uncomment to instead send the number
# of unread messages.
#
#group_unread_count_by_room: false
""" """

View File

@ -354,7 +354,8 @@ class IdentityHandler(BaseHandler):
raise SynapseError(500, "An error was encountered when sending the email") raise SynapseError(500, "An error was encountered when sending the email")
token_expires = ( token_expires = (
self.hs.clock.time_msec() + self.hs.config.email_validation_token_lifetime self.hs.get_clock().time_msec()
+ self.hs.config.email_validation_token_lifetime
) )
await self.store.start_or_continue_validation_session( await self.store.start_or_continue_validation_session(

View File

@ -299,17 +299,22 @@ class PaginationHandler:
""" """
return self._purges_by_id.get(purge_id) return self._purges_by_id.get(purge_id)
async def purge_room(self, room_id: str) -> None: async def purge_room(self, room_id: str, force: bool = False) -> None:
"""Purge the given room from the database""" """Purge the given room from the database.
Args:
room_id: room to be purged
force: set true to skip checking for joined users.
"""
with await self.pagination_lock.write(room_id): with await self.pagination_lock.write(room_id):
# check we know about the room # check we know about the room
await self.store.get_room_version_id(room_id) await self.store.get_room_version_id(room_id)
# first check that we have no users in this room # first check that we have no users in this room
joined = await self.store.is_host_joined(room_id, self._server_name) if not force:
joined = await self.store.is_host_joined(room_id, self._server_name)
if joined: if joined:
raise SynapseError(400, "Users are still joined to this room") raise SynapseError(400, "Users are still joined to this room")
await self.storage.purge_events.purge_room(room_id) await self.storage.purge_events.purge_room(room_id)

View File

@ -31,7 +31,6 @@ from synapse.api.errors import (
from synapse.api.ratelimiting import Ratelimiter from synapse.api.ratelimiting import Ratelimiter
from synapse.events import EventBase from synapse.events import EventBase
from synapse.events.snapshot import EventContext from synapse.events.snapshot import EventContext
from synapse.storage.roommember import RoomsForUser
from synapse.types import JsonDict, Requester, RoomAlias, RoomID, StateMap, UserID from synapse.types import JsonDict, Requester, RoomAlias, RoomID, StateMap, UserID
from synapse.util.async_helpers import Linearizer from synapse.util.async_helpers import Linearizer
from synapse.util.distributor import user_left_room from synapse.util.distributor import user_left_room
@ -347,7 +346,15 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
# later on. # later on.
content = dict(content) content = dict(content)
if not self.allow_per_room_profiles or requester.shadow_banned: # allow the server notices mxid to set room-level profile
is_requester_server_notices_user = (
self._server_notices_mxid is not None
and requester.user.to_string() == self._server_notices_mxid
)
if (
not self.allow_per_room_profiles and not is_requester_server_notices_user
) or requester.shadow_banned:
# Strip profile data, knowing that new profile data will be added to the # Strip profile data, knowing that new profile data will be added to the
# event's content in event_creation_handler.create_event() using the target's # event's content in event_creation_handler.create_event() using the target's
# global profile. # global profile.
@ -515,10 +522,16 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
elif effective_membership_state == Membership.LEAVE: elif effective_membership_state == Membership.LEAVE:
if not is_host_in_room: if not is_host_in_room:
# perhaps we've been invited # perhaps we've been invited
invite = await self.store.get_invite_for_local_user_in_room( (
user_id=target.to_string(), room_id=room_id current_membership_type,
) # type: Optional[RoomsForUser] current_membership_event_id,
if not invite: ) = await self.store.get_local_current_membership_for_user_in_room(
target.to_string(), room_id
)
if (
current_membership_type != Membership.INVITE
or not current_membership_event_id
):
logger.info( logger.info(
"%s sent a leave request to %s, but that is not an active room " "%s sent a leave request to %s, but that is not an active room "
"on this server, and there is no pending invite", "on this server, and there is no pending invite",
@ -528,6 +541,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
raise SynapseError(404, "Not a known room") raise SynapseError(404, "Not a known room")
invite = await self.store.get_event(current_membership_event_id)
logger.info( logger.info(
"%s rejects invite to %s from %s", target, room_id, invite.sender "%s rejects invite to %s from %s", target, room_id, invite.sender
) )

View File

@ -25,7 +25,7 @@ from io import BytesIO
from typing import Any, Callable, Dict, Iterator, List, Tuple, Union from typing import Any, Callable, Dict, Iterator, List, Tuple, Union
import jinja2 import jinja2
from canonicaljson import iterencode_canonical_json, iterencode_pretty_printed_json from canonicaljson import iterencode_canonical_json
from zope.interface import implementer from zope.interface import implementer
from twisted.internet import defer, interfaces from twisted.internet import defer, interfaces
@ -94,11 +94,7 @@ def return_json_error(f: failure.Failure, request: SynapseRequest) -> None:
pass pass
else: else:
respond_with_json( respond_with_json(
request, request, error_code, error_dict, send_cors=True,
error_code,
error_dict,
send_cors=True,
pretty_print=_request_user_agent_is_curl(request),
) )
@ -290,7 +286,6 @@ class DirectServeJsonResource(_AsyncResource):
code, code,
response_object, response_object,
send_cors=True, send_cors=True,
pretty_print=_request_user_agent_is_curl(request),
canonical_json=self.canonical_json, canonical_json=self.canonical_json,
) )
@ -587,7 +582,6 @@ def respond_with_json(
code: int, code: int,
json_object: Any, json_object: Any,
send_cors: bool = False, send_cors: bool = False,
pretty_print: bool = False,
canonical_json: bool = True, canonical_json: bool = True,
): ):
"""Sends encoded JSON in response to the given request. """Sends encoded JSON in response to the given request.
@ -598,8 +592,6 @@ def respond_with_json(
json_object: The object to serialize to JSON. json_object: The object to serialize to JSON.
send_cors: Whether to send Cross-Origin Resource Sharing headers send_cors: Whether to send Cross-Origin Resource Sharing headers
https://fetch.spec.whatwg.org/#http-cors-protocol https://fetch.spec.whatwg.org/#http-cors-protocol
pretty_print: Whether to include indentation and line-breaks in the
resulting JSON bytes.
canonical_json: Whether to use the canonicaljson algorithm when encoding canonical_json: Whether to use the canonicaljson algorithm when encoding
the JSON bytes. the JSON bytes.
@ -615,13 +607,10 @@ def respond_with_json(
) )
return None return None
if pretty_print: if canonical_json:
encoder = iterencode_pretty_printed_json encoder = iterencode_canonical_json
else: else:
if canonical_json: encoder = _encode_json_bytes
encoder = iterencode_canonical_json
else:
encoder = _encode_json_bytes
request.setResponseCode(code) request.setResponseCode(code)
request.setHeader(b"Content-Type", b"application/json") request.setHeader(b"Content-Type", b"application/json")
@ -759,11 +748,3 @@ def finish_request(request: Request):
request.finish() request.finish()
except RuntimeError as e: except RuntimeError as e:
logger.info("Connection disconnected before response was written: %r", e) logger.info("Connection disconnected before response was written: %r", e)
def _request_user_agent_is_curl(request: Request) -> bool:
user_agents = request.requestHeaders.getRawHeaders(b"User-Agent", default=[])
for user_agent in user_agents:
if b"curl" in user_agent:
return True
return False

View File

@ -75,6 +75,7 @@ class HttpPusher:
self.failing_since = pusherdict["failing_since"] self.failing_since = pusherdict["failing_since"]
self.timed_call = None self.timed_call = None
self._is_processing = False self._is_processing = False
self._group_unread_count_by_room = hs.config.push_group_unread_count_by_room
# This is the highest stream ordering we know it's safe to process. # This is the highest stream ordering we know it's safe to process.
# When new events arrive, we'll be given a window of new events: we # When new events arrive, we'll be given a window of new events: we
@ -136,7 +137,11 @@ class HttpPusher:
async def _update_badge(self): async def _update_badge(self):
# XXX as per https://github.com/matrix-org/matrix-doc/issues/2627, this seems # XXX as per https://github.com/matrix-org/matrix-doc/issues/2627, this seems
# to be largely redundant. perhaps we can remove it. # to be largely redundant. perhaps we can remove it.
badge = await push_tools.get_badge_count(self.hs.get_datastore(), self.user_id) badge = await push_tools.get_badge_count(
self.hs.get_datastore(),
self.user_id,
group_by_room=self._group_unread_count_by_room,
)
await self._send_badge(badge) await self._send_badge(badge)
def on_timer(self): def on_timer(self):
@ -283,7 +288,11 @@ class HttpPusher:
return True return True
tweaks = push_rule_evaluator.tweaks_for_actions(push_action["actions"]) tweaks = push_rule_evaluator.tweaks_for_actions(push_action["actions"])
badge = await push_tools.get_badge_count(self.hs.get_datastore(), self.user_id) badge = await push_tools.get_badge_count(
self.hs.get_datastore(),
self.user_id,
group_by_room=self._group_unread_count_by_room,
)
event = await self.store.get_event(push_action["event_id"], allow_none=True) event = await self.store.get_event(push_action["event_id"], allow_none=True)
if event is None: if event is None:

View File

@ -12,12 +12,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from synapse.push.presentable_names import calculate_room_name, name_from_member_event from synapse.push.presentable_names import calculate_room_name, name_from_member_event
from synapse.storage import Storage from synapse.storage import Storage
from synapse.storage.databases.main import DataStore
async def get_badge_count(store, user_id): async def get_badge_count(store: DataStore, user_id: str, group_by_room: bool) -> int:
invites = await store.get_invited_rooms_for_local_user(user_id) invites = await store.get_invited_rooms_for_local_user(user_id)
joins = await store.get_rooms_for_user(user_id) joins = await store.get_rooms_for_user(user_id)
@ -34,9 +34,15 @@ async def get_badge_count(store, user_id):
room_id, user_id, last_unread_event_id room_id, user_id, last_unread_event_id
) )
) )
# return one badge count per conversation, as count per if notifs["notify_count"] == 0:
# message is so noisy as to be almost useless continue
badge += 1 if notifs["notify_count"] else 0
if group_by_room:
# return one badge count per conversation
badge += 1
else:
# increment the badge count by the number of unread messages in the room
badge += notifs["notify_count"]
return badge return badge

View File

@ -12,9 +12,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging import logging
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, List, Optional, Tuple
from twisted.web.http import Request
from synapse.http.servlet import parse_json_object_from_request from synapse.http.servlet import parse_json_object_from_request
from synapse.replication.http._base import ReplicationEndpoint from synapse.replication.http._base import ReplicationEndpoint
@ -52,16 +53,23 @@ class ReplicationRemoteJoinRestServlet(ReplicationEndpoint):
self.clock = hs.get_clock() self.clock = hs.get_clock()
@staticmethod @staticmethod
async def _serialize_payload( async def _serialize_payload( # type: ignore
requester, room_id, user_id, remote_room_hosts, content requester: Requester,
): room_id: str,
user_id: str,
remote_room_hosts: List[str],
content: JsonDict,
) -> JsonDict:
""" """
Args: Args:
requester(Requester) requester: The user making the request according to the access token
room_id (str) room_id: The ID of the room.
user_id (str) user_id: The ID of the user.
remote_room_hosts (list[str]): Servers to try and join via remote_room_hosts: Servers to try and join via
content(dict): The event content to use for the join event content: The event content to use for the join event
Returns:
A dict representing the payload of the request.
""" """
return { return {
"requester": requester.serialize(), "requester": requester.serialize(),
@ -69,7 +77,9 @@ class ReplicationRemoteJoinRestServlet(ReplicationEndpoint):
"content": content, "content": content,
} }
async def _handle_request(self, request, room_id, user_id): async def _handle_request( # type: ignore
self, request: Request, room_id: str, user_id: str
) -> Tuple[int, JsonDict]:
content = parse_json_object_from_request(request) content = parse_json_object_from_request(request)
remote_room_hosts = content["remote_room_hosts"] remote_room_hosts = content["remote_room_hosts"]
@ -118,14 +128,17 @@ class ReplicationRemoteRejectInviteRestServlet(ReplicationEndpoint):
txn_id: Optional[str], txn_id: Optional[str],
requester: Requester, requester: Requester,
content: JsonDict, content: JsonDict,
): ) -> JsonDict:
""" """
Args: Args:
invite_event_id: ID of the invite to be rejected invite_event_id: The ID of the invite to be rejected.
txn_id: optional transaction ID supplied by the client txn_id: Optional transaction ID supplied by the client
requester: user making the rejection request, according to the access token requester: User making the rejection request, according to the access token
content: additional content to include in the rejection event. content: Additional content to include in the rejection event.
Normally an empty dict. Normally an empty dict.
Returns:
A dict representing the payload of the request.
""" """
return { return {
"txn_id": txn_id, "txn_id": txn_id,
@ -133,7 +146,9 @@ class ReplicationRemoteRejectInviteRestServlet(ReplicationEndpoint):
"content": content, "content": content,
} }
async def _handle_request(self, request, invite_event_id): async def _handle_request( # type: ignore
self, request: Request, invite_event_id: str
) -> Tuple[int, JsonDict]:
content = parse_json_object_from_request(request) content = parse_json_object_from_request(request)
txn_id = content["txn_id"] txn_id = content["txn_id"]
@ -174,18 +189,25 @@ class ReplicationUserJoinedLeftRoomRestServlet(ReplicationEndpoint):
self.distributor = hs.get_distributor() self.distributor = hs.get_distributor()
@staticmethod @staticmethod
async def _serialize_payload(room_id, user_id, change): async def _serialize_payload( # type: ignore
room_id: str, user_id: str, change: str
) -> JsonDict:
""" """
Args: Args:
room_id (str) room_id: The ID of the room.
user_id (str) user_id: The ID of the user.
change (str): "left" change: "left"
Returns:
A dict representing the payload of the request.
""" """
assert change == "left" assert change == "left"
return {} return {}
def _handle_request(self, request, room_id, user_id, change): def _handle_request( # type: ignore
self, request: Request, room_id: str, user_id: str, change: str
) -> Tuple[int, JsonDict]:
logger.info("user membership change: %s in %s", user_id, room_id) logger.info("user membership change: %s in %s", user_id, room_id)
user = UserID.from_string(user_id) user = UserID.from_string(user_id)

View File

@ -21,11 +21,7 @@ import synapse
from synapse.api.errors import Codes, NotFoundError, SynapseError from synapse.api.errors import Codes, NotFoundError, SynapseError
from synapse.http.server import JsonResource from synapse.http.server import JsonResource
from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.rest.admin._base import ( from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin
admin_patterns,
assert_requester_is_admin,
historical_admin_path_patterns,
)
from synapse.rest.admin.devices import ( from synapse.rest.admin.devices import (
DeleteDevicesRestServlet, DeleteDevicesRestServlet,
DeviceRestServlet, DeviceRestServlet,
@ -84,7 +80,7 @@ class VersionServlet(RestServlet):
class PurgeHistoryRestServlet(RestServlet): class PurgeHistoryRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns(
"/purge_history/(?P<room_id>[^/]*)(/(?P<event_id>[^/]+))?" "/purge_history/(?P<room_id>[^/]*)(/(?P<event_id>[^/]+))?"
) )
@ -169,9 +165,7 @@ class PurgeHistoryRestServlet(RestServlet):
class PurgeHistoryStatusRestServlet(RestServlet): class PurgeHistoryStatusRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns("/purge_history_status/(?P<purge_id>[^/]+)")
"/purge_history_status/(?P<purge_id>[^/]+)"
)
def __init__(self, hs): def __init__(self, hs):
""" """

View File

@ -22,28 +22,6 @@ from synapse.api.errors import AuthError
from synapse.types import UserID from synapse.types import UserID
def historical_admin_path_patterns(path_regex):
"""Returns the list of patterns for an admin endpoint, including historical ones
This is a backwards-compatibility hack. Previously, the Admin API was exposed at
various paths under /_matrix/client. This function returns a list of patterns
matching those paths (as well as the new one), so that existing scripts which rely
on the endpoints being available there are not broken.
Note that this should only be used for existing endpoints: new ones should just
register for the /_synapse/admin path.
"""
return [
re.compile(prefix + path_regex)
for prefix in (
"^/_synapse/admin/v1",
"^/_matrix/client/api/v1/admin",
"^/_matrix/client/unstable/admin",
"^/_matrix/client/r0/admin",
)
]
def admin_patterns(path_regex: str, version: str = "v1"): def admin_patterns(path_regex: str, version: str = "v1"):
"""Returns the list of patterns for an admin endpoint """Returns the list of patterns for an admin endpoint

View File

@ -16,10 +16,7 @@ import logging
from synapse.api.errors import SynapseError from synapse.api.errors import SynapseError
from synapse.http.servlet import RestServlet from synapse.http.servlet import RestServlet
from synapse.rest.admin._base import ( from synapse.rest.admin._base import admin_patterns, assert_user_is_admin
assert_user_is_admin,
historical_admin_path_patterns,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -28,7 +25,7 @@ class DeleteGroupAdminRestServlet(RestServlet):
"""Allows deleting of local groups """Allows deleting of local groups
""" """
PATTERNS = historical_admin_path_patterns("/delete_group/(?P<group_id>[^/]*)") PATTERNS = admin_patterns("/delete_group/(?P<group_id>[^/]*)")
def __init__(self, hs): def __init__(self, hs):
self.group_server = hs.get_groups_server_handler() self.group_server = hs.get_groups_server_handler()

View File

@ -22,7 +22,6 @@ from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin, assert_requester_is_admin,
assert_user_is_admin, assert_user_is_admin,
historical_admin_path_patterns,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -34,10 +33,10 @@ class QuarantineMediaInRoom(RestServlet):
""" """
PATTERNS = ( PATTERNS = (
historical_admin_path_patterns("/room/(?P<room_id>[^/]+)/media/quarantine") admin_patterns("/room/(?P<room_id>[^/]+)/media/quarantine")
+ +
# This path kept around for legacy reasons # This path kept around for legacy reasons
historical_admin_path_patterns("/quarantine_media/(?P<room_id>[^/]+)") admin_patterns("/quarantine_media/(?P<room_id>[^/]+)")
) )
def __init__(self, hs): def __init__(self, hs):
@ -63,9 +62,7 @@ class QuarantineMediaByUser(RestServlet):
this server. this server.
""" """
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns("/user/(?P<user_id>[^/]+)/media/quarantine")
"/user/(?P<user_id>[^/]+)/media/quarantine"
)
def __init__(self, hs): def __init__(self, hs):
self.store = hs.get_datastore() self.store = hs.get_datastore()
@ -90,7 +87,7 @@ class QuarantineMediaByID(RestServlet):
it via this server. it via this server.
""" """
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns(
"/media/quarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)" "/media/quarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)"
) )
@ -116,7 +113,7 @@ class ListMediaInRoom(RestServlet):
"""Lists all of the media in a given room. """Lists all of the media in a given room.
""" """
PATTERNS = historical_admin_path_patterns("/room/(?P<room_id>[^/]+)/media") PATTERNS = admin_patterns("/room/(?P<room_id>[^/]+)/media")
def __init__(self, hs): def __init__(self, hs):
self.store = hs.get_datastore() self.store = hs.get_datastore()
@ -134,7 +131,7 @@ class ListMediaInRoom(RestServlet):
class PurgeMediaCacheRestServlet(RestServlet): class PurgeMediaCacheRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/purge_media_cache") PATTERNS = admin_patterns("/purge_media_cache")
def __init__(self, hs): def __init__(self, hs):
self.media_repository = hs.get_media_repository() self.media_repository = hs.get_media_repository()

View File

@ -29,7 +29,6 @@ from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin, assert_requester_is_admin,
assert_user_is_admin, assert_user_is_admin,
historical_admin_path_patterns,
) )
from synapse.storage.databases.main.room import RoomSortOrder from synapse.storage.databases.main.room import RoomSortOrder
from synapse.types import RoomAlias, RoomID, UserID, create_requester from synapse.types import RoomAlias, RoomID, UserID, create_requester
@ -44,7 +43,7 @@ class ShutdownRoomRestServlet(RestServlet):
joined to the new room. joined to the new room.
""" """
PATTERNS = historical_admin_path_patterns("/shutdown_room/(?P<room_id>[^/]+)") PATTERNS = admin_patterns("/shutdown_room/(?P<room_id>[^/]+)")
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs
@ -71,14 +70,18 @@ class ShutdownRoomRestServlet(RestServlet):
class DeleteRoomRestServlet(RestServlet): class DeleteRoomRestServlet(RestServlet):
"""Delete a room from server. It is a combination and improvement of """Delete a room from server.
shut down and purge room.
It is a combination and improvement of shutdown and purge room.
Shuts down a room by removing all local users from the room. Shuts down a room by removing all local users from the room.
Blocking all future invites and joins to the room is optional. Blocking all future invites and joins to the room is optional.
If desired any local aliases will be repointed to a new room If desired any local aliases will be repointed to a new room
created by `new_room_user_id` and kicked users will be auto created by `new_room_user_id` and kicked users will be auto-
joined to the new room. joined to the new room.
It will remove all trace of a room from the database.
If 'purge' is true, it will remove all traces of a room from the database.
""" """
PATTERNS = admin_patterns("/rooms/(?P<room_id>[^/]+)/delete$") PATTERNS = admin_patterns("/rooms/(?P<room_id>[^/]+)/delete$")
@ -111,6 +114,14 @@ class DeleteRoomRestServlet(RestServlet):
Codes.BAD_JSON, Codes.BAD_JSON,
) )
force_purge = content.get("force_purge", False)
if not isinstance(force_purge, bool):
raise SynapseError(
HTTPStatus.BAD_REQUEST,
"Param 'force_purge' must be a boolean, if given",
Codes.BAD_JSON,
)
ret = await self.room_shutdown_handler.shutdown_room( ret = await self.room_shutdown_handler.shutdown_room(
room_id=room_id, room_id=room_id,
new_room_user_id=content.get("new_room_user_id"), new_room_user_id=content.get("new_room_user_id"),
@ -122,7 +133,7 @@ class DeleteRoomRestServlet(RestServlet):
# Purge room # Purge room
if purge: if purge:
await self.pagination_handler.purge_room(room_id) await self.pagination_handler.purge_room(room_id, force=force_purge)
return (200, ret) return (200, ret)

View File

@ -33,8 +33,8 @@ from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin, assert_requester_is_admin,
assert_user_is_admin, assert_user_is_admin,
historical_admin_path_patterns,
) )
from synapse.rest.client.v2_alpha._base import client_patterns
from synapse.types import JsonDict, UserID from synapse.types import JsonDict, UserID
if TYPE_CHECKING: if TYPE_CHECKING:
@ -55,7 +55,7 @@ _GET_PUSHERS_ALLOWED_KEYS = {
class UsersRestServlet(RestServlet): class UsersRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/users/(?P<user_id>[^/]*)$") PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)$")
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs
@ -338,7 +338,7 @@ class UserRegisterServlet(RestServlet):
nonce to the time it was generated, in int seconds. nonce to the time it was generated, in int seconds.
""" """
PATTERNS = historical_admin_path_patterns("/register") PATTERNS = admin_patterns("/register")
NONCE_TIMEOUT = 60 NONCE_TIMEOUT = 60
def __init__(self, hs): def __init__(self, hs):
@ -461,7 +461,14 @@ class UserRegisterServlet(RestServlet):
class WhoisRestServlet(RestServlet): class WhoisRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/whois/(?P<user_id>[^/]*)") path_regex = "/whois/(?P<user_id>[^/]*)$"
PATTERNS = (
admin_patterns(path_regex)
+
# URL for spec reason
# https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid
client_patterns("/admin" + path_regex, v1=True)
)
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs
@ -485,7 +492,7 @@ class WhoisRestServlet(RestServlet):
class DeactivateAccountRestServlet(RestServlet): class DeactivateAccountRestServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/deactivate/(?P<target_user_id>[^/]*)") PATTERNS = admin_patterns("/deactivate/(?P<target_user_id>[^/]*)")
def __init__(self, hs): def __init__(self, hs):
self._deactivate_account_handler = hs.get_deactivate_account_handler() self._deactivate_account_handler = hs.get_deactivate_account_handler()
@ -516,7 +523,7 @@ class DeactivateAccountRestServlet(RestServlet):
class AccountValidityRenewServlet(RestServlet): class AccountValidityRenewServlet(RestServlet):
PATTERNS = historical_admin_path_patterns("/account_validity/validity$") PATTERNS = admin_patterns("/account_validity/validity$")
def __init__(self, hs): def __init__(self, hs):
""" """
@ -559,9 +566,7 @@ class ResetPasswordRestServlet(RestServlet):
200 OK with empty object if success otherwise an error. 200 OK with empty object if success otherwise an error.
""" """
PATTERNS = historical_admin_path_patterns( PATTERNS = admin_patterns("/reset_password/(?P<target_user_id>[^/]*)")
"/reset_password/(?P<target_user_id>[^/]*)"
)
def __init__(self, hs): def __init__(self, hs):
self.store = hs.get_datastore() self.store = hs.get_datastore()
@ -603,7 +608,7 @@ class SearchUsersRestServlet(RestServlet):
200 OK with json object {list[dict[str, Any]], count} or empty object. 200 OK with json object {list[dict[str, Any]], count} or empty object.
""" """
PATTERNS = historical_admin_path_patterns("/search_users/(?P<target_user_id>[^/]*)") PATTERNS = admin_patterns("/search_users/(?P<target_user_id>[^/]*)")
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs

View File

@ -154,13 +154,28 @@ class LoginRestServlet(RestServlet):
async def _do_appservice_login( async def _do_appservice_login(
self, login_submission: JsonDict, appservice: ApplicationService self, login_submission: JsonDict, appservice: ApplicationService
): ):
logger.info( identifier = login_submission.get("identifier")
"Got appservice login request with identifier: %r", logger.info("Got appservice login request with identifier: %r", identifier)
login_submission.get("identifier"),
)
identifier = convert_client_dict_legacy_fields_to_identifier(login_submission) if not isinstance(identifier, dict):
qualified_user_id = self._get_qualified_user_id(identifier) raise SynapseError(
400, "Invalid identifier in login submission", Codes.INVALID_PARAM
)
# this login flow only supports identifiers of type "m.id.user".
if identifier.get("type") != "m.id.user":
raise SynapseError(
400, "Unknown login identifier type", Codes.INVALID_PARAM
)
user = identifier.get("user")
if not isinstance(user, str):
raise SynapseError(400, "Invalid user in identifier", Codes.INVALID_PARAM)
if user.startswith("@"):
qualified_user_id = user
else:
qualified_user_id = UserID(user, self.hs.hostname).to_string()
if not appservice.is_interested_in_user(qualified_user_id): if not appservice.is_interested_in_user(qualified_user_id):
raise LoginError(403, "Invalid access_token", errcode=Codes.FORBIDDEN) raise LoginError(403, "Invalid access_token", errcode=Codes.FORBIDDEN)

View File

@ -115,7 +115,7 @@ class EmailPasswordRequestTokenRestServlet(RestServlet):
# comments for request_token_inhibit_3pid_errors. # comments for request_token_inhibit_3pid_errors.
# Also wait for some random amount of time between 100ms and 1s to make it # Also wait for some random amount of time between 100ms and 1s to make it
# look like we did something. # look like we did something.
await self.hs.clock.sleep(random.randint(1, 10) / 10) await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
return 200, {"sid": random_string(16)} return 200, {"sid": random_string(16)}
raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND) raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND)
@ -387,7 +387,7 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
# comments for request_token_inhibit_3pid_errors. # comments for request_token_inhibit_3pid_errors.
# Also wait for some random amount of time between 100ms and 1s to make it # Also wait for some random amount of time between 100ms and 1s to make it
# look like we did something. # look like we did something.
await self.hs.clock.sleep(random.randint(1, 10) / 10) await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
return 200, {"sid": random_string(16)} return 200, {"sid": random_string(16)}
raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE) raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
@ -466,7 +466,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet):
# comments for request_token_inhibit_3pid_errors. # comments for request_token_inhibit_3pid_errors.
# Also wait for some random amount of time between 100ms and 1s to make it # Also wait for some random amount of time between 100ms and 1s to make it
# look like we did something. # look like we did something.
await self.hs.clock.sleep(random.randint(1, 10) / 10) await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
return 200, {"sid": random_string(16)} return 200, {"sid": random_string(16)}
raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE) raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE)

View File

@ -135,7 +135,7 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
# comments for request_token_inhibit_3pid_errors. # comments for request_token_inhibit_3pid_errors.
# Also wait for some random amount of time between 100ms and 1s to make it # Also wait for some random amount of time between 100ms and 1s to make it
# look like we did something. # look like we did something.
await self.hs.clock.sleep(random.randint(1, 10) / 10) await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
return 200, {"sid": random_string(16)} return 200, {"sid": random_string(16)}
raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE) raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
@ -214,7 +214,7 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet):
# comments for request_token_inhibit_3pid_errors. # comments for request_token_inhibit_3pid_errors.
# Also wait for some random amount of time between 100ms and 1s to make it # Also wait for some random amount of time between 100ms and 1s to make it
# look like we did something. # look like we did something.
await self.hs.clock.sleep(random.randint(1, 10) / 10) await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
return 200, {"sid": random_string(16)} return 200, {"sid": random_string(16)}
raise SynapseError( raise SynapseError(

View File

@ -66,7 +66,7 @@ class LocalKey(Resource):
def __init__(self, hs): def __init__(self, hs):
self.config = hs.config self.config = hs.config
self.clock = hs.clock self.clock = hs.get_clock()
self.update_response_body(self.clock.time_msec()) self.update_response_body(self.clock.time_msec())
Resource.__init__(self) Resource.__init__(self)

View File

@ -147,7 +147,8 @@ def cache_in_self(builder: T) -> T:
"@cache_in_self can only be used on functions starting with `get_`" "@cache_in_self can only be used on functions starting with `get_`"
) )
depname = builder.__name__[len("get_") :] # get_attr -> _attr
depname = builder.__name__[len("get") :]
building = [False] building = [False]
@ -235,15 +236,6 @@ class HomeServer(metaclass=abc.ABCMeta):
self._instance_id = random_string(5) self._instance_id = random_string(5)
self._instance_name = config.worker_name or "master" self._instance_name = config.worker_name or "master"
self.clock = Clock(reactor)
self.distributor = Distributor()
self.registration_ratelimiter = Ratelimiter(
clock=self.clock,
rate_hz=config.rc_registration.per_second,
burst_count=config.rc_registration.burst_count,
)
self.version_string = version_string self.version_string = version_string
self.datastores = None # type: Optional[Databases] self.datastores = None # type: Optional[Databases]
@ -301,8 +293,9 @@ class HomeServer(metaclass=abc.ABCMeta):
def is_mine_id(self, string: str) -> bool: def is_mine_id(self, string: str) -> bool:
return string.split(":", 1)[1] == self.hostname return string.split(":", 1)[1] == self.hostname
@cache_in_self
def get_clock(self) -> Clock: def get_clock(self) -> Clock:
return self.clock return Clock(self._reactor)
def get_datastore(self) -> DataStore: def get_datastore(self) -> DataStore:
if not self.datastores: if not self.datastores:
@ -319,11 +312,17 @@ class HomeServer(metaclass=abc.ABCMeta):
def get_config(self) -> HomeServerConfig: def get_config(self) -> HomeServerConfig:
return self.config return self.config
@cache_in_self
def get_distributor(self) -> Distributor: def get_distributor(self) -> Distributor:
return self.distributor return Distributor()
@cache_in_self
def get_registration_ratelimiter(self) -> Ratelimiter: def get_registration_ratelimiter(self) -> Ratelimiter:
return self.registration_ratelimiter return Ratelimiter(
clock=self.get_clock(),
rate_hz=self.config.rc_registration.per_second,
burst_count=self.config.rc_registration.burst_count,
)
@cache_in_self @cache_in_self
def get_federation_client(self) -> FederationClient: def get_federation_client(self) -> FederationClient:
@ -709,7 +708,7 @@ class HomeServer(metaclass=abc.ABCMeta):
@cache_in_self @cache_in_self
def get_federation_ratelimiter(self) -> FederationRateLimiter: def get_federation_ratelimiter(self) -> FederationRateLimiter:
return FederationRateLimiter(self.clock, config=self.config.rc_federation) return FederationRateLimiter(self.get_clock(), config=self.config.rc_federation)
@cache_in_self @cache_in_self
def get_module_api(self) -> ModuleApi: def get_module_api(self) -> ModuleApi:

View File

@ -314,6 +314,7 @@ class PurgeEventsStore(StateGroupWorkerStore, SQLBaseStore):
for table in ( for table in (
"event_auth", "event_auth",
"event_edges", "event_edges",
"event_json",
"event_push_actions_staging", "event_push_actions_staging",
"event_reference_hashes", "event_reference_hashes",
"event_relations", "event_relations",
@ -340,7 +341,6 @@ class PurgeEventsStore(StateGroupWorkerStore, SQLBaseStore):
"destination_rooms", "destination_rooms",
"event_backward_extremities", "event_backward_extremities",
"event_forward_extremities", "event_forward_extremities",
"event_json",
"event_push_actions", "event_push_actions",
"event_search", "event_search",
"events", "events",

View File

@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging import logging
from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Optional, Set from typing import TYPE_CHECKING, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple
from synapse.api.constants import EventTypes, Membership from synapse.api.constants import EventTypes, Membership
from synapse.events import EventBase from synapse.events import EventBase
@ -350,6 +350,38 @@ class RoomMemberWorkerStore(EventsWorkerStore):
return results return results
async def get_local_current_membership_for_user_in_room(
self, user_id: str, room_id: str
) -> Tuple[Optional[str], Optional[str]]:
"""Retrieve the current local membership state and event ID for a user in a room.
Args:
user_id: The ID of the user.
room_id: The ID of the room.
Returns:
A tuple of (membership_type, event_id). Both will be None if a
room_id/user_id pair is not found.
"""
# Paranoia check.
if not self.hs.is_mine_id(user_id):
raise Exception(
"Cannot call 'get_local_current_membership_for_user_in_room' on "
"non-local user %s" % (user_id,),
)
results_dict = await self.db_pool.simple_select_one(
"local_current_membership",
{"room_id": room_id, "user_id": user_id},
("membership", "event_id"),
allow_none=True,
desc="get_local_current_membership_for_user_in_room",
)
if not results_dict:
return None, None
return results_dict.get("membership"), results_dict.get("event_id")
@cached(max_entries=500000, iterable=True) @cached(max_entries=500000, iterable=True)
async def get_rooms_for_user_with_stream_ordering( async def get_rooms_for_user_with_stream_ordering(
self, user_id: str self, user_id: str

View File

@ -0,0 +1,19 @@
/* Copyright 2020 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.
*/
-- this index is essentially redundant. The only time it was ever used was when purging
-- rooms - and Synapse 1.24 will change that.
DROP INDEX IF EXISTS event_json_room_id;

View File

@ -52,7 +52,7 @@ class AuthTestCase(unittest.TestCase):
self.fail("some_user was not in %s" % macaroon.inspect()) self.fail("some_user was not in %s" % macaroon.inspect())
def test_macaroon_caveats(self): def test_macaroon_caveats(self):
self.hs.clock.now = 5000 self.hs.get_clock().now = 5000
token = self.macaroon_generator.generate_access_token("a_user") token = self.macaroon_generator.generate_access_token("a_user")
macaroon = pymacaroons.Macaroon.deserialize(token) macaroon = pymacaroons.Macaroon.deserialize(token)
@ -78,7 +78,7 @@ class AuthTestCase(unittest.TestCase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_short_term_login_token_gives_user_id(self): def test_short_term_login_token_gives_user_id(self):
self.hs.clock.now = 1000 self.hs.get_clock().now = 1000
token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000) token = self.macaroon_generator.generate_short_term_login_token("a_user", 5000)
user_id = yield defer.ensureDeferred( user_id = yield defer.ensureDeferred(
@ -87,7 +87,7 @@ class AuthTestCase(unittest.TestCase):
self.assertEqual("a_user", user_id) self.assertEqual("a_user", user_id)
# when we advance the clock, the token should be rejected # when we advance the clock, the token should be rejected
self.hs.clock.now = 6000 self.hs.get_clock().now = 6000
with self.assertRaises(synapse.api.errors.AuthError): with self.assertRaises(synapse.api.errors.AuthError):
yield defer.ensureDeferred( yield defer.ensureDeferred(
self.auth_handler.validate_short_term_login_token_and_get_user_id(token) self.auth_handler.validate_short_term_login_token_and_get_user_id(token)

View File

@ -12,7 +12,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from mock import Mock from mock import Mock
from twisted.internet.defer import Deferred from twisted.internet.defer import Deferred
@ -20,8 +19,9 @@ from twisted.internet.defer import Deferred
import synapse.rest.admin import synapse.rest.admin
from synapse.logging.context import make_deferred_yieldable from synapse.logging.context import make_deferred_yieldable
from synapse.rest.client.v1 import login, room from synapse.rest.client.v1 import login, room
from synapse.rest.client.v2_alpha import receipts
from tests.unittest import HomeserverTestCase from tests.unittest import HomeserverTestCase, override_config
class HTTPPusherTests(HomeserverTestCase): class HTTPPusherTests(HomeserverTestCase):
@ -29,6 +29,7 @@ class HTTPPusherTests(HomeserverTestCase):
synapse.rest.admin.register_servlets_for_client_rest_resource, synapse.rest.admin.register_servlets_for_client_rest_resource,
room.register_servlets, room.register_servlets,
login.register_servlets, login.register_servlets,
receipts.register_servlets,
] ]
user_id = True user_id = True
hijack_auth = False hijack_auth = False
@ -501,3 +502,161 @@ class HTTPPusherTests(HomeserverTestCase):
# check that this is low-priority # check that this is low-priority
self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low") self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
def test_push_unread_count_group_by_room(self):
"""
The HTTP pusher will group unread count by number of unread rooms.
"""
# Carry out common push count tests and setup
self._test_push_unread_count()
# Carry out our option-value specific test
#
# This push should still only contain an unread count of 1 (for 1 unread room)
self.assertEqual(
self.push_attempts[5][2]["notification"]["counts"]["unread"], 1
)
@override_config({"push": {"group_unread_count_by_room": False}})
def test_push_unread_count_message_count(self):
"""
The HTTP pusher will send the total unread message count.
"""
# Carry out common push count tests and setup
self._test_push_unread_count()
# Carry out our option-value specific test
#
# We're counting every unread message, so there should now be 4 since the
# last read receipt
self.assertEqual(
self.push_attempts[5][2]["notification"]["counts"]["unread"], 4
)
def _test_push_unread_count(self):
"""
Tests that the correct unread count appears in sent push notifications
Note that:
* Sending messages will cause push notifications to go out to relevant users
* Sending a read receipt will cause a "badge update" notification to go out to
the user that sent the receipt
"""
# Register the user who gets notified
user_id = self.register_user("user", "pass")
access_token = self.login("user", "pass")
# Register the user who sends the message
other_user_id = self.register_user("other_user", "pass")
other_access_token = self.login("other_user", "pass")
# Create a room (as other_user)
room_id = self.helper.create_room_as(other_user_id, tok=other_access_token)
# The user to get notified joins
self.helper.join(room=room_id, user=user_id, tok=access_token)
# Register the pusher
user_tuple = self.get_success(
self.hs.get_datastore().get_user_by_access_token(access_token)
)
token_id = user_tuple.token_id
self.get_success(
self.hs.get_pusherpool().add_pusher(
user_id=user_id,
access_token=token_id,
kind="http",
app_id="m.http",
app_display_name="HTTP Push Notifications",
device_display_name="pushy push",
pushkey="a@example.com",
lang=None,
data={"url": "example.com"},
)
)
# Send a message
response = self.helper.send(
room_id, body="Hello there!", tok=other_access_token
)
# To get an unread count, the user who is getting notified has to have a read
# position in the room. We'll set the read position to this event in a moment
first_message_event_id = response["event_id"]
# Advance time a bit (so the pusher will register something has happened) and
# make the push succeed
self.push_attempts[0][0].callback({})
self.pump()
# Check our push made it
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
# Check that the unread count for the room is 0
#
# The unread count is zero as the user has no read receipt in the room yet
self.assertEqual(
self.push_attempts[0][2]["notification"]["counts"]["unread"], 0
)
# Now set the user's read receipt position to the first event
#
# This will actually trigger a new notification to be sent out so that
# even if the user does not receive another message, their unread
# count goes down
request, channel = self.make_request(
"POST",
"/rooms/%s/receipt/m.read/%s" % (room_id, first_message_event_id),
{},
access_token=access_token,
)
self.assertEqual(channel.code, 200, channel.json_body)
# Advance time and make the push succeed
self.push_attempts[1][0].callback({})
self.pump()
# Unread count is still zero as we've read the only message in the room
self.assertEqual(len(self.push_attempts), 2)
self.assertEqual(
self.push_attempts[1][2]["notification"]["counts"]["unread"], 0
)
# Send another message
self.helper.send(
room_id, body="How's the weather today?", tok=other_access_token
)
# Advance time and make the push succeed
self.push_attempts[2][0].callback({})
self.pump()
# This push should contain an unread count of 1 as there's now been one
# message since our last read receipt
self.assertEqual(len(self.push_attempts), 3)
self.assertEqual(
self.push_attempts[2][2]["notification"]["counts"]["unread"], 1
)
# Since we're grouping by room, sending more messages shouldn't increase the
# unread count, as they're all being sent in the same room
self.helper.send(room_id, body="Hello?", tok=other_access_token)
# Advance time and make the push succeed
self.pump()
self.push_attempts[3][0].callback({})
self.helper.send(room_id, body="Hello??", tok=other_access_token)
# Advance time and make the push succeed
self.pump()
self.push_attempts[4][0].callback({})
self.helper.send(room_id, body="HELLO???", tok=other_access_token)
# Advance time and make the push succeed
self.pump()
self.push_attempts[5][0].callback({})
self.assertEqual(len(self.push_attempts), 6)

View File

@ -78,7 +78,7 @@ class BaseStreamTestCase(unittest.HomeserverTestCase):
self.worker_hs.get_datastore().db_pool = hs.get_datastore().db_pool self.worker_hs.get_datastore().db_pool = hs.get_datastore().db_pool
self.test_handler = self._build_replication_data_handler() self.test_handler = self._build_replication_data_handler()
self.worker_hs.replication_data_handler = self.test_handler self.worker_hs._replication_data_handler = self.test_handler
repl_handler = ReplicationCommandHandler(self.worker_hs) repl_handler = ReplicationCommandHandler(self.worker_hs)
self.client = ClientReplicationStreamProtocol( self.client = ClientReplicationStreamProtocol(

View File

@ -100,7 +100,7 @@ class DeleteGroupTestCase(unittest.HomeserverTestCase):
self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token)) self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token))
# Now delete the group # Now delete the group
url = "/admin/delete_group/" + group_id url = "/_synapse/admin/v1/delete_group/" + group_id
request, channel = self.make_request( request, channel = self.make_request(
"POST", "POST",
url.encode("ascii"), url.encode("ascii"),

View File

@ -78,7 +78,7 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
) )
# Test that the admin can still send shutdown # Test that the admin can still send shutdown
url = "admin/shutdown_room/" + room_id url = "/_synapse/admin/v1/shutdown_room/" + room_id
request, channel = self.make_request( request, channel = self.make_request(
"POST", "POST",
url.encode("ascii"), url.encode("ascii"),
@ -112,7 +112,7 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
# Test that the admin can still send shutdown # Test that the admin can still send shutdown
url = "admin/shutdown_room/" + room_id url = "/_synapse/admin/v1/shutdown_room/" + room_id
request, channel = self.make_request( request, channel = self.make_request(
"POST", "POST",
url.encode("ascii"), url.encode("ascii"),

View File

@ -41,7 +41,7 @@ class UserRegisterTestCase(unittest.HomeserverTestCase):
def make_homeserver(self, reactor, clock): def make_homeserver(self, reactor, clock):
self.url = "/_matrix/client/r0/admin/register" self.url = "/_synapse/admin/v1/register"
self.registration_handler = Mock() self.registration_handler = Mock()
self.identity_handler = Mock() self.identity_handler = Mock()
@ -1768,3 +1768,111 @@ class UserTokenRestTestCase(unittest.HomeserverTestCase):
# though the MAU limit would stop the user doing so. # though the MAU limit would stop the user doing so.
puppet_token = self._get_token() puppet_token = self._get_token()
self.helper.join(room_id, user=self.other_user, tok=puppet_token) self.helper.join(room_id, user=self.other_user, tok=puppet_token)
class WhoisRestTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
login.register_servlets,
]
def prepare(self, reactor, clock, hs):
self.store = hs.get_datastore()
self.admin_user = self.register_user("admin", "pass", admin=True)
self.admin_user_tok = self.login("admin", "pass")
self.other_user = self.register_user("user", "pass")
self.url1 = "/_synapse/admin/v1/whois/%s" % urllib.parse.quote(self.other_user)
self.url2 = "/_matrix/client/r0/admin/whois/%s" % urllib.parse.quote(
self.other_user
)
def test_no_auth(self):
"""
Try to get information of an user without authentication.
"""
request, channel = self.make_request("GET", self.url1, b"{}")
self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
request, channel = self.make_request("GET", self.url2, b"{}")
self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
def test_requester_is_not_admin(self):
"""
If the user is not a server admin, an error is returned.
"""
self.register_user("user2", "pass")
other_user2_token = self.login("user2", "pass")
request, channel = self.make_request(
"GET", self.url1, access_token=other_user2_token,
)
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
request, channel = self.make_request(
"GET", self.url2, access_token=other_user2_token,
)
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
def test_user_is_not_local(self):
"""
Tests that a lookup for a user that is not a local returns a 400
"""
url1 = "/_synapse/admin/v1/whois/@unknown_person:unknown_domain"
url2 = "/_matrix/client/r0/admin/whois/@unknown_person:unknown_domain"
request, channel = self.make_request(
"GET", url1, access_token=self.admin_user_tok,
)
self.assertEqual(400, channel.code, msg=channel.json_body)
self.assertEqual("Can only whois a local user", channel.json_body["error"])
request, channel = self.make_request(
"GET", url2, access_token=self.admin_user_tok,
)
self.assertEqual(400, channel.code, msg=channel.json_body)
self.assertEqual("Can only whois a local user", channel.json_body["error"])
def test_get_whois_admin(self):
"""
The lookup should succeed for an admin.
"""
request, channel = self.make_request(
"GET", self.url1, access_token=self.admin_user_tok,
)
self.assertEqual(200, channel.code, msg=channel.json_body)
self.assertEqual(self.other_user, channel.json_body["user_id"])
self.assertIn("devices", channel.json_body)
request, channel = self.make_request(
"GET", self.url2, access_token=self.admin_user_tok,
)
self.assertEqual(200, channel.code, msg=channel.json_body)
self.assertEqual(self.other_user, channel.json_body["user_id"])
self.assertIn("devices", channel.json_body)
def test_get_whois_user(self):
"""
The lookup should succeed for a normal user looking up their own information.
"""
other_user_token = self.login("user", "pass")
request, channel = self.make_request(
"GET", self.url1, access_token=other_user_token,
)
self.assertEqual(200, channel.code, msg=channel.json_body)
self.assertEqual(self.other_user, channel.json_body["user_id"])
self.assertIn("devices", channel.json_body)
request, channel = self.make_request(
"GET", self.url2, access_token=other_user_token,
)
self.assertEqual(200, channel.code, msg=channel.json_body)
self.assertEqual(self.other_user, channel.json_body["user_id"])
self.assertIn("devices", channel.json_body)

View File

@ -33,12 +33,15 @@ class PresenceTestCase(unittest.HomeserverTestCase):
def make_homeserver(self, reactor, clock): def make_homeserver(self, reactor, clock):
hs = self.setup_test_homeserver( presence_handler = Mock()
"red", federation_http_client=None, federation_client=Mock() presence_handler.set_state.return_value = defer.succeed(None)
)
hs.presence_handler = Mock() hs = self.setup_test_homeserver(
hs.presence_handler.set_state.return_value = defer.succeed(None) "red",
federation_http_client=None,
federation_client=Mock(),
presence_handler=presence_handler,
)
return hs return hs
@ -55,7 +58,7 @@ class PresenceTestCase(unittest.HomeserverTestCase):
) )
self.assertEqual(channel.code, 200) self.assertEqual(channel.code, 200)
self.assertEqual(self.hs.presence_handler.set_state.call_count, 1) self.assertEqual(self.hs.get_presence_handler().set_state.call_count, 1)
def test_put_presence_disabled(self): def test_put_presence_disabled(self):
""" """
@ -70,4 +73,4 @@ class PresenceTestCase(unittest.HomeserverTestCase):
) )
self.assertEqual(channel.code, 200) self.assertEqual(channel.code, 200)
self.assertEqual(self.hs.presence_handler.set_state.call_count, 0) self.assertEqual(self.hs.get_presence_handler().set_state.call_count, 0)

View File

@ -342,7 +342,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
self.register_user("admin", "adminpassword", admin=True) self.register_user("admin", "adminpassword", admin=True)
admin_tok = self.login("admin", "adminpassword") admin_tok = self.login("admin", "adminpassword")
url = "/_matrix/client/unstable/admin/account_validity/validity" url = "/_synapse/admin/v1/account_validity/validity"
params = {"user_id": user_id} params = {"user_id": user_id}
request_data = json.dumps(params) request_data = json.dumps(params)
request, channel = self.make_request( request, channel = self.make_request(
@ -362,7 +362,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
self.register_user("admin", "adminpassword", admin=True) self.register_user("admin", "adminpassword", admin=True)
admin_tok = self.login("admin", "adminpassword") admin_tok = self.login("admin", "adminpassword")
url = "/_matrix/client/unstable/admin/account_validity/validity" url = "/_synapse/admin/v1/account_validity/validity"
params = { params = {
"user_id": user_id, "user_id": user_id,
"expiration_ts": 0, "expiration_ts": 0,
@ -389,7 +389,7 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
self.register_user("admin", "adminpassword", admin=True) self.register_user("admin", "adminpassword", admin=True)
admin_tok = self.login("admin", "adminpassword") admin_tok = self.login("admin", "adminpassword")
url = "/_matrix/client/unstable/admin/account_validity/validity" url = "/_synapse/admin/v1/account_validity/validity"
params = { params = {
"user_id": user_id, "user_id": user_id,
"expiration_ts": 0, "expiration_ts": 0,
@ -569,7 +569,7 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
tok = self.login("kermit", "monkey") tok = self.login("kermit", "monkey")
# We need to manually add an email address otherwise the handler will do # We need to manually add an email address otherwise the handler will do
# nothing. # nothing.
now = self.hs.clock.time_msec() now = self.hs.get_clock().time_msec()
self.get_success( self.get_success(
self.store.user_add_threepid( self.store.user_add_threepid(
user_id=user_id, user_id=user_id,
@ -587,7 +587,7 @@ class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
# We need to manually add an email address otherwise the handler will do # We need to manually add an email address otherwise the handler will do
# nothing. # nothing.
now = self.hs.clock.time_msec() now = self.hs.get_clock().time_msec()
self.get_success( self.get_success(
self.store.user_add_threepid( self.store.user_add_threepid(
user_id=user_id, user_id=user_id,
@ -646,7 +646,7 @@ class AccountValidityBackgroundJobTestCase(unittest.HomeserverTestCase):
self.hs.config.account_validity.startup_job_max_delta = self.max_delta self.hs.config.account_validity.startup_job_max_delta = self.max_delta
now_ms = self.hs.clock.time_msec() now_ms = self.hs.get_clock().time_msec()
self.get_success(self.store._set_expiration_date_when_missing()) self.get_success(self.store._set_expiration_date_when_missing())
res = self.get_success(self.store.get_expiration_ts_for_user(user_id)) res = self.get_success(self.store.get_expiration_ts_for_user(user_id))

View File

@ -416,7 +416,7 @@ class ClientIpAuthTestCase(unittest.HomeserverTestCase):
self.reactor, self.reactor,
self.site, self.site,
"GET", "GET",
"/_matrix/client/r0/admin/users/" + self.user_id, "/_synapse/admin/v1/users/" + self.user_id,
access_token=access_token, access_token=access_token,
custom_headers=headers1.items(), custom_headers=headers1.items(),
**make_request_args, **make_request_args,

View File

@ -554,7 +554,7 @@ class HomeserverTestCase(TestCase):
self.hs.config.registration_shared_secret = "shared" self.hs.config.registration_shared_secret = "shared"
# Create the user # Create the user
request, channel = self.make_request("GET", "/_matrix/client/r0/admin/register") request, channel = self.make_request("GET", "/_synapse/admin/v1/register")
self.assertEqual(channel.code, 200, msg=channel.result) self.assertEqual(channel.code, 200, msg=channel.result)
nonce = channel.json_body["nonce"] nonce = channel.json_body["nonce"]
@ -580,7 +580,7 @@ class HomeserverTestCase(TestCase):
} }
) )
request, channel = self.make_request( request, channel = self.make_request(
"POST", "/_matrix/client/r0/admin/register", body.encode("utf8") "POST", "/_synapse/admin/v1/register", body.encode("utf8")
) )
self.assertEqual(channel.code, 200, channel.json_body) self.assertEqual(channel.code, 200, channel.json_body)

View File

@ -271,7 +271,7 @@ def setup_test_homeserver(
# Install @cache_in_self attributes # Install @cache_in_self attributes
for key, val in kwargs.items(): for key, val in kwargs.items():
setattr(hs, key, val) setattr(hs, "_" + key, val)
# Mock TLS # Mock TLS
hs.tls_server_context_factory = Mock() hs.tls_server_context_factory = Mock()