Merge branch 'develop' into matrix-org-hotfixes
commit
3f488bfded
16
CHANGES.md
16
CHANGES.md
|
@ -12,6 +12,22 @@ from Synapse as most users have updated their client. Further context can be
|
||||||
found at [\#6766](https://github.com/matrix-org/synapse/issues/6766).
|
found at [\#6766](https://github.com/matrix-org/synapse/issues/6766).
|
||||||
|
|
||||||
|
|
||||||
|
Synapse 1.19.1 (2020-08-27)
|
||||||
|
===========================
|
||||||
|
|
||||||
|
No significant changes.
|
||||||
|
|
||||||
|
|
||||||
|
Synapse 1.19.1rc1 (2020-08-25)
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Fix a bug introduced in v1.19.0 where appservices with ratelimiting disabled would still be ratelimited when joining rooms. ([\#8139](https://github.com/matrix-org/synapse/issues/8139))
|
||||||
|
- Fix a bug introduced in v1.19.0 that would cause e.g. profile updates to fail due to incorrect application of rate limits on join requests. ([\#8153](https://github.com/matrix-org/synapse/issues/8153))
|
||||||
|
|
||||||
|
|
||||||
Synapse 1.19.0 (2020-08-17)
|
Synapse 1.19.0 (2020-08-17)
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add filter `name` to the `/users` admin API, which filters by user ID or displayname. Contributed by Awesome Technologies Innovationslabor GmbH.
|
|
@ -0,0 +1 @@
|
||||||
|
Reduce run times of some unit tests by advancing the reactor a fewer number of times.
|
|
@ -0,0 +1 @@
|
||||||
|
Don't fail `/submit_token` requests on incorrect session ID if `request_token_inhibit_3pid_errors` is turned on.
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Fix a bug introduced in v1.7.2 impacting message retention policies that would allow federated homeservers to dictate a retention period that's lower than the configured minimum allowed duration in the configuration file.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix a long-standing bug where invalid JSON would be accepted by Synapse.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix a bug introduced in Synapse 1.12.0 which could cause `/sync` requests to fail with a 404 if you had a very old outstanding room invite.
|
|
@ -0,0 +1 @@
|
||||||
|
Separate `get_current_token` into two since there are two different use cases for it.
|
|
@ -0,0 +1 @@
|
||||||
|
Iteratively encode JSON to avoid blocking the reactor.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Updated documentation to note that Synapse does not follow `HTTP 308` redirects due to an upstream library not supporting them. Contributed by Ryan Cole.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove `ChainedIdGenerator`.
|
|
@ -0,0 +1 @@
|
||||||
|
Reduce the amount of whitespace in JSON stored and sent in responses.
|
|
@ -0,0 +1 @@
|
||||||
|
Add type hints to `synapse.storage.database`.
|
|
@ -0,0 +1 @@
|
||||||
|
Return a proper error code when the rooms of an invalid group are requested.
|
|
@ -0,0 +1 @@
|
||||||
|
Update the test federation client to handle streaming responses.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix a bug which could cause a leaked postgres connection if synapse was set to daemonize.
|
|
@ -0,0 +1 @@
|
||||||
|
Micro-optimisations to get_auth_chain_ids.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Clarify the error code if a user tries to register with a numeric ID. This bug was introduced in v1.15.0.
|
|
@ -0,0 +1 @@
|
||||||
|
Fixes a bug where appservices with ratelimiting disabled would still be ratelimited when joining rooms. This bug was introduced in v1.19.0.
|
|
@ -0,0 +1 @@
|
||||||
|
Add type hints to `synapse.state`.
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Fix builds of the Docker image on non-x86 platforms.
|
|
@ -0,0 +1 @@
|
||||||
|
Added curl for healthcheck support and readme updates for the change. Contributed by @maquis196.
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Refactor `StreamIdGenerator` and `MultiWriterIdGenerator` to have the same interface.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Add filter `name` to the `/users` admin API, which filters by user ID or displayname. Contributed by Awesome Technologies Innovationslabor GmbH.
|
|
@ -0,0 +1 @@
|
||||||
|
Add functions to `MultiWriterIdGen` used by events stream.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix tests that were broken due to the merge of 1.19.1.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Make `SlavedIdTracker.advance` have the same interface as `MultiWriterIDGenerator`.
|
|
@ -0,0 +1 @@
|
||||||
|
Convert various parts of the codebase to async/await.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove unused `is_guest` parameter from, and add safeguard to, `MessageHandler.get_room_data`.
|
|
@ -0,0 +1 @@
|
||||||
|
Standardize the mypy configuration.
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for shadow-banning users (ignoring any message send requests).
|
|
@ -0,0 +1 @@
|
||||||
|
Add functions to `MultiWriterIdGen` used by events stream.
|
|
@ -0,0 +1 @@
|
||||||
|
Remove unused `is_guest` parameter from, and add safeguard to, `MessageHandler.get_room_data`.
|
|
@ -1,3 +1,15 @@
|
||||||
|
matrix-synapse-py3 (1.19.0ubuntu1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Use Type=notify in systemd service
|
||||||
|
|
||||||
|
-- Dexter Chua <dec41@srcf.net> Wed, 26 Aug 2020 12:41:36 +0000
|
||||||
|
|
||||||
|
matrix-synapse-py3 (1.19.1) stable; urgency=medium
|
||||||
|
|
||||||
|
* New synapse release 1.19.1.
|
||||||
|
|
||||||
|
-- Synapse Packaging team <packages@matrix.org> Thu, 27 Aug 2020 10:50:19 +0100
|
||||||
|
|
||||||
matrix-synapse-py3 (1.19.0) stable; urgency=medium
|
matrix-synapse-py3 (1.19.0) stable; urgency=medium
|
||||||
|
|
||||||
[ Synapse Packaging team ]
|
[ Synapse Packaging team ]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Description=Synapse Matrix homeserver
|
Description=Synapse Matrix homeserver
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=notify
|
||||||
User=matrix-synapse
|
User=matrix-synapse
|
||||||
WorkingDirectory=/var/lib/matrix-synapse
|
WorkingDirectory=/var/lib/matrix-synapse
|
||||||
EnvironmentFile=/etc/default/matrix-synapse
|
EnvironmentFile=/etc/default/matrix-synapse
|
||||||
|
|
|
@ -19,11 +19,16 @@ ARG PYTHON_VERSION=3.7
|
||||||
FROM docker.io/python:${PYTHON_VERSION}-slim as builder
|
FROM docker.io/python:${PYTHON_VERSION}-slim as builder
|
||||||
|
|
||||||
# install the OS build deps
|
# install the OS build deps
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
libffi-dev \
|
||||||
|
libjpeg-dev \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libwebp-dev \
|
||||||
|
libxml++2.6-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
zlib1g-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Build dependencies that are not available as wheels, to speed up rebuilds
|
# Build dependencies that are not available as wheels, to speed up rebuilds
|
||||||
|
@ -55,9 +60,12 @@ RUN pip install --prefix="/install" --no-warn-script-location \
|
||||||
FROM docker.io/python:${PYTHON_VERSION}-slim
|
FROM docker.io/python:${PYTHON_VERSION}-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
libpq5 \
|
curl \
|
||||||
xmlsec1 \
|
|
||||||
gosu \
|
gosu \
|
||||||
|
libjpeg62-turbo \
|
||||||
|
libpq5 \
|
||||||
|
libwebp6 \
|
||||||
|
xmlsec1 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
COPY --from=builder /install /usr/local
|
||||||
|
@ -69,3 +77,6 @@ VOLUME ["/data"]
|
||||||
EXPOSE 8008/tcp 8009/tcp 8448/tcp
|
EXPOSE 8008/tcp 8009/tcp 8448/tcp
|
||||||
|
|
||||||
ENTRYPOINT ["/start.py"]
|
ENTRYPOINT ["/start.py"]
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=1m --timeout=5s \
|
||||||
|
CMD curl -fSs http://localhost:8008/health || exit 1
|
||||||
|
|
|
@ -162,3 +162,32 @@ docker build -t matrixdotorg/synapse -f docker/Dockerfile .
|
||||||
|
|
||||||
You can choose to build a different docker image by changing the value of the `-f` flag to
|
You can choose to build a different docker image by changing the value of the `-f` flag to
|
||||||
point to another Dockerfile.
|
point to another Dockerfile.
|
||||||
|
|
||||||
|
## Disabling the healthcheck
|
||||||
|
|
||||||
|
If you are using a non-standard port or tls inside docker you can disable the healthcheck
|
||||||
|
whilst running the above `docker run` commands.
|
||||||
|
|
||||||
|
```
|
||||||
|
--no-healthcheck
|
||||||
|
```
|
||||||
|
## Setting custom healthcheck on docker run
|
||||||
|
|
||||||
|
If you wish to point the healthcheck at a different port with docker command, add the following
|
||||||
|
|
||||||
|
```
|
||||||
|
--health-cmd 'curl -fSs http://localhost:1234/health'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting the healthcheck in docker-compose file
|
||||||
|
|
||||||
|
You can add the following to set a custom healthcheck in a docker compose file.
|
||||||
|
You will need version >2.1 for this to work.
|
||||||
|
|
||||||
|
```
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fSs", "http://localhost:8008/health"]
|
||||||
|
interval: 1m
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
```
|
||||||
|
|
|
@ -108,7 +108,7 @@ The api is::
|
||||||
|
|
||||||
GET /_synapse/admin/v2/users?from=0&limit=10&guests=false
|
GET /_synapse/admin/v2/users?from=0&limit=10&guests=false
|
||||||
|
|
||||||
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>`_.
|
||||||
|
|
||||||
The parameter ``from`` is optional but used for pagination, denoting the
|
The parameter ``from`` is optional but used for pagination, denoting the
|
||||||
|
@ -119,8 +119,11 @@ from a previous call.
|
||||||
The parameter ``limit`` is optional but is used for pagination, denoting the
|
The parameter ``limit`` is optional but is used for pagination, denoting the
|
||||||
maximum number of items to return in this call. Defaults to ``100``.
|
maximum number of items to return in this call. Defaults to ``100``.
|
||||||
|
|
||||||
The parameter ``user_id`` is optional and filters to only users with user IDs
|
The parameter ``user_id`` is optional and filters to only return users with user IDs
|
||||||
that contain this value.
|
that contain this value. This parameter is ignored when using the ``name`` parameter.
|
||||||
|
|
||||||
|
The parameter ``name`` is optional and filters to only return users with user ID localparts
|
||||||
|
**or** displaynames that contain this value.
|
||||||
|
|
||||||
The parameter ``guests`` is optional and if ``false`` will **exclude** guest users.
|
The parameter ``guests`` is optional and if ``false`` will **exclude** guest users.
|
||||||
Defaults to ``true`` to include guest users.
|
Defaults to ``true`` to include guest users.
|
||||||
|
|
|
@ -47,6 +47,18 @@ you invite them to. This can be caused by an incorrectly-configured reverse
|
||||||
proxy: see [reverse_proxy.md](<reverse_proxy.md>) for instructions on how to correctly
|
proxy: see [reverse_proxy.md](<reverse_proxy.md>) for instructions on how to correctly
|
||||||
configure a reverse proxy.
|
configure a reverse proxy.
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
|
||||||
|
**HTTP `308 Permanent Redirect` redirects are not followed**: Due to missing features
|
||||||
|
in the HTTP library used by Synapse, 308 redirects are currently not followed by
|
||||||
|
federating servers, which can cause `M_UNKNOWN` or `401 Unauthorized` errors. This
|
||||||
|
may affect users who are redirecting apex-to-www (e.g. `example.com` -> `www.example.com`),
|
||||||
|
and especially users of the Kubernetes *Nginx Ingress* module, which uses 308 redirect
|
||||||
|
codes by default. For those Kubernetes users, [this Stackoverflow post](https://stackoverflow.com/a/52617528/5096871)
|
||||||
|
might be helpful. For other users, switching to a `301 Moved Permanently` code may be
|
||||||
|
an option. 308 redirect codes will be supported properly in a future
|
||||||
|
release of Synapse.
|
||||||
|
|
||||||
## Running a demo federation of Synapses
|
## Running a demo federation of Synapses
|
||||||
|
|
||||||
If you want to get up and running quickly with a trio of homeservers in a
|
If you want to get up and running quickly with a trio of homeservers in a
|
||||||
|
|
|
@ -378,11 +378,10 @@ retention:
|
||||||
# min_lifetime: 1d
|
# min_lifetime: 1d
|
||||||
# max_lifetime: 1y
|
# max_lifetime: 1y
|
||||||
|
|
||||||
# Retention policy limits. If set, a user won't be able to send a
|
# Retention policy limits. If set, and the state of a room contains a
|
||||||
# 'm.room.retention' event which features a 'min_lifetime' or a 'max_lifetime'
|
# 'm.room.retention' event in its state which contains a 'min_lifetime' or a
|
||||||
# that's not within this range. This is especially useful in closed federations,
|
# 'max_lifetime' that's out of these bounds, Synapse will cap the room's policy
|
||||||
# in which server admins can make sure every federating server applies the same
|
# to these limits when running purge jobs.
|
||||||
# rules.
|
|
||||||
#
|
#
|
||||||
#allowed_lifetime_min: 1d
|
#allowed_lifetime_min: 1d
|
||||||
#allowed_lifetime_max: 1y
|
#allowed_lifetime_max: 1y
|
||||||
|
@ -408,12 +407,19 @@ retention:
|
||||||
# (e.g. every 12h), but not want that purge to be performed by a job that's
|
# (e.g. every 12h), but not want that purge to be performed by a job that's
|
||||||
# iterating over every room it knows, which could be heavy on the server.
|
# iterating over every room it knows, which could be heavy on the server.
|
||||||
#
|
#
|
||||||
|
# If any purge job is configured, it is strongly recommended to have at least
|
||||||
|
# a single job with neither 'shortest_max_lifetime' nor 'longest_max_lifetime'
|
||||||
|
# set, or one job without 'shortest_max_lifetime' and one job without
|
||||||
|
# 'longest_max_lifetime' set. Otherwise some rooms might be ignored, even if
|
||||||
|
# 'allowed_lifetime_min' and 'allowed_lifetime_max' are set, because capping a
|
||||||
|
# room's policy to these values is done after the policies are retrieved from
|
||||||
|
# Synapse's database (which is done using the range specified in a purge job's
|
||||||
|
# configuration).
|
||||||
|
#
|
||||||
#purge_jobs:
|
#purge_jobs:
|
||||||
# - shortest_max_lifetime: 1d
|
# - longest_max_lifetime: 3d
|
||||||
# longest_max_lifetime: 3d
|
|
||||||
# interval: 12h
|
# interval: 12h
|
||||||
# - shortest_max_lifetime: 3d
|
# - shortest_max_lifetime: 3d
|
||||||
# longest_max_lifetime: 1y
|
|
||||||
# interval: 1d
|
# interval: 1d
|
||||||
|
|
||||||
# Inhibits the /requestToken endpoints from returning an error that might leak
|
# Inhibits the /requestToken endpoints from returning an error that might leak
|
||||||
|
|
49
mypy.ini
49
mypy.ini
|
@ -6,6 +6,55 @@ check_untyped_defs = True
|
||||||
show_error_codes = True
|
show_error_codes = True
|
||||||
show_traceback = True
|
show_traceback = True
|
||||||
mypy_path = stubs
|
mypy_path = stubs
|
||||||
|
files =
|
||||||
|
synapse/api,
|
||||||
|
synapse/appservice,
|
||||||
|
synapse/config,
|
||||||
|
synapse/event_auth.py,
|
||||||
|
synapse/events/builder.py,
|
||||||
|
synapse/events/spamcheck.py,
|
||||||
|
synapse/federation,
|
||||||
|
synapse/handlers/auth.py,
|
||||||
|
synapse/handlers/cas_handler.py,
|
||||||
|
synapse/handlers/directory.py,
|
||||||
|
synapse/handlers/federation.py,
|
||||||
|
synapse/handlers/identity.py,
|
||||||
|
synapse/handlers/message.py,
|
||||||
|
synapse/handlers/oidc_handler.py,
|
||||||
|
synapse/handlers/presence.py,
|
||||||
|
synapse/handlers/room.py,
|
||||||
|
synapse/handlers/room_member.py,
|
||||||
|
synapse/handlers/room_member_worker.py,
|
||||||
|
synapse/handlers/saml_handler.py,
|
||||||
|
synapse/handlers/sync.py,
|
||||||
|
synapse/handlers/ui_auth,
|
||||||
|
synapse/http/server.py,
|
||||||
|
synapse/http/site.py,
|
||||||
|
synapse/logging/,
|
||||||
|
synapse/metrics,
|
||||||
|
synapse/module_api,
|
||||||
|
synapse/notifier.py,
|
||||||
|
synapse/push/pusherpool.py,
|
||||||
|
synapse/push/push_rule_evaluator.py,
|
||||||
|
synapse/replication,
|
||||||
|
synapse/rest,
|
||||||
|
synapse/server.py,
|
||||||
|
synapse/server_notices,
|
||||||
|
synapse/spam_checker_api,
|
||||||
|
synapse/state,
|
||||||
|
synapse/storage/databases/main/ui_auth.py,
|
||||||
|
synapse/storage/database.py,
|
||||||
|
synapse/storage/engines,
|
||||||
|
synapse/storage/state.py,
|
||||||
|
synapse/storage/util,
|
||||||
|
synapse/streams,
|
||||||
|
synapse/types.py,
|
||||||
|
synapse/util/caches/stream_change_cache.py,
|
||||||
|
synapse/util/metrics.py,
|
||||||
|
tests/replication,
|
||||||
|
tests/test_utils,
|
||||||
|
tests/rest/client/v2_alpha/test_auth.py,
|
||||||
|
tests/util/test_stream_change_cache.py
|
||||||
|
|
||||||
[mypy-pymacaroons.*]
|
[mypy-pymacaroons.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
|
@ -21,10 +21,12 @@ import argparse
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Any, Optional
|
||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
import nacl.signing
|
import nacl.signing
|
||||||
import requests
|
import requests
|
||||||
|
import signedjson.types
|
||||||
import srvlookup
|
import srvlookup
|
||||||
import yaml
|
import yaml
|
||||||
from requests.adapters import HTTPAdapter
|
from requests.adapters import HTTPAdapter
|
||||||
|
@ -69,7 +71,9 @@ def encode_canonical_json(value):
|
||||||
).encode("UTF-8")
|
).encode("UTF-8")
|
||||||
|
|
||||||
|
|
||||||
def sign_json(json_object, signing_key, signing_name):
|
def sign_json(
|
||||||
|
json_object: Any, signing_key: signedjson.types.SigningKey, signing_name: str
|
||||||
|
) -> Any:
|
||||||
signatures = json_object.pop("signatures", {})
|
signatures = json_object.pop("signatures", {})
|
||||||
unsigned = json_object.pop("unsigned", None)
|
unsigned = json_object.pop("unsigned", None)
|
||||||
|
|
||||||
|
@ -122,7 +126,14 @@ def read_signing_keys(stream):
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
|
||||||
def request_json(method, origin_name, origin_key, destination, path, content):
|
def request(
|
||||||
|
method: Optional[str],
|
||||||
|
origin_name: str,
|
||||||
|
origin_key: signedjson.types.SigningKey,
|
||||||
|
destination: str,
|
||||||
|
path: str,
|
||||||
|
content: Optional[str],
|
||||||
|
) -> requests.Response:
|
||||||
if method is None:
|
if method is None:
|
||||||
if content is None:
|
if content is None:
|
||||||
method = "GET"
|
method = "GET"
|
||||||
|
@ -159,11 +170,14 @@ def request_json(method, origin_name, origin_key, destination, path, content):
|
||||||
if method == "POST":
|
if method == "POST":
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
result = s.request(
|
return s.request(
|
||||||
method=method, url=dest, headers=headers, verify=False, data=content
|
method=method,
|
||||||
|
url=dest,
|
||||||
|
headers=headers,
|
||||||
|
verify=False,
|
||||||
|
data=content,
|
||||||
|
stream=True,
|
||||||
)
|
)
|
||||||
sys.stderr.write("Status Code: %d\n" % (result.status_code,))
|
|
||||||
return result.json()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -222,7 +236,7 @@ def main():
|
||||||
with open(args.signing_key_path) as f:
|
with open(args.signing_key_path) as f:
|
||||||
key = read_signing_keys(f)[0]
|
key = read_signing_keys(f)[0]
|
||||||
|
|
||||||
result = request_json(
|
result = request(
|
||||||
args.method,
|
args.method,
|
||||||
args.server_name,
|
args.server_name,
|
||||||
key,
|
key,
|
||||||
|
@ -231,7 +245,12 @@ def main():
|
||||||
content=args.body,
|
content=args.body,
|
||||||
)
|
)
|
||||||
|
|
||||||
json.dump(result, sys.stdout)
|
sys.stderr.write("Status Code: %d\n" % (result.status_code,))
|
||||||
|
|
||||||
|
for chunk in result.iter_content():
|
||||||
|
# we write raw utf8 to stdout.
|
||||||
|
sys.stdout.buffer.write(chunk)
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# Stub for frozendict.
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Hashable,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
Mapping,
|
||||||
|
overload,
|
||||||
|
Tuple,
|
||||||
|
TypeVar,
|
||||||
|
)
|
||||||
|
|
||||||
|
_KT = TypeVar("_KT", bound=Hashable) # Key type.
|
||||||
|
_VT = TypeVar("_VT") # Value type.
|
||||||
|
|
||||||
|
class frozendict(Mapping[_KT, _VT]):
|
||||||
|
@overload
|
||||||
|
def __init__(self, **kwargs: _VT) -> None: ...
|
||||||
|
@overload
|
||||||
|
def __init__(self, __map: Mapping[_KT, _VT], **kwargs: _VT) -> None: ...
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self, __iterable: Iterable[Tuple[_KT, _VT]], **kwargs: _VT
|
||||||
|
) -> None: ...
|
||||||
|
def __getitem__(self, key: _KT) -> _VT: ...
|
||||||
|
def __contains__(self, key: Any) -> bool: ...
|
||||||
|
def copy(self, **add_or_replace: Any) -> frozendict: ...
|
||||||
|
def __iter__(self) -> Iterator[_KT]: ...
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
def __repr__(self) -> str: ...
|
||||||
|
def __hash__(self) -> int: ...
|
|
@ -48,7 +48,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
__version__ = "1.19.0"
|
__version__ = "1.19.1"
|
||||||
|
|
||||||
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
|
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
|
||||||
# We import here so that we don't have to install a bunch of deps when
|
# We import here so that we don't have to install a bunch of deps when
|
||||||
|
|
|
@ -21,10 +21,10 @@ import typing
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from twisted.web import http
|
from twisted.web import http
|
||||||
|
|
||||||
|
from synapse.util import json_decoder
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from synapse.types import JsonDict
|
from synapse.types import JsonDict
|
||||||
|
|
||||||
|
@ -593,7 +593,7 @@ class HttpResponseException(CodeMessageException):
|
||||||
# try to parse the body as json, to get better errcode/msg, but
|
# try to parse the body as json, to get better errcode/msg, but
|
||||||
# default to M_UNKNOWN with the HTTP status as the error text
|
# default to M_UNKNOWN with the HTTP status as the error text
|
||||||
try:
|
try:
|
||||||
j = json.loads(self.response.decode("utf-8"))
|
j = json_decoder.decode(self.response.decode("utf-8"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
j = {}
|
j = {}
|
||||||
|
|
||||||
|
@ -604,3 +604,11 @@ class HttpResponseException(CodeMessageException):
|
||||||
errmsg = j.pop("error", self.msg)
|
errmsg = j.pop("error", self.msg)
|
||||||
|
|
||||||
return ProxiedRequestError(self.code, errmsg, errcode, j)
|
return ProxiedRequestError(self.code, errmsg, errcode, j)
|
||||||
|
|
||||||
|
|
||||||
|
class ShadowBanError(Exception):
|
||||||
|
"""
|
||||||
|
Raised when a shadow-banned user attempts to perform an action.
|
||||||
|
|
||||||
|
This should be caught and a proper "fake" success response sent to the user.
|
||||||
|
"""
|
||||||
|
|
|
@ -17,6 +17,7 @@ from collections import OrderedDict
|
||||||
from typing import Any, Optional, Tuple
|
from typing import Any, Optional, Tuple
|
||||||
|
|
||||||
from synapse.api.errors import LimitExceededError
|
from synapse.api.errors import LimitExceededError
|
||||||
|
from synapse.types import Requester
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,6 +44,42 @@ class Ratelimiter(object):
|
||||||
# * The rate_hz of this particular entry. This can vary per request
|
# * The rate_hz of this particular entry. This can vary per request
|
||||||
self.actions = OrderedDict() # type: OrderedDict[Any, Tuple[float, int, float]]
|
self.actions = OrderedDict() # type: OrderedDict[Any, Tuple[float, int, float]]
|
||||||
|
|
||||||
|
def can_requester_do_action(
|
||||||
|
self,
|
||||||
|
requester: Requester,
|
||||||
|
rate_hz: Optional[float] = None,
|
||||||
|
burst_count: Optional[int] = None,
|
||||||
|
update: bool = True,
|
||||||
|
_time_now_s: Optional[int] = None,
|
||||||
|
) -> Tuple[bool, float]:
|
||||||
|
"""Can the requester perform the action?
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: The requester to key off when rate limiting. The user property
|
||||||
|
will be used.
|
||||||
|
rate_hz: The long term number of actions that can be performed in a second.
|
||||||
|
Overrides the value set during instantiation if set.
|
||||||
|
burst_count: How many actions that can be performed before being limited.
|
||||||
|
Overrides the value set during instantiation if set.
|
||||||
|
update: Whether to count this check as performing the action
|
||||||
|
_time_now_s: The current time. Optional, defaults to the current time according
|
||||||
|
to self.clock. Only used by tests.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple containing:
|
||||||
|
* A bool indicating if they can perform the action now
|
||||||
|
* The reactor timestamp for when the action can be performed next.
|
||||||
|
-1 if rate_hz is less than or equal to zero
|
||||||
|
"""
|
||||||
|
# Disable rate limiting of users belonging to any AS that is configured
|
||||||
|
# not to be rate limited in its registration file (rate_limited: true|false).
|
||||||
|
if requester.app_service and not requester.app_service.is_rate_limited():
|
||||||
|
return True, -1.0
|
||||||
|
|
||||||
|
return self.can_do_action(
|
||||||
|
requester.user.to_string(), rate_hz, burst_count, update, _time_now_s
|
||||||
|
)
|
||||||
|
|
||||||
def can_do_action(
|
def can_do_action(
|
||||||
self,
|
self,
|
||||||
key: Any,
|
key: Any,
|
||||||
|
|
|
@ -961,11 +961,10 @@ class ServerConfig(Config):
|
||||||
# min_lifetime: 1d
|
# min_lifetime: 1d
|
||||||
# max_lifetime: 1y
|
# max_lifetime: 1y
|
||||||
|
|
||||||
# Retention policy limits. If set, a user won't be able to send a
|
# Retention policy limits. If set, and the state of a room contains a
|
||||||
# 'm.room.retention' event which features a 'min_lifetime' or a 'max_lifetime'
|
# 'm.room.retention' event in its state which contains a 'min_lifetime' or a
|
||||||
# that's not within this range. This is especially useful in closed federations,
|
# 'max_lifetime' that's out of these bounds, Synapse will cap the room's policy
|
||||||
# in which server admins can make sure every federating server applies the same
|
# to these limits when running purge jobs.
|
||||||
# rules.
|
|
||||||
#
|
#
|
||||||
#allowed_lifetime_min: 1d
|
#allowed_lifetime_min: 1d
|
||||||
#allowed_lifetime_max: 1y
|
#allowed_lifetime_max: 1y
|
||||||
|
@ -991,12 +990,19 @@ class ServerConfig(Config):
|
||||||
# (e.g. every 12h), but not want that purge to be performed by a job that's
|
# (e.g. every 12h), but not want that purge to be performed by a job that's
|
||||||
# iterating over every room it knows, which could be heavy on the server.
|
# iterating over every room it knows, which could be heavy on the server.
|
||||||
#
|
#
|
||||||
|
# If any purge job is configured, it is strongly recommended to have at least
|
||||||
|
# a single job with neither 'shortest_max_lifetime' nor 'longest_max_lifetime'
|
||||||
|
# set, or one job without 'shortest_max_lifetime' and one job without
|
||||||
|
# 'longest_max_lifetime' set. Otherwise some rooms might be ignored, even if
|
||||||
|
# 'allowed_lifetime_min' and 'allowed_lifetime_max' are set, because capping a
|
||||||
|
# room's policy to these values is done after the policies are retrieved from
|
||||||
|
# Synapse's database (which is done using the range specified in a purge job's
|
||||||
|
# configuration).
|
||||||
|
#
|
||||||
#purge_jobs:
|
#purge_jobs:
|
||||||
# - shortest_max_lifetime: 1d
|
# - longest_max_lifetime: 3d
|
||||||
# longest_max_lifetime: 3d
|
|
||||||
# interval: 12h
|
# interval: 12h
|
||||||
# - shortest_max_lifetime: 3d
|
# - shortest_max_lifetime: 3d
|
||||||
# longest_max_lifetime: 1y
|
|
||||||
# interval: 1d
|
# interval: 1d
|
||||||
|
|
||||||
# Inhibits the /requestToken endpoints from returning an error that might leak
|
# Inhibits the /requestToken endpoints from returning an error that might leak
|
||||||
|
|
|
@ -757,9 +757,8 @@ class ServerKeyFetcher(BaseV2KeyFetcher):
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error getting keys %s from %s", key_ids, server_name)
|
logger.exception("Error getting keys %s from %s", key_ids, server_name)
|
||||||
|
|
||||||
return await yieldable_gather_results(
|
await yieldable_gather_results(get_key, keys_to_fetch.items())
|
||||||
get_key, keys_to_fetch.items()
|
return results
|
||||||
).addCallback(lambda _: results)
|
|
||||||
|
|
||||||
async def get_server_verify_key_v2_direct(self, server_name, key_ids):
|
async def get_server_verify_key_v2_direct(self, server_name, key_ids):
|
||||||
"""
|
"""
|
||||||
|
@ -769,7 +768,7 @@ class ServerKeyFetcher(BaseV2KeyFetcher):
|
||||||
key_ids (iterable[str]):
|
key_ids (iterable[str]):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[dict[str, FetchKeyResult]]: map from key ID to lookup result
|
dict[str, FetchKeyResult]: map from key ID to lookup result
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
KeyLookupError if there was a problem making the lookup
|
KeyLookupError if there was a problem making the lookup
|
||||||
|
|
|
@ -47,7 +47,7 @@ def check(
|
||||||
Args:
|
Args:
|
||||||
room_version_obj: the version of the room
|
room_version_obj: the version of the room
|
||||||
event: the event being checked.
|
event: the event being checked.
|
||||||
auth_events (dict: event-key -> event): the existing room state.
|
auth_events: the existing room state.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AuthError if the checks fail
|
AuthError if the checks fail
|
||||||
|
|
|
@ -133,6 +133,8 @@ class _EventInternalMetadata(object):
|
||||||
rejection. This is needed as those events are marked as outliers, but
|
rejection. This is needed as those events are marked as outliers, but
|
||||||
they still need to be processed as if they're new events (e.g. updating
|
they still need to be processed as if they're new events (e.g. updating
|
||||||
invite state in the database, relaying to clients, etc).
|
invite state in the database, relaying to clients, etc).
|
||||||
|
|
||||||
|
(Added in synapse 0.99.0, so may be unreliable for events received before that)
|
||||||
"""
|
"""
|
||||||
return self._dict.get("out_of_band_membership", False)
|
return self._dict.get("out_of_band_membership", False)
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from synapse.spam_checker_api import SpamCheckerApi
|
from synapse.spam_checker_api import RegistrationBehaviour, SpamCheckerApi
|
||||||
|
from synapse.types import Collection
|
||||||
|
|
||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
|
@ -160,3 +161,33 @@ class SpamChecker(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def check_registration_for_spam(
|
||||||
|
self,
|
||||||
|
email_threepid: Optional[dict],
|
||||||
|
username: Optional[str],
|
||||||
|
request_info: Collection[Tuple[str, str]],
|
||||||
|
) -> RegistrationBehaviour:
|
||||||
|
"""Checks if we should allow the given registration request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email_threepid: The email threepid used for registering, if any
|
||||||
|
username: The request user name, if any
|
||||||
|
request_info: List of tuples of user agent and IP that
|
||||||
|
were used during the registration process.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Enum for how the request should be handled
|
||||||
|
"""
|
||||||
|
|
||||||
|
for spam_checker in self.spam_checkers:
|
||||||
|
# For backwards compatibility, only run if the method exists on the
|
||||||
|
# spam checker
|
||||||
|
checker = getattr(spam_checker, "check_registration_for_spam", None)
|
||||||
|
if checker:
|
||||||
|
behaviour = checker(email_threepid, username, request_info)
|
||||||
|
assert isinstance(behaviour, RegistrationBehaviour)
|
||||||
|
if behaviour != RegistrationBehaviour.ALLOW:
|
||||||
|
return behaviour
|
||||||
|
|
||||||
|
return RegistrationBehaviour.ALLOW
|
||||||
|
|
|
@ -74,15 +74,14 @@ class EventValidator(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
if event.type == EventTypes.Retention:
|
if event.type == EventTypes.Retention:
|
||||||
self._validate_retention(event, config)
|
self._validate_retention(event)
|
||||||
|
|
||||||
def _validate_retention(self, event, config):
|
def _validate_retention(self, event):
|
||||||
"""Checks that an event that defines the retention policy for a room respects the
|
"""Checks that an event that defines the retention policy for a room respects the
|
||||||
boundaries imposed by the server's administrator.
|
format enforced by the spec.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (FrozenEvent): The event to validate.
|
event (FrozenEvent): The event to validate.
|
||||||
config (Config): The homeserver's configuration.
|
|
||||||
"""
|
"""
|
||||||
min_lifetime = event.content.get("min_lifetime")
|
min_lifetime = event.content.get("min_lifetime")
|
||||||
max_lifetime = event.content.get("max_lifetime")
|
max_lifetime = event.content.get("max_lifetime")
|
||||||
|
@ -95,32 +94,6 @@ class EventValidator(object):
|
||||||
errcode=Codes.BAD_JSON,
|
errcode=Codes.BAD_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
|
||||||
config.retention_allowed_lifetime_min is not None
|
|
||||||
and min_lifetime < config.retention_allowed_lifetime_min
|
|
||||||
):
|
|
||||||
raise SynapseError(
|
|
||||||
code=400,
|
|
||||||
msg=(
|
|
||||||
"'min_lifetime' can't be lower than the minimum allowed"
|
|
||||||
" value enforced by the server's administrator"
|
|
||||||
),
|
|
||||||
errcode=Codes.BAD_JSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
config.retention_allowed_lifetime_max is not None
|
|
||||||
and min_lifetime > config.retention_allowed_lifetime_max
|
|
||||||
):
|
|
||||||
raise SynapseError(
|
|
||||||
code=400,
|
|
||||||
msg=(
|
|
||||||
"'min_lifetime' can't be greater than the maximum allowed"
|
|
||||||
" value enforced by the server's administrator"
|
|
||||||
),
|
|
||||||
errcode=Codes.BAD_JSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
if max_lifetime is not None:
|
if max_lifetime is not None:
|
||||||
if not isinstance(max_lifetime, int):
|
if not isinstance(max_lifetime, int):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
|
@ -129,32 +102,6 @@ class EventValidator(object):
|
||||||
errcode=Codes.BAD_JSON,
|
errcode=Codes.BAD_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
|
||||||
config.retention_allowed_lifetime_min is not None
|
|
||||||
and max_lifetime < config.retention_allowed_lifetime_min
|
|
||||||
):
|
|
||||||
raise SynapseError(
|
|
||||||
code=400,
|
|
||||||
msg=(
|
|
||||||
"'max_lifetime' can't be lower than the minimum allowed value"
|
|
||||||
" enforced by the server's administrator"
|
|
||||||
),
|
|
||||||
errcode=Codes.BAD_JSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
config.retention_allowed_lifetime_max is not None
|
|
||||||
and max_lifetime > config.retention_allowed_lifetime_max
|
|
||||||
):
|
|
||||||
raise SynapseError(
|
|
||||||
code=400,
|
|
||||||
msg=(
|
|
||||||
"'max_lifetime' can't be greater than the maximum allowed"
|
|
||||||
" value enforced by the server's administrator"
|
|
||||||
),
|
|
||||||
errcode=Codes.BAD_JSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
min_lifetime is not None
|
min_lifetime is not None
|
||||||
and max_lifetime is not None
|
and max_lifetime is not None
|
||||||
|
|
|
@ -28,7 +28,6 @@ from typing import (
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
from prometheus_client import Counter, Histogram
|
from prometheus_client import Counter, Histogram
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
@ -63,7 +62,7 @@ from synapse.replication.http.federation import (
|
||||||
ReplicationGetQueryRestServlet,
|
ReplicationGetQueryRestServlet,
|
||||||
)
|
)
|
||||||
from synapse.types import JsonDict, get_domain_from_id
|
from synapse.types import JsonDict, get_domain_from_id
|
||||||
from synapse.util import glob_to_regex, unwrapFirstError
|
from synapse.util import glob_to_regex, json_decoder, unwrapFirstError
|
||||||
from synapse.util.async_helpers import Linearizer, concurrently_execute
|
from synapse.util.async_helpers import Linearizer, concurrently_execute
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
|
|
||||||
|
@ -551,7 +550,7 @@ class FederationServer(FederationBase):
|
||||||
for device_id, keys in device_keys.items():
|
for device_id, keys in device_keys.items():
|
||||||
for key_id, json_str in keys.items():
|
for key_id, json_str in keys.items():
|
||||||
json_result.setdefault(user_id, {})[device_id] = {
|
json_result.setdefault(user_id, {})[device_id] = {
|
||||||
key_id: json.loads(json_str)
|
key_id: json_decoder.decode(json_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
@ -347,10 +347,10 @@ class FederationSender(object):
|
||||||
room_id = receipt.room_id
|
room_id = receipt.room_id
|
||||||
|
|
||||||
# Work out which remote servers should be poked and poke them.
|
# Work out which remote servers should be poked and poke them.
|
||||||
domains = await self.state.get_current_hosts_in_room(room_id)
|
domains_set = await self.state.get_current_hosts_in_room(room_id)
|
||||||
domains = [
|
domains = [
|
||||||
d
|
d
|
||||||
for d in domains
|
for d in domains_set
|
||||||
if d != self.server_name
|
if d != self.server_name
|
||||||
and self._federation_shard_config.should_handle(self._instance_name, d)
|
and self._federation_shard_config.should_handle(self._instance_name, d)
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, List, Tuple
|
from typing import TYPE_CHECKING, List, Tuple
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from synapse.api.errors import HttpResponseException
|
from synapse.api.errors import HttpResponseException
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase
|
||||||
from synapse.federation.persistence import TransactionActions
|
from synapse.federation.persistence import TransactionActions
|
||||||
|
@ -28,6 +26,7 @@ from synapse.logging.opentracing import (
|
||||||
tags,
|
tags,
|
||||||
whitelisted_homeserver,
|
whitelisted_homeserver,
|
||||||
)
|
)
|
||||||
|
from synapse.util import json_decoder
|
||||||
from synapse.util.metrics import measure_func
|
from synapse.util.metrics import measure_func
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -75,7 +74,7 @@ class TransactionManager(object):
|
||||||
for edu in pending_edus:
|
for edu in pending_edus:
|
||||||
context = edu.get_context()
|
context = edu.get_context()
|
||||||
if context:
|
if context:
|
||||||
span_contexts.append(extract_text_map(json.loads(context)))
|
span_contexts.append(extract_text_map(json_decoder.decode(context)))
|
||||||
if keep_destination:
|
if keep_destination:
|
||||||
edu.strip_context()
|
edu.strip_context()
|
||||||
|
|
||||||
|
|
|
@ -364,6 +364,14 @@ class AuthHandler(BaseHandler):
|
||||||
# authentication flow.
|
# authentication flow.
|
||||||
await self.store.set_ui_auth_clientdict(sid, clientdict)
|
await self.store.set_ui_auth_clientdict(sid, clientdict)
|
||||||
|
|
||||||
|
user_agent = request.requestHeaders.getRawHeaders(b"User-Agent", default=[b""])[
|
||||||
|
0
|
||||||
|
].decode("ascii", "surrogateescape")
|
||||||
|
|
||||||
|
await self.store.add_user_agent_ip_to_ui_auth_session(
|
||||||
|
session.session_id, user_agent, clientip
|
||||||
|
)
|
||||||
|
|
||||||
if not authdict:
|
if not authdict:
|
||||||
raise InteractiveAuthIncompleteError(
|
raise InteractiveAuthIncompleteError(
|
||||||
session.session_id, self._auth_dict_for_flows(flows, session.session_id)
|
session.session_id, self._auth_dict_for_flows(flows, session.session_id)
|
||||||
|
|
|
@ -35,6 +35,7 @@ class CasHandler:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
self.hs = hs
|
||||||
self._hostname = hs.hostname
|
self._hostname = hs.hostname
|
||||||
self._auth_handler = hs.get_auth_handler()
|
self._auth_handler = hs.get_auth_handler()
|
||||||
self._registration_handler = hs.get_registration_handler()
|
self._registration_handler = hs.get_registration_handler()
|
||||||
|
@ -210,8 +211,16 @@ class CasHandler:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not registered_user_id:
|
if not registered_user_id:
|
||||||
|
# Pull out the user-agent and IP from the request.
|
||||||
|
user_agent = request.requestHeaders.getRawHeaders(
|
||||||
|
b"User-Agent", default=[b""]
|
||||||
|
)[0].decode("ascii", "surrogateescape")
|
||||||
|
ip_address = self.hs.get_ip_from_request(request)
|
||||||
|
|
||||||
registered_user_id = await self._registration_handler.register_user(
|
registered_user_id = await self._registration_handler.register_user(
|
||||||
localpart=localpart, default_display_name=user_display_name
|
localpart=localpart,
|
||||||
|
default_display_name=user_display_name,
|
||||||
|
user_agent_ips=(user_agent, ip_address),
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._auth_handler.complete_sso_login(
|
await self._auth_handler.complete_sso_login(
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.logging.context import run_in_background
|
from synapse.logging.context import run_in_background
|
||||||
from synapse.logging.opentracing import (
|
from synapse.logging.opentracing import (
|
||||||
|
@ -27,6 +25,7 @@ from synapse.logging.opentracing import (
|
||||||
start_active_span,
|
start_active_span,
|
||||||
)
|
)
|
||||||
from synapse.types import UserID, get_domain_from_id
|
from synapse.types import UserID, get_domain_from_id
|
||||||
|
from synapse.util import json_encoder
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -174,7 +173,7 @@ class DeviceMessageHandler(object):
|
||||||
"sender": sender_user_id,
|
"sender": sender_user_id,
|
||||||
"type": message_type,
|
"type": message_type,
|
||||||
"message_id": message_id,
|
"message_id": message_id,
|
||||||
"org.matrix.opentracing_context": json.dumps(context),
|
"org.matrix.opentracing_context": json_encoder.encode(context),
|
||||||
}
|
}
|
||||||
|
|
||||||
log_kv({"local_messages": local_messages})
|
log_kv({"local_messages": local_messages})
|
||||||
|
|
|
@ -23,6 +23,7 @@ from synapse.api.errors import (
|
||||||
CodeMessageException,
|
CodeMessageException,
|
||||||
Codes,
|
Codes,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
ShadowBanError,
|
||||||
StoreError,
|
StoreError,
|
||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
|
@ -199,6 +200,8 @@ class DirectoryHandler(BaseHandler):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._update_canonical_alias(requester, user_id, room_id, room_alias)
|
await self._update_canonical_alias(requester, user_id, room_id, room_alias)
|
||||||
|
except ShadowBanError as e:
|
||||||
|
logger.info("Failed to update alias events due to shadow-ban: %s", e)
|
||||||
except AuthError as e:
|
except AuthError as e:
|
||||||
logger.info("Failed to update alias events: %s", e)
|
logger.info("Failed to update alias events: %s", e)
|
||||||
|
|
||||||
|
@ -292,6 +295,9 @@ class DirectoryHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
Send an updated canonical alias event if the removed alias was set as
|
Send an updated canonical alias event if the removed alias was set as
|
||||||
the canonical alias or listed in the alt_aliases field.
|
the canonical alias or listed in the alt_aliases field.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester has been shadow-banned.
|
||||||
"""
|
"""
|
||||||
alias_event = await self.state.get_current_state(
|
alias_event = await self.state.get_current_state(
|
||||||
room_id, EventTypes.CanonicalAlias, ""
|
room_id, EventTypes.CanonicalAlias, ""
|
||||||
|
|
|
@ -19,7 +19,7 @@ import logging
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
from canonicaljson import encode_canonical_json, json
|
from canonicaljson import encode_canonical_json
|
||||||
from signedjson.key import VerifyKey, decode_verify_key_bytes
|
from signedjson.key import VerifyKey, decode_verify_key_bytes
|
||||||
from signedjson.sign import SignatureVerifyException, verify_signed_json
|
from signedjson.sign import SignatureVerifyException, verify_signed_json
|
||||||
from unpaddedbase64 import decode_base64
|
from unpaddedbase64 import decode_base64
|
||||||
|
@ -35,7 +35,7 @@ from synapse.types import (
|
||||||
get_domain_from_id,
|
get_domain_from_id,
|
||||||
get_verify_key_from_cross_signing_key,
|
get_verify_key_from_cross_signing_key,
|
||||||
)
|
)
|
||||||
from synapse.util import unwrapFirstError
|
from synapse.util import json_decoder, unwrapFirstError
|
||||||
from synapse.util.async_helpers import Linearizer
|
from synapse.util.async_helpers import Linearizer
|
||||||
from synapse.util.caches.expiringcache import ExpiringCache
|
from synapse.util.caches.expiringcache import ExpiringCache
|
||||||
from synapse.util.retryutils import NotRetryingDestination
|
from synapse.util.retryutils import NotRetryingDestination
|
||||||
|
@ -404,7 +404,7 @@ class E2eKeysHandler(object):
|
||||||
for device_id, keys in device_keys.items():
|
for device_id, keys in device_keys.items():
|
||||||
for key_id, json_bytes in keys.items():
|
for key_id, json_bytes in keys.items():
|
||||||
json_result.setdefault(user_id, {})[device_id] = {
|
json_result.setdefault(user_id, {})[device_id] = {
|
||||||
key_id: json.loads(json_bytes)
|
key_id: json_decoder.decode(json_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@trace
|
@trace
|
||||||
|
@ -1186,7 +1186,7 @@ def _exception_to_failure(e):
|
||||||
|
|
||||||
|
|
||||||
def _one_time_keys_match(old_key_json, new_key):
|
def _one_time_keys_match(old_key_json, new_key):
|
||||||
old_key = json.loads(old_key_json)
|
old_key = json_decoder.decode(old_key_json)
|
||||||
|
|
||||||
# if either is a string rather than an object, they must match exactly
|
# if either is a string rather than an object, they must match exactly
|
||||||
if not isinstance(old_key, dict) or not isinstance(new_key, dict):
|
if not isinstance(old_key, dict) or not isinstance(new_key, dict):
|
||||||
|
|
|
@ -1777,9 +1777,7 @@ class FederationHandler(BaseHandler):
|
||||||
"""Returns the state at the event. i.e. not including said event.
|
"""Returns the state at the event. i.e. not including said event.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event = await self.store.get_event(
|
event = await self.store.get_event(event_id, check_room_id=room_id)
|
||||||
event_id, allow_none=False, check_room_id=room_id
|
|
||||||
)
|
|
||||||
|
|
||||||
state_groups = await self.state_store.get_state_groups(room_id, [event_id])
|
state_groups = await self.state_store.get_state_groups(room_id, [event_id])
|
||||||
|
|
||||||
|
@ -1805,9 +1803,7 @@ class FederationHandler(BaseHandler):
|
||||||
async def get_state_ids_for_pdu(self, room_id: str, event_id: str) -> List[str]:
|
async def get_state_ids_for_pdu(self, room_id: str, event_id: str) -> List[str]:
|
||||||
"""Returns the state at the event. i.e. not including said event.
|
"""Returns the state at the event. i.e. not including said event.
|
||||||
"""
|
"""
|
||||||
event = await self.store.get_event(
|
event = await self.store.get_event(event_id, check_room_id=room_id)
|
||||||
event_id, allow_none=False, check_room_id=room_id
|
|
||||||
)
|
|
||||||
|
|
||||||
state_groups = await self.state_store.get_state_groups_ids(room_id, [event_id])
|
state_groups = await self.state_store.get_state_groups_ids(room_id, [event_id])
|
||||||
|
|
||||||
|
@ -2138,10 +2134,10 @@ class FederationHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
state_sets = list(state_sets.values())
|
state_sets = list(state_sets.values())
|
||||||
state_sets.append(state)
|
state_sets.append(state)
|
||||||
current_state_ids = await self.state_handler.resolve_events(
|
current_states = await self.state_handler.resolve_events(
|
||||||
room_version, state_sets, event
|
room_version, state_sets, event
|
||||||
)
|
)
|
||||||
current_state_ids = {k: e.event_id for k, e in current_state_ids.items()}
|
current_state_ids = {k: e.event_id for k, e in current_states.items()}
|
||||||
else:
|
else:
|
||||||
current_state_ids = await self.state_handler.get_current_state_ids(
|
current_state_ids = await self.state_handler.get_current_state_ids(
|
||||||
event.room_id, latest_event_ids=extrem_ids
|
event.room_id, latest_event_ids=extrem_ids
|
||||||
|
@ -2153,11 +2149,13 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
# Now check if event pass auth against said current state
|
# Now check if event pass auth against said current state
|
||||||
auth_types = auth_types_for_event(event)
|
auth_types = auth_types_for_event(event)
|
||||||
current_state_ids = [e for k, e in current_state_ids.items() if k in auth_types]
|
current_state_ids_list = [
|
||||||
|
e for k, e in current_state_ids.items() if k in auth_types
|
||||||
|
]
|
||||||
|
|
||||||
current_auth_events = await self.store.get_events(current_state_ids)
|
auth_events_map = await self.store.get_events(current_state_ids_list)
|
||||||
current_auth_events = {
|
current_auth_events = {
|
||||||
(e.type, e.state_key): e for e in current_auth_events.values()
|
(e.type, e.state_key): e for e in auth_events_map.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -2173,9 +2171,7 @@ class FederationHandler(BaseHandler):
|
||||||
if not in_room:
|
if not in_room:
|
||||||
raise AuthError(403, "Host not in room.")
|
raise AuthError(403, "Host not in room.")
|
||||||
|
|
||||||
event = await self.store.get_event(
|
event = await self.store.get_event(event_id, check_room_id=room_id)
|
||||||
event_id, allow_none=False, check_room_id=room_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# Just go through and process each event in `remote_auth_chain`. We
|
# Just go through and process each event in `remote_auth_chain`. We
|
||||||
# don't want to fall into the trap of `missing` being wrong.
|
# don't want to fall into the trap of `missing` being wrong.
|
||||||
|
|
|
@ -21,8 +21,6 @@ import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from typing import Awaitable, Callable, Dict, List, Optional, Tuple
|
from typing import Awaitable, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from twisted.internet.error import TimeoutError
|
from twisted.internet.error import TimeoutError
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
|
@ -34,6 +32,7 @@ from synapse.api.errors import (
|
||||||
from synapse.config.emailconfig import ThreepidBehaviour
|
from synapse.config.emailconfig import ThreepidBehaviour
|
||||||
from synapse.http.client import SimpleHttpClient
|
from synapse.http.client import SimpleHttpClient
|
||||||
from synapse.types import JsonDict, Requester
|
from synapse.types import JsonDict, Requester
|
||||||
|
from synapse.util import json_decoder
|
||||||
from synapse.util.hash import sha256_and_url_safe_base64
|
from synapse.util.hash import sha256_and_url_safe_base64
|
||||||
from synapse.util.stringutils import assert_valid_client_secret, random_string
|
from synapse.util.stringutils import assert_valid_client_secret, random_string
|
||||||
|
|
||||||
|
@ -177,7 +176,7 @@ class IdentityHandler(BaseHandler):
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
raise SynapseError(500, "Timed out contacting identity server")
|
raise SynapseError(500, "Timed out contacting identity server")
|
||||||
except CodeMessageException as e:
|
except CodeMessageException as e:
|
||||||
data = json.loads(e.msg) # XXX WAT?
|
data = json_decoder.decode(e.msg) # XXX WAT?
|
||||||
return data
|
return data
|
||||||
|
|
||||||
logger.info("Got 404 when POSTing JSON %s, falling back to v1 URL", bind_url)
|
logger.info("Got 404 when POSTing JSON %s, falling back to v1 URL", bind_url)
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
# 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
|
||||||
|
import random
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from canonicaljson import encode_canonical_json, json
|
from canonicaljson import encode_canonical_json
|
||||||
|
|
||||||
from twisted.internet.interfaces import IDelayedCall
|
from twisted.internet.interfaces import IDelayedCall
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ from synapse.api.errors import (
|
||||||
Codes,
|
Codes,
|
||||||
ConsentNotGivenError,
|
ConsentNotGivenError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
ShadowBanError,
|
||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
|
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions
|
||||||
|
@ -55,6 +57,7 @@ from synapse.types import (
|
||||||
UserID,
|
UserID,
|
||||||
create_requester,
|
create_requester,
|
||||||
)
|
)
|
||||||
|
from synapse.util import json_decoder
|
||||||
from synapse.util.async_helpers import Linearizer
|
from synapse.util.async_helpers import Linearizer
|
||||||
from synapse.util.frozenutils import frozendict_json_encoder
|
from synapse.util.frozenutils import frozendict_json_encoder
|
||||||
from synapse.util.metrics import measure_func
|
from synapse.util.metrics import measure_func
|
||||||
|
@ -92,12 +95,7 @@ class MessageHandler(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_room_data(
|
async def get_room_data(
|
||||||
self,
|
self, user_id: str, room_id: str, event_type: str, state_key: str,
|
||||||
user_id: str,
|
|
||||||
room_id: str,
|
|
||||||
event_type: str,
|
|
||||||
state_key: str,
|
|
||||||
is_guest: bool,
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
""" Get data from a room.
|
""" Get data from a room.
|
||||||
|
|
||||||
|
@ -106,11 +104,10 @@ class MessageHandler(object):
|
||||||
room_id
|
room_id
|
||||||
event_type
|
event_type
|
||||||
state_key
|
state_key
|
||||||
is_guest
|
|
||||||
Returns:
|
Returns:
|
||||||
The path data content.
|
The path data content.
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError if something went wrong.
|
SynapseError or AuthError if the user is not in the room
|
||||||
"""
|
"""
|
||||||
(
|
(
|
||||||
membership,
|
membership,
|
||||||
|
@ -127,6 +124,16 @@ class MessageHandler(object):
|
||||||
[membership_event_id], StateFilter.from_types([key])
|
[membership_event_id], StateFilter.from_types([key])
|
||||||
)
|
)
|
||||||
data = room_state[membership_event_id].get(key)
|
data = room_state[membership_event_id].get(key)
|
||||||
|
else:
|
||||||
|
# check_user_in_room_or_world_readable, if it doesn't raise an AuthError, should
|
||||||
|
# only ever return a Membership.JOIN/LEAVE object
|
||||||
|
#
|
||||||
|
# Safeguard in case it returned something else
|
||||||
|
logger.error(
|
||||||
|
"Attempted to retrieve data from a room for a user that has never been in it. "
|
||||||
|
"This should not have happened."
|
||||||
|
)
|
||||||
|
raise SynapseError(403, "User not in room", errcode=Codes.FORBIDDEN)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -646,24 +653,35 @@ class EventCreationHandler(object):
|
||||||
event: EventBase,
|
event: EventBase,
|
||||||
context: EventContext,
|
context: EventContext,
|
||||||
ratelimit: bool = True,
|
ratelimit: bool = True,
|
||||||
|
ignore_shadow_ban: bool = False,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Persists and notifies local clients and federation of an event.
|
Persists and notifies local clients and federation of an event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
requester
|
requester: The requester sending the event.
|
||||||
event the event to send.
|
event: The event to send.
|
||||||
context: the context of the event.
|
context: The context of the event.
|
||||||
ratelimit: Whether to rate limit this send.
|
ratelimit: Whether to rate limit this send.
|
||||||
|
ignore_shadow_ban: True if shadow-banned users should be allowed to
|
||||||
|
send this event.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
The stream_id of the persisted event.
|
The stream_id of the persisted event.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester has been shadow-banned.
|
||||||
"""
|
"""
|
||||||
if event.type == EventTypes.Member:
|
if event.type == EventTypes.Member:
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
500, "Tried to send member event through non-member codepath"
|
500, "Tried to send member event through non-member codepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not ignore_shadow_ban and requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
user = UserID.from_string(event.sender)
|
user = UserID.from_string(event.sender)
|
||||||
|
|
||||||
assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
|
assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
|
||||||
|
@ -717,12 +735,28 @@ class EventCreationHandler(object):
|
||||||
event_dict: dict,
|
event_dict: dict,
|
||||||
ratelimit: bool = True,
|
ratelimit: bool = True,
|
||||||
txn_id: Optional[str] = None,
|
txn_id: Optional[str] = None,
|
||||||
|
ignore_shadow_ban: bool = False,
|
||||||
) -> Tuple[EventBase, int]:
|
) -> Tuple[EventBase, int]:
|
||||||
"""
|
"""
|
||||||
Creates an event, then sends it.
|
Creates an event, then sends it.
|
||||||
|
|
||||||
See self.create_event and self.send_nonmember_event.
|
See self.create_event and self.send_nonmember_event.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: The requester sending the event.
|
||||||
|
event_dict: An entire event.
|
||||||
|
ratelimit: Whether to rate limit this send.
|
||||||
|
txn_id: The transaction ID.
|
||||||
|
ignore_shadow_ban: True if shadow-banned users should be allowed to
|
||||||
|
send this event.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester has been shadow-banned.
|
||||||
"""
|
"""
|
||||||
|
if not ignore_shadow_ban and requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
# We limit the number of concurrent event sends in a room so that we
|
# We limit the number of concurrent event sends in a room so that we
|
||||||
# don't fork the DAG too much. If we don't limit then we can end up in
|
# don't fork the DAG too much. If we don't limit then we can end up in
|
||||||
|
@ -741,7 +775,11 @@ class EventCreationHandler(object):
|
||||||
raise SynapseError(403, spam_error, Codes.FORBIDDEN)
|
raise SynapseError(403, spam_error, Codes.FORBIDDEN)
|
||||||
|
|
||||||
stream_id = await self.send_nonmember_event(
|
stream_id = await self.send_nonmember_event(
|
||||||
requester, event, context, ratelimit=ratelimit
|
requester,
|
||||||
|
event,
|
||||||
|
context,
|
||||||
|
ratelimit=ratelimit,
|
||||||
|
ignore_shadow_ban=ignore_shadow_ban,
|
||||||
)
|
)
|
||||||
return event, stream_id
|
return event, stream_id
|
||||||
|
|
||||||
|
@ -866,7 +904,7 @@ class EventCreationHandler(object):
|
||||||
# Ensure that we can round trip before trying to persist in db
|
# Ensure that we can round trip before trying to persist in db
|
||||||
try:
|
try:
|
||||||
dump = frozendict_json_encoder.encode(event.content)
|
dump = frozendict_json_encoder.encode(event.content)
|
||||||
json.loads(dump)
|
json_decoder.decode(dump)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to encode content: %r", event.content)
|
logger.exception("Failed to encode content: %r", event.content)
|
||||||
raise
|
raise
|
||||||
|
@ -962,7 +1000,7 @@ class EventCreationHandler(object):
|
||||||
allow_none=True,
|
allow_none=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
is_admin_redaction = (
|
is_admin_redaction = bool(
|
||||||
original_event and event.sender != original_event.sender
|
original_event and event.sender != original_event.sender
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1082,8 +1120,8 @@ class EventCreationHandler(object):
|
||||||
auth_events_ids = self.auth.compute_auth_events(
|
auth_events_ids = self.auth.compute_auth_events(
|
||||||
event, prev_state_ids, for_verification=True
|
event, prev_state_ids, for_verification=True
|
||||||
)
|
)
|
||||||
auth_events = await self.store.get_events(auth_events_ids)
|
auth_events_map = await self.store.get_events(auth_events_ids)
|
||||||
auth_events = {(e.type, e.state_key): e for e in auth_events.values()}
|
auth_events = {(e.type, e.state_key): e for e in auth_events_map.values()}
|
||||||
|
|
||||||
room_version = await self.store.get_room_version_id(event.room_id)
|
room_version = await self.store.get_room_version_id(event.room_id)
|
||||||
room_version_obj = KNOWN_ROOM_VERSIONS[room_version]
|
room_version_obj = KNOWN_ROOM_VERSIONS[room_version]
|
||||||
|
@ -1181,8 +1219,14 @@ class EventCreationHandler(object):
|
||||||
|
|
||||||
event.internal_metadata.proactively_send = False
|
event.internal_metadata.proactively_send = False
|
||||||
|
|
||||||
|
# Since this is a dummy-event it is OK if it is sent by a
|
||||||
|
# shadow-banned user.
|
||||||
await self.send_nonmember_event(
|
await self.send_nonmember_event(
|
||||||
requester, event, context, ratelimit=False
|
requester,
|
||||||
|
event,
|
||||||
|
context,
|
||||||
|
ratelimit=False,
|
||||||
|
ignore_shadow_ban=True,
|
||||||
)
|
)
|
||||||
dummy_event_sent = True
|
dummy_event_sent = True
|
||||||
break
|
break
|
||||||
|
|
|
@ -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.
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, Generic, List, Optional, Tuple, TypeVar
|
from typing import TYPE_CHECKING, Dict, Generic, List, Optional, Tuple, TypeVar
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
@ -39,6 +38,7 @@ from synapse.http.server import respond_with_html
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
from synapse.logging.context import make_deferred_yieldable
|
from synapse.logging.context import make_deferred_yieldable
|
||||||
from synapse.types import UserID, map_username_to_mxid_localpart
|
from synapse.types import UserID, map_username_to_mxid_localpart
|
||||||
|
from synapse.util import json_decoder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
@ -93,6 +93,7 @@ class OidcHandler:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hs: "HomeServer"):
|
def __init__(self, hs: "HomeServer"):
|
||||||
|
self.hs = hs
|
||||||
self._callback_url = hs.config.oidc_callback_url # type: str
|
self._callback_url = hs.config.oidc_callback_url # type: str
|
||||||
self._scopes = hs.config.oidc_scopes # type: List[str]
|
self._scopes = hs.config.oidc_scopes # type: List[str]
|
||||||
self._client_auth = ClientAuth(
|
self._client_auth = ClientAuth(
|
||||||
|
@ -367,7 +368,7 @@ class OidcHandler:
|
||||||
# and check for an error field. If not, we respond with a generic
|
# and check for an error field. If not, we respond with a generic
|
||||||
# error message.
|
# error message.
|
||||||
try:
|
try:
|
||||||
resp = json.loads(resp_body.decode("utf-8"))
|
resp = json_decoder.decode(resp_body.decode("utf-8"))
|
||||||
error = resp["error"]
|
error = resp["error"]
|
||||||
description = resp.get("error_description", error)
|
description = resp.get("error_description", error)
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
|
@ -384,7 +385,7 @@ class OidcHandler:
|
||||||
|
|
||||||
# Since it is a not a 5xx code, body should be a valid JSON. It will
|
# Since it is a not a 5xx code, body should be a valid JSON. It will
|
||||||
# raise if not.
|
# raise if not.
|
||||||
resp = json.loads(resp_body.decode("utf-8"))
|
resp = json_decoder.decode(resp_body.decode("utf-8"))
|
||||||
|
|
||||||
if "error" in resp:
|
if "error" in resp:
|
||||||
error = resp["error"]
|
error = resp["error"]
|
||||||
|
@ -689,9 +690,17 @@ class OidcHandler:
|
||||||
self._render_error(request, "invalid_token", str(e))
|
self._render_error(request, "invalid_token", str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Pull out the user-agent and IP from the request.
|
||||||
|
user_agent = request.requestHeaders.getRawHeaders(b"User-Agent", default=[b""])[
|
||||||
|
0
|
||||||
|
].decode("ascii", "surrogateescape")
|
||||||
|
ip_address = self.hs.get_ip_from_request(request)
|
||||||
|
|
||||||
# Call the mapper to register/login the user
|
# Call the mapper to register/login the user
|
||||||
try:
|
try:
|
||||||
user_id = await self._map_userinfo_to_user(userinfo, token)
|
user_id = await self._map_userinfo_to_user(
|
||||||
|
userinfo, token, user_agent, ip_address
|
||||||
|
)
|
||||||
except MappingException as e:
|
except MappingException as e:
|
||||||
logger.exception("Could not map user")
|
logger.exception("Could not map user")
|
||||||
self._render_error(request, "mapping_error", str(e))
|
self._render_error(request, "mapping_error", str(e))
|
||||||
|
@ -828,7 +837,9 @@ class OidcHandler:
|
||||||
now = self._clock.time_msec()
|
now = self._clock.time_msec()
|
||||||
return now < expiry
|
return now < expiry
|
||||||
|
|
||||||
async def _map_userinfo_to_user(self, userinfo: UserInfo, token: Token) -> str:
|
async def _map_userinfo_to_user(
|
||||||
|
self, userinfo: UserInfo, token: Token, user_agent: str, ip_address: str
|
||||||
|
) -> str:
|
||||||
"""Maps a UserInfo object to a mxid.
|
"""Maps a UserInfo object to a mxid.
|
||||||
|
|
||||||
UserInfo should have a claim that uniquely identifies users. This claim
|
UserInfo should have a claim that uniquely identifies users. This claim
|
||||||
|
@ -843,6 +854,8 @@ class OidcHandler:
|
||||||
Args:
|
Args:
|
||||||
userinfo: an object representing the user
|
userinfo: an object representing the user
|
||||||
token: a dict with the tokens obtained from the provider
|
token: a dict with the tokens obtained from the provider
|
||||||
|
user_agent: The user agent of the client making the request.
|
||||||
|
ip_address: The IP address of the client making the request.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
MappingException: if there was an error while mapping some properties
|
MappingException: if there was an error while mapping some properties
|
||||||
|
@ -899,7 +912,9 @@ class OidcHandler:
|
||||||
# It's the first time this user is logging in and the mapped mxid was
|
# It's the first time this user is logging in and the mapped mxid was
|
||||||
# not taken, register the user
|
# not taken, register the user
|
||||||
registered_user_id = await self._registration_handler.register_user(
|
registered_user_id = await self._registration_handler.register_user(
|
||||||
localpart=localpart, default_display_name=attributes["display_name"],
|
localpart=localpart,
|
||||||
|
default_display_name=attributes["display_name"],
|
||||||
|
user_agent_ips=(user_agent, ip_address),
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._datastore.record_user_external_id(
|
await self._datastore.record_user_external_id(
|
||||||
|
|
|
@ -82,6 +82,9 @@ class PaginationHandler(object):
|
||||||
|
|
||||||
self._retention_default_max_lifetime = hs.config.retention_default_max_lifetime
|
self._retention_default_max_lifetime = hs.config.retention_default_max_lifetime
|
||||||
|
|
||||||
|
self._retention_allowed_lifetime_min = hs.config.retention_allowed_lifetime_min
|
||||||
|
self._retention_allowed_lifetime_max = hs.config.retention_allowed_lifetime_max
|
||||||
|
|
||||||
if hs.config.retention_enabled:
|
if hs.config.retention_enabled:
|
||||||
# Run the purge jobs described in the configuration file.
|
# Run the purge jobs described in the configuration file.
|
||||||
for job in hs.config.retention_purge_jobs:
|
for job in hs.config.retention_purge_jobs:
|
||||||
|
@ -111,7 +114,7 @@ class PaginationHandler(object):
|
||||||
the range to handle (inclusive). If None, it means that the range has no
|
the range to handle (inclusive). If None, it means that the range has no
|
||||||
upper limit.
|
upper limit.
|
||||||
"""
|
"""
|
||||||
# We want the storage layer to to include rooms with no retention policy in its
|
# We want the storage layer to include rooms with no retention policy in its
|
||||||
# return value only if a default retention policy is defined in the server's
|
# return value only if a default retention policy is defined in the server's
|
||||||
# configuration and that policy's 'max_lifetime' is either lower (or equal) than
|
# configuration and that policy's 'max_lifetime' is either lower (or equal) than
|
||||||
# max_ms or higher than min_ms (or both).
|
# max_ms or higher than min_ms (or both).
|
||||||
|
@ -152,13 +155,32 @@ class PaginationHandler(object):
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
max_lifetime = retention_policy["max_lifetime"]
|
# If max_lifetime is None, it means that the room has no retention policy.
|
||||||
|
# Given we only retrieve such rooms when there's a default retention policy
|
||||||
|
# defined in the server's configuration, we can safely assume that's the
|
||||||
|
# case and use it for this room.
|
||||||
|
max_lifetime = (
|
||||||
|
retention_policy["max_lifetime"] or self._retention_default_max_lifetime
|
||||||
|
)
|
||||||
|
|
||||||
if max_lifetime is None:
|
# Cap the effective max_lifetime to be within the range allowed in the
|
||||||
# If max_lifetime is None, it means that include_null equals True,
|
# config.
|
||||||
# therefore we can safely assume that there is a default policy defined
|
# We do this in two steps:
|
||||||
# in the server's configuration.
|
# 1. Make sure it's higher or equal to the minimum allowed value, and if
|
||||||
max_lifetime = self._retention_default_max_lifetime
|
# it's not replace it with that value. This is because the server
|
||||||
|
# operator can be required to not delete information before a given
|
||||||
|
# time, e.g. to comply with freedom of information laws.
|
||||||
|
# 2. Make sure the resulting value is lower or equal to the maximum allowed
|
||||||
|
# value, and if it's not replace it with that value. This is because the
|
||||||
|
# server operator can be required to delete any data after a specific
|
||||||
|
# amount of time.
|
||||||
|
if self._retention_allowed_lifetime_min is not None:
|
||||||
|
max_lifetime = max(self._retention_allowed_lifetime_min, max_lifetime)
|
||||||
|
|
||||||
|
if self._retention_allowed_lifetime_max is not None:
|
||||||
|
max_lifetime = min(max_lifetime, self._retention_allowed_lifetime_max)
|
||||||
|
|
||||||
|
logger.debug("[purge] max_lifetime for room %s: %s", room_id, max_lifetime)
|
||||||
|
|
||||||
# Figure out what token we should start purging at.
|
# Figure out what token we should start purging at.
|
||||||
ts = self.clock.time_msec() - max_lifetime
|
ts = self.clock.time_msec() - max_lifetime
|
||||||
|
|
|
@ -40,7 +40,7 @@ from synapse.metrics import LaterGauge
|
||||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
from synapse.state import StateHandler
|
from synapse.state import StateHandler
|
||||||
from synapse.storage.databases.main import DataStore
|
from synapse.storage.databases.main import DataStore
|
||||||
from synapse.types import JsonDict, UserID, get_domain_from_id
|
from synapse.types import Collection, JsonDict, UserID, get_domain_from_id
|
||||||
from synapse.util.async_helpers import Linearizer
|
from synapse.util.async_helpers import Linearizer
|
||||||
from synapse.util.caches.descriptors import cached
|
from synapse.util.caches.descriptors import cached
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
@ -1318,7 +1318,7 @@ async def get_interested_parties(
|
||||||
|
|
||||||
async def get_interested_remotes(
|
async def get_interested_remotes(
|
||||||
store: DataStore, states: List[UserPresenceState], state_handler: StateHandler
|
store: DataStore, states: List[UserPresenceState], state_handler: StateHandler
|
||||||
) -> List[Tuple[List[str], List[UserPresenceState]]]:
|
) -> List[Tuple[Collection[str], List[UserPresenceState]]]:
|
||||||
"""Given a list of presence states figure out which remote servers
|
"""Given a list of presence states figure out which remote servers
|
||||||
should be sent which.
|
should be sent which.
|
||||||
|
|
||||||
|
@ -1334,7 +1334,7 @@ async def get_interested_remotes(
|
||||||
each tuple the list of UserPresenceState should be sent to each
|
each tuple the list of UserPresenceState should be sent to each
|
||||||
destination
|
destination
|
||||||
"""
|
"""
|
||||||
hosts_and_states = []
|
hosts_and_states = [] # type: List[Tuple[Collection[str], List[UserPresenceState]]]
|
||||||
|
|
||||||
# First we look up the rooms each user is in (as well as any explicit
|
# First we look up the rooms each user is in (as well as any explicit
|
||||||
# subscriptions), then for each distinct room we look up the remote
|
# subscriptions), then for each distinct room we look up the remote
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
|
|
||||||
from synapse.api.errors import (
|
from synapse.api.errors import (
|
||||||
AuthError,
|
AuthError,
|
||||||
|
@ -213,8 +214,14 @@ class BaseProfileHandler(BaseHandler):
|
||||||
async def set_avatar_url(
|
async def set_avatar_url(
|
||||||
self, target_user, requester, new_avatar_url, by_admin=False
|
self, target_user, requester, new_avatar_url, by_admin=False
|
||||||
):
|
):
|
||||||
"""target_user is the user whose avatar_url is to be changed;
|
"""Set a new avatar URL for a user.
|
||||||
auth_user is the user attempting to make this change."""
|
|
||||||
|
Args:
|
||||||
|
target_user (UserID): the user whose avatar URL is to be changed.
|
||||||
|
requester (Requester): The user attempting to make this change.
|
||||||
|
new_avatar_url (str): The avatar URL to give this user.
|
||||||
|
by_admin (bool): Whether this change was made by an administrator.
|
||||||
|
"""
|
||||||
if not self.hs.is_mine(target_user):
|
if not self.hs.is_mine(target_user):
|
||||||
raise SynapseError(400, "User is not hosted on this homeserver")
|
raise SynapseError(400, "User is not hosted on this homeserver")
|
||||||
|
|
||||||
|
@ -278,6 +285,12 @@ class BaseProfileHandler(BaseHandler):
|
||||||
|
|
||||||
await self.ratelimit(requester)
|
await self.ratelimit(requester)
|
||||||
|
|
||||||
|
# Do not actually update the room state for shadow-banned users.
|
||||||
|
if requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
return
|
||||||
|
|
||||||
room_ids = await self.store.get_rooms_for_user(target_user.to_string())
|
room_ids = await self.store.get_rooms_for_user(target_user.to_string())
|
||||||
|
|
||||||
for room_id in room_ids:
|
for room_id in room_ids:
|
||||||
|
|
|
@ -26,6 +26,7 @@ from synapse.replication.http.register import (
|
||||||
ReplicationPostRegisterActionsServlet,
|
ReplicationPostRegisterActionsServlet,
|
||||||
ReplicationRegisterServlet,
|
ReplicationRegisterServlet,
|
||||||
)
|
)
|
||||||
|
from synapse.spam_checker_api import RegistrationBehaviour
|
||||||
from synapse.storage.state import StateFilter
|
from synapse.storage.state import StateFilter
|
||||||
from synapse.types import RoomAlias, UserID, create_requester
|
from synapse.types import RoomAlias, UserID, create_requester
|
||||||
|
|
||||||
|
@ -52,6 +53,8 @@ class RegistrationHandler(BaseHandler):
|
||||||
self.macaroon_gen = hs.get_macaroon_generator()
|
self.macaroon_gen = hs.get_macaroon_generator()
|
||||||
self._server_notices_mxid = hs.config.server_notices_mxid
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
||||||
|
|
||||||
|
self.spam_checker = hs.get_spam_checker()
|
||||||
|
|
||||||
if hs.config.worker_app:
|
if hs.config.worker_app:
|
||||||
self._register_client = ReplicationRegisterServlet.make_client(hs)
|
self._register_client = ReplicationRegisterServlet.make_client(hs)
|
||||||
self._register_device_client = RegisterDeviceReplicationServlet.make_client(
|
self._register_device_client = RegisterDeviceReplicationServlet.make_client(
|
||||||
|
@ -124,7 +127,9 @@ class RegistrationHandler(BaseHandler):
|
||||||
try:
|
try:
|
||||||
int(localpart)
|
int(localpart)
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400, "Numeric user IDs are reserved for guest users."
|
400,
|
||||||
|
"Numeric user IDs are reserved for guest users.",
|
||||||
|
errcode=Codes.INVALID_USERNAME,
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
@ -142,7 +147,7 @@ class RegistrationHandler(BaseHandler):
|
||||||
address=None,
|
address=None,
|
||||||
bind_emails=[],
|
bind_emails=[],
|
||||||
by_admin=False,
|
by_admin=False,
|
||||||
shadow_banned=False,
|
user_agent_ips=None,
|
||||||
):
|
):
|
||||||
"""Registers a new client on the server.
|
"""Registers a new client on the server.
|
||||||
|
|
||||||
|
@ -160,7 +165,8 @@ class RegistrationHandler(BaseHandler):
|
||||||
bind_emails (List[str]): list of emails to bind to this account.
|
bind_emails (List[str]): list of emails to bind to this account.
|
||||||
by_admin (bool): True if this registration is being made via the
|
by_admin (bool): True if this registration is being made via the
|
||||||
admin api, otherwise False.
|
admin api, otherwise False.
|
||||||
shadow_banned (bool): Shadow-ban the created user.
|
user_agent_ips (List[(str, str)]): Tuples of IP addresses and user-agents used
|
||||||
|
during the registration process.
|
||||||
Returns:
|
Returns:
|
||||||
str: user_id
|
str: user_id
|
||||||
Raises:
|
Raises:
|
||||||
|
@ -168,6 +174,24 @@ class RegistrationHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
self.check_registration_ratelimit(address)
|
self.check_registration_ratelimit(address)
|
||||||
|
|
||||||
|
result = self.spam_checker.check_registration_for_spam(
|
||||||
|
threepid, localpart, user_agent_ips or [],
|
||||||
|
)
|
||||||
|
|
||||||
|
if result == RegistrationBehaviour.DENY:
|
||||||
|
logger.info(
|
||||||
|
"Blocked registration of %r", localpart,
|
||||||
|
)
|
||||||
|
# We return a 429 to make it not obvious that they've been
|
||||||
|
# denied.
|
||||||
|
raise SynapseError(429, "Rate limited")
|
||||||
|
|
||||||
|
shadow_banned = result == RegistrationBehaviour.SHADOW_BAN
|
||||||
|
if shadow_banned:
|
||||||
|
logger.info(
|
||||||
|
"Shadow banning registration of %r", localpart,
|
||||||
|
)
|
||||||
|
|
||||||
# do not check_auth_blocking if the call is coming through the Admin API
|
# do not check_auth_blocking if the call is coming through the Admin API
|
||||||
if not by_admin:
|
if not by_admin:
|
||||||
await self.auth.check_auth_blocking(threepid=threepid)
|
await self.auth.check_auth_blocking(threepid=threepid)
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
import random
|
||||||
import string
|
import string
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import TYPE_CHECKING, Any, Awaitable, Dict, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Any, Awaitable, Dict, List, Optional, Tuple
|
||||||
|
@ -50,7 +51,7 @@ from synapse.types import (
|
||||||
create_requester,
|
create_requester,
|
||||||
)
|
)
|
||||||
from synapse.util import stringutils
|
from synapse.util import stringutils
|
||||||
from synapse.util.async_helpers import Linearizer, maybe_awaitable
|
from synapse.util.async_helpers import Linearizer
|
||||||
from synapse.util.caches.response_cache import ResponseCache
|
from synapse.util.caches.response_cache import ResponseCache
|
||||||
from synapse.visibility import filter_events_for_client
|
from synapse.visibility import filter_events_for_client
|
||||||
|
|
||||||
|
@ -135,6 +136,9 @@ class RoomCreationHandler(BaseHandler):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
the new room id
|
the new room id
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester is shadow-banned.
|
||||||
"""
|
"""
|
||||||
await self.ratelimit(requester)
|
await self.ratelimit(requester)
|
||||||
|
|
||||||
|
@ -170,6 +174,15 @@ class RoomCreationHandler(BaseHandler):
|
||||||
async def _upgrade_room(
|
async def _upgrade_room(
|
||||||
self, requester: Requester, old_room_id: str, new_version: RoomVersion
|
self, requester: Requester, old_room_id: str, new_version: RoomVersion
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
requester: the user requesting the upgrade
|
||||||
|
old_room_id: the id of the room to be replaced
|
||||||
|
new_versions: the version to upgrade the room to
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester is shadow-banned.
|
||||||
|
"""
|
||||||
user_id = requester.user.to_string()
|
user_id = requester.user.to_string()
|
||||||
|
|
||||||
# start by allocating a new room id
|
# start by allocating a new room id
|
||||||
|
@ -256,6 +269,9 @@ class RoomCreationHandler(BaseHandler):
|
||||||
old_room_id: the id of the room to be replaced
|
old_room_id: the id of the room to be replaced
|
||||||
new_room_id: the id of the replacement room
|
new_room_id: the id of the replacement room
|
||||||
old_room_state: the state map for the old room
|
old_room_state: the state map for the old room
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester is shadow-banned.
|
||||||
"""
|
"""
|
||||||
old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, ""))
|
old_room_pl_event_id = old_room_state.get((EventTypes.PowerLevels, ""))
|
||||||
|
|
||||||
|
@ -626,6 +642,7 @@ class RoomCreationHandler(BaseHandler):
|
||||||
if mapping:
|
if mapping:
|
||||||
raise SynapseError(400, "Room alias already taken", Codes.ROOM_IN_USE)
|
raise SynapseError(400, "Room alias already taken", Codes.ROOM_IN_USE)
|
||||||
|
|
||||||
|
invite_3pid_list = config.get("invite_3pid", [])
|
||||||
invite_list = config.get("invite", [])
|
invite_list = config.get("invite", [])
|
||||||
for i in invite_list:
|
for i in invite_list:
|
||||||
try:
|
try:
|
||||||
|
@ -634,6 +651,14 @@ class RoomCreationHandler(BaseHandler):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
raise SynapseError(400, "Invalid user_id: %s" % (i,))
|
||||||
|
|
||||||
|
if (invite_list or invite_3pid_list) and requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
|
||||||
|
# Allow the request to go through, but remove any associated invites.
|
||||||
|
invite_3pid_list = []
|
||||||
|
invite_list = []
|
||||||
|
|
||||||
await self.event_creation_handler.assert_accepted_privacy_policy(requester)
|
await self.event_creation_handler.assert_accepted_privacy_policy(requester)
|
||||||
|
|
||||||
power_level_content_override = config.get("power_level_content_override")
|
power_level_content_override = config.get("power_level_content_override")
|
||||||
|
@ -648,8 +673,6 @@ class RoomCreationHandler(BaseHandler):
|
||||||
% (user_id,),
|
% (user_id,),
|
||||||
)
|
)
|
||||||
|
|
||||||
invite_3pid_list = config.get("invite_3pid", [])
|
|
||||||
|
|
||||||
visibility = config.get("visibility", None)
|
visibility = config.get("visibility", None)
|
||||||
is_public = visibility == "public"
|
is_public = visibility == "public"
|
||||||
|
|
||||||
|
@ -744,6 +767,8 @@ class RoomCreationHandler(BaseHandler):
|
||||||
if is_direct:
|
if is_direct:
|
||||||
content["is_direct"] = is_direct
|
content["is_direct"] = is_direct
|
||||||
|
|
||||||
|
# Note that update_membership with an action of "invite" can raise a
|
||||||
|
# ShadowBanError, but this was handled above by emptying invite_list.
|
||||||
_, last_stream_id = await self.room_member_handler.update_membership(
|
_, last_stream_id = await self.room_member_handler.update_membership(
|
||||||
requester,
|
requester,
|
||||||
UserID.from_string(invitee),
|
UserID.from_string(invitee),
|
||||||
|
@ -758,6 +783,8 @@ class RoomCreationHandler(BaseHandler):
|
||||||
id_access_token = invite_3pid.get("id_access_token") # optional
|
id_access_token = invite_3pid.get("id_access_token") # optional
|
||||||
address = invite_3pid["address"]
|
address = invite_3pid["address"]
|
||||||
medium = invite_3pid["medium"]
|
medium = invite_3pid["medium"]
|
||||||
|
# Note that do_3pid_invite can raise a ShadowBanError, but this was
|
||||||
|
# handled above by emptying invite_3pid_list.
|
||||||
last_stream_id = await self.hs.get_room_member_handler().do_3pid_invite(
|
last_stream_id = await self.hs.get_room_member_handler().do_3pid_invite(
|
||||||
room_id,
|
room_id,
|
||||||
requester.user,
|
requester.user,
|
||||||
|
@ -817,11 +844,13 @@ class RoomCreationHandler(BaseHandler):
|
||||||
async def send(etype: str, content: JsonDict, **kwargs) -> int:
|
async def send(etype: str, content: JsonDict, **kwargs) -> int:
|
||||||
event = create(etype, content, **kwargs)
|
event = create(etype, content, **kwargs)
|
||||||
logger.debug("Sending %s in new room", etype)
|
logger.debug("Sending %s in new room", etype)
|
||||||
|
# Allow these events to be sent even if the user is shadow-banned to
|
||||||
|
# allow the room creation to complete.
|
||||||
(
|
(
|
||||||
_,
|
_,
|
||||||
last_stream_id,
|
last_stream_id,
|
||||||
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
) = await self.event_creation_handler.create_and_send_nonmember_event(
|
||||||
creator, event, ratelimit=False
|
creator, event, ratelimit=False, ignore_shadow_ban=True,
|
||||||
)
|
)
|
||||||
return last_stream_id
|
return last_stream_id
|
||||||
|
|
||||||
|
@ -1300,9 +1329,7 @@ class RoomShutdownHandler(object):
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
aliases_for_room = await maybe_awaitable(
|
aliases_for_room = await self.store.get_aliases_for_room(room_id)
|
||||||
self.store.get_aliases_for_room(room_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.store.update_aliases_for_room(
|
await self.store.update_aliases_for_room(
|
||||||
room_id, new_room_id, requester_user_id
|
room_id, new_room_id, requester_user_id
|
||||||
|
|
|
@ -15,14 +15,21 @@
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Tuple, Union
|
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from unpaddedbase64 import encode_base64
|
from unpaddedbase64 import encode_base64
|
||||||
|
|
||||||
from synapse import types
|
from synapse import types
|
||||||
from synapse.api.constants import MAX_DEPTH, EventTypes, Membership
|
from synapse.api.constants import MAX_DEPTH, EventTypes, Membership
|
||||||
from synapse.api.errors import AuthError, Codes, LimitExceededError, SynapseError
|
from synapse.api.errors import (
|
||||||
|
AuthError,
|
||||||
|
Codes,
|
||||||
|
LimitExceededError,
|
||||||
|
ShadowBanError,
|
||||||
|
SynapseError,
|
||||||
|
)
|
||||||
from synapse.api.ratelimiting import Ratelimiter
|
from synapse.api.ratelimiting import Ratelimiter
|
||||||
from synapse.api.room_versions import EventFormatVersions
|
from synapse.api.room_versions import EventFormatVersions
|
||||||
from synapse.crypto.event_signing import compute_event_reference_hash
|
from synapse.crypto.event_signing import compute_event_reference_hash
|
||||||
|
@ -31,7 +38,15 @@ from synapse.events.builder import create_local_event_from_event_dict
|
||||||
from synapse.events.snapshot import EventContext
|
from synapse.events.snapshot import EventContext
|
||||||
from synapse.events.validator import EventValidator
|
from synapse.events.validator import EventValidator
|
||||||
from synapse.storage.roommember import RoomsForUser
|
from synapse.storage.roommember import RoomsForUser
|
||||||
from synapse.types import Collection, JsonDict, Requester, RoomAlias, RoomID, UserID
|
from synapse.types import (
|
||||||
|
Collection,
|
||||||
|
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_joined_room, user_left_room
|
from synapse.util.distributor import user_joined_room, user_left_room
|
||||||
|
|
||||||
|
@ -211,24 +226,40 @@ class RoomMemberHandler(object):
|
||||||
_, stream_id = await self.store.get_event_ordering(duplicate.event_id)
|
_, stream_id = await self.store.get_event_ordering(duplicate.event_id)
|
||||||
return duplicate.event_id, stream_id
|
return duplicate.event_id, stream_id
|
||||||
|
|
||||||
stream_id = await self.event_creation_handler.handle_new_client_event(
|
|
||||||
requester, event, context, extra_users=[target], ratelimit=ratelimit,
|
|
||||||
)
|
|
||||||
|
|
||||||
prev_state_ids = await context.get_prev_state_ids()
|
prev_state_ids = await context.get_prev_state_ids()
|
||||||
|
|
||||||
prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
|
prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
|
||||||
|
|
||||||
|
newly_joined = False
|
||||||
if event.membership == Membership.JOIN:
|
if event.membership == Membership.JOIN:
|
||||||
# Only fire user_joined_room if the user has actually joined the
|
|
||||||
# room. Don't bother if the user is just changing their profile
|
|
||||||
# info.
|
|
||||||
newly_joined = True
|
newly_joined = True
|
||||||
if prev_member_event_id:
|
if prev_member_event_id:
|
||||||
prev_member_event = await self.store.get_event(prev_member_event_id)
|
prev_member_event = await self.store.get_event(prev_member_event_id)
|
||||||
newly_joined = prev_member_event.membership != Membership.JOIN
|
newly_joined = prev_member_event.membership != Membership.JOIN
|
||||||
|
|
||||||
|
# Only rate-limit if the user actually joined the room, otherwise we'll end
|
||||||
|
# up blocking profile updates.
|
||||||
if newly_joined:
|
if newly_joined:
|
||||||
await self._user_joined_room(target, room_id)
|
time_now_s = self.clock.time()
|
||||||
|
(
|
||||||
|
allowed,
|
||||||
|
time_allowed,
|
||||||
|
) = self._join_rate_limiter_local.can_requester_do_action(requester)
|
||||||
|
|
||||||
|
if not allowed:
|
||||||
|
raise LimitExceededError(
|
||||||
|
retry_after_ms=int(1000 * (time_allowed - time_now_s))
|
||||||
|
)
|
||||||
|
|
||||||
|
stream_id = await self.event_creation_handler.handle_new_client_event(
|
||||||
|
requester, event, context, extra_users=[target], ratelimit=ratelimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
if event.membership == Membership.JOIN and newly_joined:
|
||||||
|
# Only fire user_joined_room if the user has actually joined the
|
||||||
|
# room. Don't bother if the user is just changing their profile
|
||||||
|
# info.
|
||||||
|
await self._user_joined_room(target, room_id)
|
||||||
elif event.membership == Membership.LEAVE:
|
elif event.membership == Membership.LEAVE:
|
||||||
if prev_member_event_id:
|
if prev_member_event_id:
|
||||||
prev_member_event = await self.store.get_event(prev_member_event_id)
|
prev_member_event = await self.store.get_event(prev_member_event_id)
|
||||||
|
@ -286,6 +317,31 @@ class RoomMemberHandler(object):
|
||||||
content: Optional[dict] = None,
|
content: Optional[dict] = None,
|
||||||
require_consent: bool = True,
|
require_consent: bool = True,
|
||||||
) -> Tuple[str, int]:
|
) -> Tuple[str, int]:
|
||||||
|
"""Update a user's membership in a room.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
requester: The user who is performing the update.
|
||||||
|
target: The user whose membership is being updated.
|
||||||
|
room_id: The room ID whose membership is being updated.
|
||||||
|
action: The membership change, see synapse.api.constants.Membership.
|
||||||
|
txn_id: The transaction ID, if given.
|
||||||
|
remote_room_hosts: Remote servers to send the update to.
|
||||||
|
third_party_signed: Information from a 3PID invite.
|
||||||
|
ratelimit: Whether to rate limit the request.
|
||||||
|
content: The content of the created event.
|
||||||
|
require_consent: Whether consent is required.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A tuple of the new event ID and stream ID.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if a shadow-banned requester attempts to send an invite.
|
||||||
|
"""
|
||||||
|
if action == Membership.INVITE and requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
key = (room_id,)
|
key = (room_id,)
|
||||||
|
|
||||||
as_id = object()
|
as_id = object()
|
||||||
|
@ -344,7 +400,7 @@ class RoomMemberHandler(object):
|
||||||
# later on.
|
# later on.
|
||||||
content = dict(content)
|
content = dict(content)
|
||||||
|
|
||||||
if not self.allow_per_room_profiles:
|
if not self.allow_per_room_profiles 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.
|
||||||
|
@ -477,22 +533,12 @@ class RoomMemberHandler(object):
|
||||||
# so don't really fit into the general auth process.
|
# so don't really fit into the general auth process.
|
||||||
raise AuthError(403, "Guest access not allowed")
|
raise AuthError(403, "Guest access not allowed")
|
||||||
|
|
||||||
if is_host_in_room:
|
if not is_host_in_room:
|
||||||
time_now_s = self.clock.time()
|
time_now_s = self.clock.time()
|
||||||
allowed, time_allowed = self._join_rate_limiter_local.can_do_action(
|
(
|
||||||
requester.user.to_string(),
|
allowed,
|
||||||
)
|
time_allowed,
|
||||||
|
) = self._join_rate_limiter_remote.can_requester_do_action(requester,)
|
||||||
if not allowed:
|
|
||||||
raise LimitExceededError(
|
|
||||||
retry_after_ms=int(1000 * (time_allowed - time_now_s))
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
time_now_s = self.clock.time()
|
|
||||||
allowed, time_allowed = self._join_rate_limiter_remote.can_do_action(
|
|
||||||
requester.user.to_string(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if not allowed:
|
if not allowed:
|
||||||
raise LimitExceededError(
|
raise LimitExceededError(
|
||||||
|
@ -724,9 +770,7 @@ class RoomMemberHandler(object):
|
||||||
if prev_member_event.membership == Membership.JOIN:
|
if prev_member_event.membership == Membership.JOIN:
|
||||||
await self._user_left_room(target_user, room_id)
|
await self._user_left_room(target_user, room_id)
|
||||||
|
|
||||||
async def _can_guest_join(
|
async def _can_guest_join(self, current_state_ids: StateMap[str]) -> bool:
|
||||||
self, current_state_ids: Dict[Tuple[str, str], str]
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Returns whether a guest can join a room based on its current state.
|
Returns whether a guest can join a room based on its current state.
|
||||||
"""
|
"""
|
||||||
|
@ -736,7 +780,7 @@ class RoomMemberHandler(object):
|
||||||
|
|
||||||
guest_access = await self.store.get_event(guest_access_id)
|
guest_access = await self.store.get_event(guest_access_id)
|
||||||
|
|
||||||
return (
|
return bool(
|
||||||
guest_access
|
guest_access
|
||||||
and guest_access.content
|
and guest_access.content
|
||||||
and "guest_access" in guest_access.content
|
and "guest_access" in guest_access.content
|
||||||
|
@ -793,6 +837,25 @@ class RoomMemberHandler(object):
|
||||||
txn_id: Optional[str],
|
txn_id: Optional[str],
|
||||||
id_access_token: Optional[str] = None,
|
id_access_token: Optional[str] = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
|
"""Invite a 3PID to a room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id: The room to invite the 3PID to.
|
||||||
|
inviter: The user sending the invite.
|
||||||
|
medium: The 3PID's medium.
|
||||||
|
address: The 3PID's address.
|
||||||
|
id_server: The identity server to use.
|
||||||
|
requester: The user making the request.
|
||||||
|
txn_id: The transaction ID this is part of, or None if this is not
|
||||||
|
part of a transaction.
|
||||||
|
id_access_token: The optional identity server access token.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The new stream ID.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ShadowBanError if the requester has been shadow-banned.
|
||||||
|
"""
|
||||||
if self.config.block_non_admin_invites:
|
if self.config.block_non_admin_invites:
|
||||||
is_requester_admin = await self.auth.is_server_admin(requester.user)
|
is_requester_admin = await self.auth.is_server_admin(requester.user)
|
||||||
if not is_requester_admin:
|
if not is_requester_admin:
|
||||||
|
@ -800,6 +863,11 @@ class RoomMemberHandler(object):
|
||||||
403, "Invites have been disabled on this server", Codes.FORBIDDEN
|
403, "Invites have been disabled on this server", Codes.FORBIDDEN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
# We need to rate limit *before* we send out any 3PID invites, so we
|
# We need to rate limit *before* we send out any 3PID invites, so we
|
||||||
# can't just rely on the standard ratelimiting of events.
|
# can't just rely on the standard ratelimiting of events.
|
||||||
await self.base_handler.ratelimit(requester)
|
await self.base_handler.ratelimit(requester)
|
||||||
|
@ -824,6 +892,8 @@ class RoomMemberHandler(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
if invitee:
|
if invitee:
|
||||||
|
# Note that update_membership with an action of "invite" can raise
|
||||||
|
# a ShadowBanError, but this was done above already.
|
||||||
_, stream_id = await self.update_membership(
|
_, stream_id = await self.update_membership(
|
||||||
requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id
|
requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id
|
||||||
)
|
)
|
||||||
|
@ -929,9 +999,7 @@ class RoomMemberHandler(object):
|
||||||
)
|
)
|
||||||
return stream_id
|
return stream_id
|
||||||
|
|
||||||
async def _is_host_in_room(
|
async def _is_host_in_room(self, current_state_ids: StateMap[str]) -> bool:
|
||||||
self, current_state_ids: Dict[Tuple[str, str], str]
|
|
||||||
) -> bool:
|
|
||||||
# Have we just created the room, and is this about to be the very
|
# Have we just created the room, and is this about to be the very
|
||||||
# first member event?
|
# first member event?
|
||||||
create_event_id = current_state_ids.get(("m.room.create", ""))
|
create_event_id = current_state_ids.get(("m.room.create", ""))
|
||||||
|
@ -1062,7 +1130,7 @@ class RoomMemberMasterHandler(RoomMemberHandler):
|
||||||
return event_id, stream_id
|
return event_id, stream_id
|
||||||
|
|
||||||
# The room is too large. Leave.
|
# The room is too large. Leave.
|
||||||
requester = types.create_requester(user, None, False, None)
|
requester = types.create_requester(user, None, False, False, None)
|
||||||
await self.update_membership(
|
await self.update_membership(
|
||||||
requester=requester, target=user, room_id=room_id, action="leave"
|
requester=requester, target=user, room_id=room_id, action="leave"
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,6 +54,7 @@ class Saml2SessionData:
|
||||||
|
|
||||||
class SamlHandler:
|
class SamlHandler:
|
||||||
def __init__(self, hs: "synapse.server.HomeServer"):
|
def __init__(self, hs: "synapse.server.HomeServer"):
|
||||||
|
self.hs = hs
|
||||||
self._saml_client = Saml2Client(hs.config.saml2_sp_config)
|
self._saml_client = Saml2Client(hs.config.saml2_sp_config)
|
||||||
self._auth = hs.get_auth()
|
self._auth = hs.get_auth()
|
||||||
self._auth_handler = hs.get_auth_handler()
|
self._auth_handler = hs.get_auth_handler()
|
||||||
|
@ -133,8 +134,14 @@ class SamlHandler:
|
||||||
# the dict.
|
# the dict.
|
||||||
self.expire_sessions()
|
self.expire_sessions()
|
||||||
|
|
||||||
|
# Pull out the user-agent and IP from the request.
|
||||||
|
user_agent = request.requestHeaders.getRawHeaders(b"User-Agent", default=[b""])[
|
||||||
|
0
|
||||||
|
].decode("ascii", "surrogateescape")
|
||||||
|
ip_address = self.hs.get_ip_from_request(request)
|
||||||
|
|
||||||
user_id, current_session = await self._map_saml_response_to_user(
|
user_id, current_session = await self._map_saml_response_to_user(
|
||||||
resp_bytes, relay_state
|
resp_bytes, relay_state, user_agent, ip_address
|
||||||
)
|
)
|
||||||
|
|
||||||
# Complete the interactive auth session or the login.
|
# Complete the interactive auth session or the login.
|
||||||
|
@ -147,7 +154,11 @@ class SamlHandler:
|
||||||
await self._auth_handler.complete_sso_login(user_id, request, relay_state)
|
await self._auth_handler.complete_sso_login(user_id, request, relay_state)
|
||||||
|
|
||||||
async def _map_saml_response_to_user(
|
async def _map_saml_response_to_user(
|
||||||
self, resp_bytes: str, client_redirect_url: str
|
self,
|
||||||
|
resp_bytes: str,
|
||||||
|
client_redirect_url: str,
|
||||||
|
user_agent: str,
|
||||||
|
ip_address: str,
|
||||||
) -> Tuple[str, Optional[Saml2SessionData]]:
|
) -> Tuple[str, Optional[Saml2SessionData]]:
|
||||||
"""
|
"""
|
||||||
Given a sample response, retrieve the cached session and user for it.
|
Given a sample response, retrieve the cached session and user for it.
|
||||||
|
@ -155,6 +166,8 @@ class SamlHandler:
|
||||||
Args:
|
Args:
|
||||||
resp_bytes: The SAML response.
|
resp_bytes: The SAML response.
|
||||||
client_redirect_url: The redirect URL passed in by the client.
|
client_redirect_url: The redirect URL passed in by the client.
|
||||||
|
user_agent: The user agent of the client making the request.
|
||||||
|
ip_address: The IP address of the client making the request.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of the user ID and SAML session associated with this response.
|
Tuple of the user ID and SAML session associated with this response.
|
||||||
|
@ -291,6 +304,7 @@ class SamlHandler:
|
||||||
localpart=localpart,
|
localpart=localpart,
|
||||||
default_display_name=displayname,
|
default_display_name=displayname,
|
||||||
bind_emails=emails,
|
bind_emails=emails,
|
||||||
|
user_agent_ips=(user_agent, ip_address),
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._datastore.record_user_external_id(
|
await self._datastore.record_user_external_id(
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import TYPE_CHECKING, List, Set, Tuple
|
from typing import TYPE_CHECKING, List, Set, Tuple
|
||||||
|
|
||||||
from synapse.api.errors import AuthError, SynapseError
|
from synapse.api.errors import AuthError, ShadowBanError, SynapseError
|
||||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
from synapse.replication.tcp.streams import TypingStream
|
from synapse.replication.tcp.streams import TypingStream
|
||||||
from synapse.types import UserID, get_domain_from_id
|
from synapse.types import UserID, get_domain_from_id
|
||||||
|
@ -227,9 +228,9 @@ class TypingWriterHandler(FollowerTypingHandler):
|
||||||
self._stopped_typing(member)
|
self._stopped_typing(member)
|
||||||
return
|
return
|
||||||
|
|
||||||
async def started_typing(self, target_user, auth_user, room_id, timeout):
|
async def started_typing(self, target_user, requester, room_id, timeout):
|
||||||
target_user_id = target_user.to_string()
|
target_user_id = target_user.to_string()
|
||||||
auth_user_id = auth_user.to_string()
|
auth_user_id = requester.user.to_string()
|
||||||
|
|
||||||
if not self.is_mine_id(target_user_id):
|
if not self.is_mine_id(target_user_id):
|
||||||
raise SynapseError(400, "User is not hosted on this homeserver")
|
raise SynapseError(400, "User is not hosted on this homeserver")
|
||||||
|
@ -237,6 +238,11 @@ class TypingWriterHandler(FollowerTypingHandler):
|
||||||
if target_user_id != auth_user_id:
|
if target_user_id != auth_user_id:
|
||||||
raise AuthError(400, "Cannot set another user's typing state")
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
if requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
await self.auth.check_user_in_room(room_id, target_user_id)
|
await self.auth.check_user_in_room(room_id, target_user_id)
|
||||||
|
|
||||||
logger.debug("%s has started typing in %s", target_user_id, room_id)
|
logger.debug("%s has started typing in %s", target_user_id, room_id)
|
||||||
|
@ -256,9 +262,9 @@ class TypingWriterHandler(FollowerTypingHandler):
|
||||||
|
|
||||||
self._push_update(member=member, typing=True)
|
self._push_update(member=member, typing=True)
|
||||||
|
|
||||||
async def stopped_typing(self, target_user, auth_user, room_id):
|
async def stopped_typing(self, target_user, requester, room_id):
|
||||||
target_user_id = target_user.to_string()
|
target_user_id = target_user.to_string()
|
||||||
auth_user_id = auth_user.to_string()
|
auth_user_id = requester.user.to_string()
|
||||||
|
|
||||||
if not self.is_mine_id(target_user_id):
|
if not self.is_mine_id(target_user_id):
|
||||||
raise SynapseError(400, "User is not hosted on this homeserver")
|
raise SynapseError(400, "User is not hosted on this homeserver")
|
||||||
|
@ -266,6 +272,11 @@ class TypingWriterHandler(FollowerTypingHandler):
|
||||||
if target_user_id != auth_user_id:
|
if target_user_id != auth_user_id:
|
||||||
raise AuthError(400, "Cannot set another user's typing state")
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
if requester.shadow_banned:
|
||||||
|
# We randomly sleep a bit just to annoy the requester.
|
||||||
|
await self.clock.sleep(random.randint(1, 10))
|
||||||
|
raise ShadowBanError()
|
||||||
|
|
||||||
await self.auth.check_user_in_room(room_id, target_user_id)
|
await self.auth.check_user_in_room(room_id, target_user_id)
|
||||||
|
|
||||||
logger.debug("%s has stopped typing in %s", target_user_id, room_id)
|
logger.debug("%s has stopped typing in %s", target_user_id, room_id)
|
||||||
|
|
|
@ -16,13 +16,12 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from twisted.web.client import PartialDownloadError
|
from twisted.web.client import PartialDownloadError
|
||||||
|
|
||||||
from synapse.api.constants import LoginType
|
from synapse.api.constants import LoginType
|
||||||
from synapse.api.errors import Codes, LoginError, SynapseError
|
from synapse.api.errors import Codes, LoginError, SynapseError
|
||||||
from synapse.config.emailconfig import ThreepidBehaviour
|
from synapse.config.emailconfig import ThreepidBehaviour
|
||||||
|
from synapse.util import json_decoder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -117,7 +116,7 @@ class RecaptchaAuthChecker(UserInteractiveAuthChecker):
|
||||||
except PartialDownloadError as pde:
|
except PartialDownloadError as pde:
|
||||||
# Twisted is silly
|
# Twisted is silly
|
||||||
data = pde.response
|
data = pde.response
|
||||||
resp_body = json.loads(data.decode("utf-8"))
|
resp_body = json_decoder.decode(data.decode("utf-8"))
|
||||||
|
|
||||||
if "success" in resp_body:
|
if "success" in resp_body:
|
||||||
# Note that we do NOT check the hostname here: we explicitly
|
# Note that we do NOT check the hostname here: we explicitly
|
||||||
|
|
|
@ -19,7 +19,7 @@ import urllib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import treq
|
import treq
|
||||||
from canonicaljson import encode_canonical_json, json
|
from canonicaljson import encode_canonical_json
|
||||||
from netaddr import IPAddress
|
from netaddr import IPAddress
|
||||||
from prometheus_client import Counter
|
from prometheus_client import Counter
|
||||||
from zope.interface import implementer, provider
|
from zope.interface import implementer, provider
|
||||||
|
@ -47,6 +47,7 @@ from synapse.http import (
|
||||||
from synapse.http.proxyagent import ProxyAgent
|
from synapse.http.proxyagent import ProxyAgent
|
||||||
from synapse.logging.context import make_deferred_yieldable
|
from synapse.logging.context import make_deferred_yieldable
|
||||||
from synapse.logging.opentracing import set_tag, start_active_span, tags
|
from synapse.logging.opentracing import set_tag, start_active_span, tags
|
||||||
|
from synapse.util import json_decoder
|
||||||
from synapse.util.async_helpers import timeout_deferred
|
from synapse.util.async_helpers import timeout_deferred
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -391,7 +392,7 @@ class SimpleHttpClient(object):
|
||||||
body = await make_deferred_yieldable(readBody(response))
|
body = await make_deferred_yieldable(readBody(response))
|
||||||
|
|
||||||
if 200 <= response.code < 300:
|
if 200 <= response.code < 300:
|
||||||
return json.loads(body.decode("utf-8"))
|
return json_decoder.decode(body.decode("utf-8"))
|
||||||
else:
|
else:
|
||||||
raise HttpResponseException(
|
raise HttpResponseException(
|
||||||
response.code, response.phrase.decode("ascii", errors="replace"), body
|
response.code, response.phrase.decode("ascii", errors="replace"), body
|
||||||
|
@ -433,7 +434,7 @@ class SimpleHttpClient(object):
|
||||||
body = await make_deferred_yieldable(readBody(response))
|
body = await make_deferred_yieldable(readBody(response))
|
||||||
|
|
||||||
if 200 <= response.code < 300:
|
if 200 <= response.code < 300:
|
||||||
return json.loads(body.decode("utf-8"))
|
return json_decoder.decode(body.decode("utf-8"))
|
||||||
else:
|
else:
|
||||||
raise HttpResponseException(
|
raise HttpResponseException(
|
||||||
response.code, response.phrase.decode("ascii", errors="replace"), body
|
response.code, response.phrase.decode("ascii", errors="replace"), body
|
||||||
|
@ -463,7 +464,7 @@ class SimpleHttpClient(object):
|
||||||
actual_headers.update(headers)
|
actual_headers.update(headers)
|
||||||
|
|
||||||
body = await self.get_raw(uri, args, headers=headers)
|
body = await self.get_raw(uri, args, headers=headers)
|
||||||
return json.loads(body.decode("utf-8"))
|
return json_decoder.decode(body.decode("utf-8"))
|
||||||
|
|
||||||
async def put_json(self, uri, json_body, args={}, headers=None):
|
async def put_json(self, uri, json_body, args={}, headers=None):
|
||||||
""" Puts some json to the given URI.
|
""" Puts some json to the given URI.
|
||||||
|
@ -506,7 +507,7 @@ class SimpleHttpClient(object):
|
||||||
body = await make_deferred_yieldable(readBody(response))
|
body = await make_deferred_yieldable(readBody(response))
|
||||||
|
|
||||||
if 200 <= response.code < 300:
|
if 200 <= response.code < 300:
|
||||||
return json.loads(body.decode("utf-8"))
|
return json_decoder.decode(body.decode("utf-8"))
|
||||||
else:
|
else:
|
||||||
raise HttpResponseException(
|
raise HttpResponseException(
|
||||||
response.code, response.phrase.decode("ascii", errors="replace"), body
|
response.code, response.phrase.decode("ascii", errors="replace"), body
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
# 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 json
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
@ -26,7 +25,7 @@ from twisted.web.http import stringToDatetime
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
|
|
||||||
from synapse.logging.context import make_deferred_yieldable
|
from synapse.logging.context import make_deferred_yieldable
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock, json_decoder
|
||||||
from synapse.util.caches.ttlcache import TTLCache
|
from synapse.util.caches.ttlcache import TTLCache
|
||||||
from synapse.util.metrics import Measure
|
from synapse.util.metrics import Measure
|
||||||
|
|
||||||
|
@ -185,7 +184,7 @@ class WellKnownResolver(object):
|
||||||
if response.code != 200:
|
if response.code != 200:
|
||||||
raise Exception("Non-200 response %s" % (response.code,))
|
raise Exception("Non-200 response %s" % (response.code,))
|
||||||
|
|
||||||
parsed_body = json.loads(body.decode("utf-8"))
|
parsed_body = json_decoder.decode(body.decode("utf-8"))
|
||||||
logger.info("Response from .well-known: %s", parsed_body)
|
logger.info("Response from .well-known: %s", parsed_body)
|
||||||
|
|
||||||
result = parsed_body["m.server"].encode("ascii")
|
result = parsed_body["m.server"].encode("ascii")
|
||||||
|
|
|
@ -500,7 +500,7 @@ class RootOptionsRedirectResource(OptionsResource, RootRedirect):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@implementer(interfaces.IPullProducer)
|
@implementer(interfaces.IPushProducer)
|
||||||
class _ByteProducer:
|
class _ByteProducer:
|
||||||
"""
|
"""
|
||||||
Iteratively write bytes to the request.
|
Iteratively write bytes to the request.
|
||||||
|
@ -515,52 +515,64 @@ class _ByteProducer:
|
||||||
):
|
):
|
||||||
self._request = request
|
self._request = request
|
||||||
self._iterator = iterator
|
self._iterator = iterator
|
||||||
|
self._paused = False
|
||||||
|
|
||||||
def start(self) -> None:
|
# Register the producer and start producing data.
|
||||||
self._request.registerProducer(self, False)
|
self._request.registerProducer(self, True)
|
||||||
|
self.resumeProducing()
|
||||||
|
|
||||||
def _send_data(self, data: List[bytes]) -> None:
|
def _send_data(self, data: List[bytes]) -> None:
|
||||||
"""
|
"""
|
||||||
Send a list of strings as a response to the request.
|
Send a list of bytes as a chunk of a response.
|
||||||
"""
|
"""
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
self._request.write(b"".join(data))
|
self._request.write(b"".join(data))
|
||||||
|
|
||||||
|
def pauseProducing(self) -> None:
|
||||||
|
self._paused = True
|
||||||
|
|
||||||
def resumeProducing(self) -> None:
|
def resumeProducing(self) -> None:
|
||||||
# We've stopped producing in the meantime (note that this might be
|
# We've stopped producing in the meantime (note that this might be
|
||||||
# re-entrant after calling write).
|
# re-entrant after calling write).
|
||||||
if not self._request:
|
if not self._request:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the next chunk and write it to the request.
|
self._paused = False
|
||||||
#
|
|
||||||
# The output of the JSON encoder is coalesced until min_chunk_size is
|
|
||||||
# reached. (This is because JSON encoders produce a very small output
|
|
||||||
# per iteration.)
|
|
||||||
#
|
|
||||||
# Note that buffer stores a list of bytes (instead of appending to
|
|
||||||
# bytes) to hopefully avoid many allocations.
|
|
||||||
buffer = []
|
|
||||||
buffered_bytes = 0
|
|
||||||
while buffered_bytes < self.min_chunk_size:
|
|
||||||
try:
|
|
||||||
data = next(self._iterator)
|
|
||||||
buffer.append(data)
|
|
||||||
buffered_bytes += len(data)
|
|
||||||
except StopIteration:
|
|
||||||
# The entire JSON object has been serialized, write any
|
|
||||||
# remaining data, finalize the producer and the request, and
|
|
||||||
# clean-up any references.
|
|
||||||
self._send_data(buffer)
|
|
||||||
self._request.unregisterProducer()
|
|
||||||
self._request.finish()
|
|
||||||
self.stopProducing()
|
|
||||||
return
|
|
||||||
|
|
||||||
self._send_data(buffer)
|
# Write until there's backpressure telling us to stop.
|
||||||
|
while not self._paused:
|
||||||
|
# Get the next chunk and write it to the request.
|
||||||
|
#
|
||||||
|
# The output of the JSON encoder is buffered and coalesced until
|
||||||
|
# min_chunk_size is reached. This is because JSON encoders produce
|
||||||
|
# very small output per iteration and the Request object converts
|
||||||
|
# each call to write() to a separate chunk. Without this there would
|
||||||
|
# be an explosion in bytes written (e.g. b"{" becoming "1\r\n{\r\n").
|
||||||
|
#
|
||||||
|
# Note that buffer stores a list of bytes (instead of appending to
|
||||||
|
# bytes) to hopefully avoid many allocations.
|
||||||
|
buffer = []
|
||||||
|
buffered_bytes = 0
|
||||||
|
while buffered_bytes < self.min_chunk_size:
|
||||||
|
try:
|
||||||
|
data = next(self._iterator)
|
||||||
|
buffer.append(data)
|
||||||
|
buffered_bytes += len(data)
|
||||||
|
except StopIteration:
|
||||||
|
# The entire JSON object has been serialized, write any
|
||||||
|
# remaining data, finalize the producer and the request, and
|
||||||
|
# clean-up any references.
|
||||||
|
self._send_data(buffer)
|
||||||
|
self._request.unregisterProducer()
|
||||||
|
self._request.finish()
|
||||||
|
self.stopProducing()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._send_data(buffer)
|
||||||
|
|
||||||
def stopProducing(self) -> None:
|
def stopProducing(self) -> None:
|
||||||
|
# Clear a circular reference.
|
||||||
self._request = None
|
self._request = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -620,8 +632,7 @@ def respond_with_json(
|
||||||
if send_cors:
|
if send_cors:
|
||||||
set_cors_headers(request)
|
set_cors_headers(request)
|
||||||
|
|
||||||
producer = _ByteProducer(request, encoder(json_object))
|
_ByteProducer(request, encoder(json_object))
|
||||||
producer.start()
|
|
||||||
return NOT_DONE_YET
|
return NOT_DONE_YET
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,8 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, SynapseError
|
||||||
|
from synapse.util import json_decoder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -215,7 +214,7 @@ def parse_json_value_from_request(request, allow_empty_body=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = json.loads(content_bytes.decode("utf-8"))
|
content = json_decoder.decode(content_bytes.decode("utf-8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Unable to parse JSON: %s", e)
|
logger.warning("Unable to parse JSON: %s", e)
|
||||||
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
||||||
|
|
|
@ -172,11 +172,11 @@ from functools import wraps
|
||||||
from typing import TYPE_CHECKING, Dict, Optional, Type
|
from typing import TYPE_CHECKING, Dict, Optional, Type
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
from canonicaljson import json
|
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.config import ConfigError
|
from synapse.config import ConfigError
|
||||||
|
from synapse.util import json_decoder, json_encoder
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
|
@ -499,7 +499,9 @@ def start_active_span_from_edu(
|
||||||
if opentracing is None:
|
if opentracing is None:
|
||||||
return _noop_context_manager()
|
return _noop_context_manager()
|
||||||
|
|
||||||
carrier = json.loads(edu_content.get("context", "{}")).get("opentracing", {})
|
carrier = json_decoder.decode(edu_content.get("context", "{}")).get(
|
||||||
|
"opentracing", {}
|
||||||
|
)
|
||||||
context = opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)
|
context = opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)
|
||||||
_references = [
|
_references = [
|
||||||
opentracing.child_of(span_context_from_string(x))
|
opentracing.child_of(span_context_from_string(x))
|
||||||
|
@ -690,7 +692,7 @@ def active_span_context_as_string():
|
||||||
opentracing.tracer.inject(
|
opentracing.tracer.inject(
|
||||||
opentracing.tracer.active_span, opentracing.Format.TEXT_MAP, carrier
|
opentracing.tracer.active_span, opentracing.Format.TEXT_MAP, carrier
|
||||||
)
|
)
|
||||||
return json.dumps(carrier)
|
return json_encoder.encode(carrier)
|
||||||
|
|
||||||
|
|
||||||
@only_if_tracing
|
@only_if_tracing
|
||||||
|
@ -699,7 +701,7 @@ def span_context_from_string(carrier):
|
||||||
Returns:
|
Returns:
|
||||||
The active span context decoded from a string.
|
The active span context decoded from a string.
|
||||||
"""
|
"""
|
||||||
carrier = json.loads(carrier)
|
carrier = json_decoder.decode(carrier)
|
||||||
return opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)
|
return opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ def run_as_background_process(desc: str, func, *args, **kwargs):
|
||||||
It returns a Deferred which completes when the function completes, but it doesn't
|
It returns a Deferred which completes when the function completes, but it doesn't
|
||||||
follow the synapse logcontext rules, which makes it appropriate for passing to
|
follow the synapse logcontext rules, which makes it appropriate for passing to
|
||||||
clock.looping_call and friends (or for firing-and-forgetting in the middle of a
|
clock.looping_call and friends (or for firing-and-forgetting in the middle of a
|
||||||
normal synapse inlineCallbacks function).
|
normal synapse async function).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
desc: a description for this background process type
|
desc: a description for this background process type
|
||||||
|
|
|
@ -167,8 +167,10 @@ class ModuleApi(object):
|
||||||
external_id: id on that system
|
external_id: id on that system
|
||||||
user_id: complete mxid that it is mapped to
|
user_id: complete mxid that it is mapped to
|
||||||
"""
|
"""
|
||||||
return self._store.record_user_external_id(
|
return defer.ensureDeferred(
|
||||||
auth_provider_id, remote_user_id, registered_user_id
|
self._store.record_user_external_id(
|
||||||
|
auth_provider_id, remote_user_id, registered_user_id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_short_term_login_token(
|
def generate_short_term_login_token(
|
||||||
|
@ -223,7 +225,9 @@ class ModuleApi(object):
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[object]: result of func
|
Deferred[object]: result of func
|
||||||
"""
|
"""
|
||||||
return self._store.db_pool.runInteraction(desc, func, *args, **kwargs)
|
return defer.ensureDeferred(
|
||||||
|
self._store.db_pool.runInteraction(desc, func, *args, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
def complete_sso_login(
|
def complete_sso_login(
|
||||||
self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str
|
self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str
|
||||||
|
|
|
@ -21,9 +21,9 @@ class SlavedIdTracker(object):
|
||||||
self.step = step
|
self.step = step
|
||||||
self._current = _load_current_id(db_conn, table, column, step)
|
self._current = _load_current_id(db_conn, table, column, step)
|
||||||
for table, column in extra_tables:
|
for table, column in extra_tables:
|
||||||
self.advance(_load_current_id(db_conn, table, column))
|
self.advance(None, _load_current_id(db_conn, table, column))
|
||||||
|
|
||||||
def advance(self, new_id):
|
def advance(self, instance_name, new_id):
|
||||||
self._current = (max if self.step > 0 else min)(self._current, new_id)
|
self._current = (max if self.step > 0 else min)(self._current, new_id)
|
||||||
|
|
||||||
def get_current_token(self):
|
def get_current_token(self):
|
||||||
|
@ -33,3 +33,11 @@ class SlavedIdTracker(object):
|
||||||
int
|
int
|
||||||
"""
|
"""
|
||||||
return self._current
|
return self._current
|
||||||
|
|
||||||
|
def get_current_token_for_writer(self, instance_name: str) -> int:
|
||||||
|
"""Returns the position of the given writer.
|
||||||
|
|
||||||
|
For streams with single writers this is equivalent to
|
||||||
|
`get_current_token`.
|
||||||
|
"""
|
||||||
|
return self.get_current_token()
|
||||||
|
|
|
@ -41,12 +41,12 @@ class SlavedAccountDataStore(TagsWorkerStore, AccountDataWorkerStore, BaseSlaved
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
if stream_name == TagAccountDataStream.NAME:
|
if stream_name == TagAccountDataStream.NAME:
|
||||||
self._account_data_id_gen.advance(token)
|
self._account_data_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.get_tags_for_user.invalidate((row.user_id,))
|
self.get_tags_for_user.invalidate((row.user_id,))
|
||||||
self._account_data_stream_cache.entity_has_changed(row.user_id, token)
|
self._account_data_stream_cache.entity_has_changed(row.user_id, token)
|
||||||
elif stream_name == AccountDataStream.NAME:
|
elif stream_name == AccountDataStream.NAME:
|
||||||
self._account_data_id_gen.advance(token)
|
self._account_data_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if not row.room_id:
|
if not row.room_id:
|
||||||
self.get_global_account_data_by_type_for_user.invalidate(
|
self.get_global_account_data_by_type_for_user.invalidate(
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SlavedDeviceInboxStore(DeviceInboxWorkerStore, BaseSlavedStore):
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
if stream_name == ToDeviceStream.NAME:
|
if stream_name == ToDeviceStream.NAME:
|
||||||
self._device_inbox_id_gen.advance(token)
|
self._device_inbox_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row.entity.startswith("@"):
|
if row.entity.startswith("@"):
|
||||||
self._device_inbox_stream_cache.entity_has_changed(
|
self._device_inbox_stream_cache.entity_has_changed(
|
||||||
|
|
|
@ -50,10 +50,10 @@ class SlavedDeviceStore(EndToEndKeyWorkerStore, DeviceWorkerStore, BaseSlavedSto
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
if stream_name == DeviceListsStream.NAME:
|
if stream_name == DeviceListsStream.NAME:
|
||||||
self._device_list_id_gen.advance(token)
|
self._device_list_id_gen.advance(instance_name, token)
|
||||||
self._invalidate_caches_for_devices(token, rows)
|
self._invalidate_caches_for_devices(token, rows)
|
||||||
elif stream_name == UserSignatureStream.NAME:
|
elif stream_name == UserSignatureStream.NAME:
|
||||||
self._device_list_id_gen.advance(token)
|
self._device_list_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self._user_signature_stream_cache.entity_has_changed(row.user_id, token)
|
self._user_signature_stream_cache.entity_has_changed(row.user_id, token)
|
||||||
return super().process_replication_rows(stream_name, instance_name, token, rows)
|
return super().process_replication_rows(stream_name, instance_name, token, rows)
|
||||||
|
|
|
@ -40,7 +40,7 @@ class SlavedGroupServerStore(GroupServerWorkerStore, BaseSlavedStore):
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
if stream_name == GroupServerStream.NAME:
|
if stream_name == GroupServerStream.NAME:
|
||||||
self._group_updates_id_gen.advance(token)
|
self._group_updates_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self._group_updates_stream_cache.entity_has_changed(row.user_id, token)
|
self._group_updates_stream_cache.entity_has_changed(row.user_id, token)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class SlavedPresenceStore(BaseSlavedStore):
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
if stream_name == PresenceStream.NAME:
|
if stream_name == PresenceStream.NAME:
|
||||||
self._presence_id_gen.advance(token)
|
self._presence_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.presence_stream_cache.entity_has_changed(row.user_id, token)
|
self.presence_stream_cache.entity_has_changed(row.user_id, token)
|
||||||
self._get_presence_for_user.invalidate((row.user_id,))
|
self._get_presence_for_user.invalidate((row.user_id,))
|
||||||
|
|
|
@ -14,6 +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.
|
||||||
|
|
||||||
|
from synapse.replication.slave.storage._slaved_id_tracker import SlavedIdTracker
|
||||||
from synapse.replication.tcp.streams import PushRulesStream
|
from synapse.replication.tcp.streams import PushRulesStream
|
||||||
from synapse.storage.databases.main.push_rule import PushRulesWorkerStore
|
from synapse.storage.databases.main.push_rule import PushRulesWorkerStore
|
||||||
|
|
||||||
|
@ -21,18 +22,15 @@ from .events import SlavedEventStore
|
||||||
|
|
||||||
|
|
||||||
class SlavedPushRuleStore(SlavedEventStore, PushRulesWorkerStore):
|
class SlavedPushRuleStore(SlavedEventStore, PushRulesWorkerStore):
|
||||||
def get_push_rules_stream_token(self):
|
|
||||||
return (
|
|
||||||
self._push_rules_stream_id_gen.get_current_token(),
|
|
||||||
self._stream_id_gen.get_current_token(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_max_push_rules_stream_id(self):
|
def get_max_push_rules_stream_id(self):
|
||||||
return self._push_rules_stream_id_gen.get_current_token()
|
return self._push_rules_stream_id_gen.get_current_token()
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
|
# We assert this for the benefit of mypy
|
||||||
|
assert isinstance(self._push_rules_stream_id_gen, SlavedIdTracker)
|
||||||
|
|
||||||
if stream_name == PushRulesStream.NAME:
|
if stream_name == PushRulesStream.NAME:
|
||||||
self._push_rules_stream_id_gen.advance(token)
|
self._push_rules_stream_id_gen.advance(instance_name, token)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.get_push_rules_for_user.invalidate((row.user_id,))
|
self.get_push_rules_for_user.invalidate((row.user_id,))
|
||||||
self.get_push_rules_enabled_for_user.invalidate((row.user_id,))
|
self.get_push_rules_enabled_for_user.invalidate((row.user_id,))
|
||||||
|
|
|
@ -34,5 +34,5 @@ class SlavedPusherStore(PusherWorkerStore, BaseSlavedStore):
|
||||||
|
|
||||||
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
def process_replication_rows(self, stream_name, instance_name, token, rows):
|
||||||
if stream_name == PushersStream.NAME:
|
if stream_name == PushersStream.NAME:
|
||||||
self._pushers_id_gen.advance(token)
|
self._pushers_id_gen.advance(instance_name, token)
|
||||||
return super().process_replication_rows(stream_name, instance_name, token, rows)
|
return super().process_replication_rows(stream_name, instance_name, token, rows)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue