Merge branch 'develop' of github.com:matrix-org/synapse into matrix-org-hotfixes
This commit is contained in:
commit
17201abd53
|
@ -3,6 +3,5 @@ Dockerfile
|
|||
.gitignore
|
||||
demo/etc
|
||||
tox.ini
|
||||
synctl
|
||||
.git/*
|
||||
.tox/*
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -44,6 +44,7 @@ media_store/
|
|||
build/
|
||||
venv/
|
||||
venv*/
|
||||
*venv/
|
||||
|
||||
localhost-800*/
|
||||
static/client/register/register_config.js
|
||||
|
|
|
@ -8,9 +8,6 @@ before_script:
|
|||
- git remote set-branches --add origin develop
|
||||
- git fetch origin develop
|
||||
|
||||
services:
|
||||
- postgresql
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
|
@ -25,6 +22,8 @@ matrix:
|
|||
|
||||
- python: 2.7
|
||||
env: TOX_ENV=py27-postgres TRIAL_FLAGS="-j 4"
|
||||
services:
|
||||
- postgresql
|
||||
|
||||
- python: 3.6
|
||||
env: TOX_ENV=py36
|
||||
|
@ -35,10 +34,6 @@ matrix:
|
|||
- python: 3.6
|
||||
env: TOX_ENV=check-newsfragment
|
||||
|
||||
allow_failures:
|
||||
- python: 2.7
|
||||
env: TOX_ENV=py27-postgres TRIAL_FLAGS="-j 4"
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
||||
|
|
87
CHANGES.md
87
CHANGES.md
|
@ -1,3 +1,86 @@
|
|||
Synapse 0.33.4 (2018-09-07)
|
||||
===========================
|
||||
|
||||
Internal Changes
|
||||
----------------
|
||||
|
||||
- Unignore synctl in .dockerignore to fix docker builds ([\#3802](https://github.com/matrix-org/synapse/issues/3802))
|
||||
|
||||
|
||||
Synapse 0.33.4rc2 (2018-09-06)
|
||||
==============================
|
||||
|
||||
Pull in security fixes from v0.33.3.1
|
||||
|
||||
|
||||
Synapse 0.33.3.1 (2018-09-06)
|
||||
=============================
|
||||
|
||||
SECURITY FIXES
|
||||
--------------
|
||||
|
||||
- Fix an issue where event signatures were not always correctly validated ([\#3796](https://github.com/matrix-org/synapse/issues/3796))
|
||||
- Fix an issue where server_acls could be circumvented for incoming events ([\#3796](https://github.com/matrix-org/synapse/issues/3796))
|
||||
|
||||
|
||||
Internal Changes
|
||||
----------------
|
||||
|
||||
- Unignore synctl in .dockerignore to fix docker builds ([\#3802](https://github.com/matrix-org/synapse/issues/3802))
|
||||
|
||||
|
||||
Synapse 0.33.4rc1 (2018-09-04)
|
||||
==============================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Support profile API endpoints on workers ([\#3659](https://github.com/matrix-org/synapse/issues/3659))
|
||||
- Server notices for resource limit blocking ([\#3680](https://github.com/matrix-org/synapse/issues/3680))
|
||||
- Allow guests to use /rooms/:roomId/event/:eventId ([\#3724](https://github.com/matrix-org/synapse/issues/3724))
|
||||
- Add mau_trial_days config param, so that users only get counted as MAU after N days. ([\#3749](https://github.com/matrix-org/synapse/issues/3749))
|
||||
- Require twisted 17.1 or later (fixes [#3741](https://github.com/matrix-org/synapse/issues/3741)). ([\#3751](https://github.com/matrix-org/synapse/issues/3751))
|
||||
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix error collecting prometheus metrics when run on dedicated thread due to threading concurrency issues ([\#3722](https://github.com/matrix-org/synapse/issues/3722))
|
||||
- Fix bug where we resent "limit exceeded" server notices repeatedly ([\#3747](https://github.com/matrix-org/synapse/issues/3747))
|
||||
- Fix bug where we broke sync when using limit_usage_by_mau but hadn't configured server notices ([\#3753](https://github.com/matrix-org/synapse/issues/3753))
|
||||
- Fix 'federation_domain_whitelist' such that an empty list correctly blocks all outbound federation traffic ([\#3754](https://github.com/matrix-org/synapse/issues/3754))
|
||||
- Fix tagging of server notice rooms ([\#3755](https://github.com/matrix-org/synapse/issues/3755), [\#3756](https://github.com/matrix-org/synapse/issues/3756))
|
||||
- Fix 'admin_uri' config variable and error parameter to be 'admin_contact' to match the spec. ([\#3758](https://github.com/matrix-org/synapse/issues/3758))
|
||||
- Don't return non-LL-member state in incremental sync state blocks ([\#3760](https://github.com/matrix-org/synapse/issues/3760))
|
||||
- Fix bug in sending presence over federation ([\#3768](https://github.com/matrix-org/synapse/issues/3768))
|
||||
- Fix bug where preserved threepid user comes to sign up and server is mau blocked ([\#3777](https://github.com/matrix-org/synapse/issues/3777))
|
||||
|
||||
Internal Changes
|
||||
----------------
|
||||
|
||||
- Removed the link to the unmaintained matrix-synapse-auto-deploy project from the readme. ([\#3378](https://github.com/matrix-org/synapse/issues/3378))
|
||||
- Refactor state module to support multiple room versions ([\#3673](https://github.com/matrix-org/synapse/issues/3673))
|
||||
- The synapse.storage module has been ported to Python 3. ([\#3725](https://github.com/matrix-org/synapse/issues/3725))
|
||||
- Split the state_group_cache into member and non-member state events (and so speed up LL /sync) ([\#3726](https://github.com/matrix-org/synapse/issues/3726))
|
||||
- Log failure to authenticate remote servers as warnings (without stack traces) ([\#3727](https://github.com/matrix-org/synapse/issues/3727))
|
||||
- The CONTRIBUTING guidelines have been updated to mention our use of Markdown and that .misc files have content. ([\#3730](https://github.com/matrix-org/synapse/issues/3730))
|
||||
- Reference the need for an HTTP replication port when using the federation_reader worker ([\#3734](https://github.com/matrix-org/synapse/issues/3734))
|
||||
- Fix minor spelling error in federation client documentation. ([\#3735](https://github.com/matrix-org/synapse/issues/3735))
|
||||
- Remove redundant state resolution function ([\#3737](https://github.com/matrix-org/synapse/issues/3737))
|
||||
- The test suite now passes on PostgreSQL. ([\#3740](https://github.com/matrix-org/synapse/issues/3740))
|
||||
- Fix MAU cache invalidation due to missing yield ([\#3746](https://github.com/matrix-org/synapse/issues/3746))
|
||||
- Make sure that we close db connections opened during init ([\#3764](https://github.com/matrix-org/synapse/issues/3764))
|
||||
|
||||
|
||||
Synapse 0.33.3 (2018-08-22)
|
||||
===========================
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix bug introduced in v0.33.3rc1 which made the ToS give a 500 error ([\#3732](https://github.com/matrix-org/synapse/issues/3732))
|
||||
|
||||
|
||||
Synapse 0.33.3rc2 (2018-08-21)
|
||||
==============================
|
||||
|
||||
|
@ -13,7 +96,7 @@ Synapse 0.33.3rc1 (2018-08-21)
|
|||
Features
|
||||
--------
|
||||
|
||||
- Add support for the SNI extension to federation TLS connections ([\#1491](https://github.com/matrix-org/synapse/issues/1491))
|
||||
- Add support for the SNI extension to federation TLS connections. Thanks to @vojeroen! ([\#3439](https://github.com/matrix-org/synapse/issues/3439))
|
||||
- Add /_media/r0/config ([\#3184](https://github.com/matrix-org/synapse/issues/3184))
|
||||
- speed up /members API and add `at` and `membership` params as per MSC1227 ([\#3568](https://github.com/matrix-org/synapse/issues/3568))
|
||||
- implement `summary` block in /sync response as per MSC688 ([\#3574](https://github.com/matrix-org/synapse/issues/3574))
|
||||
|
@ -97,7 +180,7 @@ Features
|
|||
Bugfixes
|
||||
--------
|
||||
|
||||
- Make /directory/list API return 404 for room not found instead of 400 ([\#2952](https://github.com/matrix-org/synapse/issues/2952))
|
||||
- Make /directory/list API return 404 for room not found instead of 400. Thanks to @fuzzmz! ([\#3620](https://github.com/matrix-org/synapse/issues/3620))
|
||||
- Default inviter_display_name to mxid for email invites ([\#3391](https://github.com/matrix-org/synapse/issues/3391))
|
||||
- Don't generate TURN credentials if no TURN config options are set ([\#3514](https://github.com/matrix-org/synapse/issues/3514))
|
||||
- Correctly announce deleted devices over federation ([\#3520](https://github.com/matrix-org/synapse/issues/3520))
|
||||
|
|
|
@ -59,9 +59,10 @@ To create a changelog entry, make a new file in the ``changelog.d``
|
|||
file named in the format of ``PRnumber.type``. The type can be
|
||||
one of ``feature``, ``bugfix``, ``removal`` (also used for
|
||||
deprecations), or ``misc`` (for internal-only changes). The content of
|
||||
the file is your changelog entry, which can contain RestructuredText
|
||||
formatting. A note of contributors is welcomed in changelogs for
|
||||
non-misc changes (the content of misc changes is not displayed).
|
||||
the file is your changelog entry, which can contain Markdown
|
||||
formatting. Adding credits to the changelog is encouraged, we value
|
||||
your contributions and would like to have you shouted out in the
|
||||
release notes!
|
||||
|
||||
For example, a fix in PR #1234 would have its changelog entry in
|
||||
``changelog.d/1234.bugfix``, and contain content like "The security levels of
|
||||
|
|
17
README.rst
17
README.rst
|
@ -167,11 +167,6 @@ Alternatively, Andreas Peters (previously Silvio Fricke) has contributed a
|
|||
Dockerfile to automate a synapse server in a single Docker image, at
|
||||
https://hub.docker.com/r/avhost/docker-matrix/tags/
|
||||
|
||||
Also, Martin Giess has created an auto-deployment process with vagrant/ansible,
|
||||
tested with VirtualBox/AWS/DigitalOcean - see
|
||||
https://github.com/EMnify/matrix-synapse-auto-deploy
|
||||
for details.
|
||||
|
||||
Configuring synapse
|
||||
-------------------
|
||||
|
||||
|
@ -747,6 +742,18 @@ so an example nginx configuration might look like::
|
|||
}
|
||||
}
|
||||
|
||||
and an example apache configuration may look like::
|
||||
|
||||
<VirtualHost *:443>
|
||||
SSLEngine on
|
||||
ServerName matrix.example.com;
|
||||
|
||||
<Location /_matrix>
|
||||
ProxyPass http://127.0.0.1:8008/_matrix nocanon
|
||||
ProxyPassReverse http://127.0.0.1:8008/_matrix
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
|
||||
You will also want to set ``bind_addresses: ['127.0.0.1']`` and ``x_forwarded: true``
|
||||
for port 8008 in ``homeserver.yaml`` to ensure that client IP addresses are
|
||||
recorded correctly.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Support profile API endpoints on workers
|
|
@ -1 +0,0 @@
|
|||
Refactor state module to support multiple room versions
|
|
@ -1 +0,0 @@
|
|||
Fix error collecting prometheus metrics when run on dedicated thread due to threading concurrency issues
|
|
@ -1 +0,0 @@
|
|||
Split the state_group_cache into member and non-member state events (and so speed up LL /sync)
|
|
@ -1 +0,0 @@
|
|||
Log failure to authenticate remote servers as warnings (without stack traces)
|
|
@ -1 +0,0 @@
|
|||
Fix bug introduced in v0.33.3rc1 which made the ToS give a 500 error
|
|
@ -1 +0,0 @@
|
|||
Fix minor spelling error in federation client documentation.
|
1
changelog.d/3771.misc
Normal file
1
changelog.d/3771.misc
Normal file
|
@ -0,0 +1 @@
|
|||
http/ is now ported to Python 3.
|
1
changelog.d/3788.bugfix
Normal file
1
changelog.d/3788.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Remove connection ID for replication prometheus metrics, as it creates a large number of new series.
|
1
changelog.d/3789.misc
Normal file
1
changelog.d/3789.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Improve human readable error messages for threepid registration/account update
|
1
changelog.d/3790.feature
Normal file
1
changelog.d/3790.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Implement `event_format` filter param in `/sync`
|
1
changelog.d/3795.misc
Normal file
1
changelog.d/3795.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Make /sync slightly faster by avoiding needless copies
|
1
changelog.d/3800.bugfix
Normal file
1
changelog.d/3800.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
guest users should not be part of mau total
|
1
changelog.d/3803.misc
Normal file
1
changelog.d/3803.misc
Normal file
|
@ -0,0 +1 @@
|
|||
handlers/ is now ported to Python 3.
|
1
changelog.d/3804.bugfix
Normal file
1
changelog.d/3804.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Bump dependency on pyopenssl 16.x, to avoid incompatibility with recent Twisted.
|
1
changelog.d/3805.misc
Normal file
1
changelog.d/3805.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Limit the number of PDUs/EDUs per federation transaction
|
1
changelog.d/3806.misc
Normal file
1
changelog.d/3806.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Only start postgres instance for postgres tests on Travis CI
|
1
changelog.d/3808.misc
Normal file
1
changelog.d/3808.misc
Normal file
|
@ -0,0 +1 @@
|
|||
tests/ is now ported to Python 3.
|
1
changelog.d/3810.bugfix
Normal file
1
changelog.d/3810.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix existing room tags not coming down sync when joining a room
|
1
changelog.d/3834.misc
Normal file
1
changelog.d/3834.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Improved Dockerfile to remove build requirements after building reducing the image size.
|
|
@ -1,6 +1,8 @@
|
|||
FROM docker.io/python:2-alpine3.8
|
||||
|
||||
RUN apk add --no-cache --virtual .nacl_deps \
|
||||
COPY . /synapse
|
||||
|
||||
RUN apk add --no-cache --virtual .build_deps \
|
||||
build-base \
|
||||
libffi-dev \
|
||||
libjpeg-turbo-dev \
|
||||
|
@ -8,13 +10,16 @@ RUN apk add --no-cache --virtual .nacl_deps \
|
|||
libxslt-dev \
|
||||
linux-headers \
|
||||
postgresql-dev \
|
||||
su-exec \
|
||||
zlib-dev
|
||||
|
||||
COPY . /synapse
|
||||
|
||||
# A wheel cache may be provided in ./cache for faster build
|
||||
RUN cd /synapse \
|
||||
zlib-dev \
|
||||
&& cd /synapse \
|
||||
&& apk add --no-cache --virtual .runtime_deps \
|
||||
libffi \
|
||||
libjpeg-turbo \
|
||||
libressl \
|
||||
libxslt \
|
||||
libpq \
|
||||
zlib \
|
||||
su-exec \
|
||||
&& pip install --upgrade \
|
||||
lxml \
|
||||
pip \
|
||||
|
@ -26,8 +31,9 @@ RUN cd /synapse \
|
|||
&& rm -rf \
|
||||
setup.cfg \
|
||||
setup.py \
|
||||
synapse
|
||||
|
||||
synapse \
|
||||
&& apk del .build_deps
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
EXPOSE 8008/tcp 8448/tcp
|
||||
|
|
|
@ -74,7 +74,7 @@ replication endpoints that it's talking to on the main synapse process.
|
|||
``worker_replication_port`` should point to the TCP replication listener port and
|
||||
``worker_replication_http_port`` should point to the HTTP replication port.
|
||||
|
||||
Currently, only the ``event_creator`` worker requires specifying
|
||||
Currently, the ``event_creator`` and ``federation_reader`` workers require specifying
|
||||
``worker_replication_http_port``.
|
||||
|
||||
For instance::
|
||||
|
|
|
@ -31,5 +31,5 @@ $TOX_BIN/pip install 'setuptools>=18.5'
|
|||
$TOX_BIN/pip install 'pip>=10'
|
||||
|
||||
{ python synapse/python_dependencies.py
|
||||
echo lxml psycopg2
|
||||
echo lxml
|
||||
} | xargs $TOX_BIN/pip install
|
||||
|
|
|
@ -17,13 +17,14 @@ ignore =
|
|||
[pep8]
|
||||
max-line-length = 90
|
||||
# W503 requires that binary operators be at the end, not start, of lines. Erik
|
||||
# doesn't like it. E203 is contrary to PEP8.
|
||||
ignore = W503,E203
|
||||
# doesn't like it. E203 is contrary to PEP8. E731 is silly.
|
||||
ignore = W503,E203,E731
|
||||
|
||||
[flake8]
|
||||
# note that flake8 inherits the "ignore" settings from "pep8" (because it uses
|
||||
# pep8 to do those checks), but not the "max-line-length" setting
|
||||
max-line-length = 90
|
||||
ignore=W503,E203,E731
|
||||
|
||||
[isort]
|
||||
line_length = 89
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
""" This is a reference implementation of a Matrix home server.
|
||||
"""
|
||||
|
||||
__version__ = "0.33.3rc2"
|
||||
__version__ = "0.33.4"
|
||||
|
|
|
@ -26,6 +26,7 @@ import synapse.types
|
|||
from synapse import event_auth
|
||||
from synapse.api.constants import EventTypes, JoinRules, Membership
|
||||
from synapse.api.errors import AuthError, Codes, ResourceLimitError
|
||||
from synapse.config.server import is_threepid_reserved
|
||||
from synapse.types import UserID
|
||||
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
|
||||
from synapse.util.caches.lrucache import LruCache
|
||||
|
@ -775,34 +776,56 @@ class Auth(object):
|
|||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def check_auth_blocking(self, user_id=None):
|
||||
def check_auth_blocking(self, user_id=None, threepid=None):
|
||||
"""Checks if the user should be rejected for some external reason,
|
||||
such as monthly active user limiting or global disable flag
|
||||
|
||||
Args:
|
||||
user_id(str|None): If present, checks for presence against existing
|
||||
MAU cohort
|
||||
|
||||
threepid(dict|None): If present, checks for presence against configured
|
||||
reserved threepid. Used in cases where the user is trying register
|
||||
with a MAU blocked server, normally they would be rejected but their
|
||||
threepid is on the reserved list. user_id and
|
||||
threepid should never be set at the same time.
|
||||
"""
|
||||
|
||||
# Never fail an auth check for the server notices users
|
||||
# This can be a problem where event creation is prohibited due to blocking
|
||||
if user_id == self.hs.config.server_notices_mxid:
|
||||
return
|
||||
|
||||
if self.hs.config.hs_disabled:
|
||||
raise ResourceLimitError(
|
||||
403, self.hs.config.hs_disabled_message,
|
||||
errcode=Codes.RESOURCE_LIMIT_EXCEED,
|
||||
admin_uri=self.hs.config.admin_uri,
|
||||
errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
|
||||
admin_contact=self.hs.config.admin_contact,
|
||||
limit_type=self.hs.config.hs_disabled_limit_type
|
||||
)
|
||||
if self.hs.config.limit_usage_by_mau is True:
|
||||
# If the user is already part of the MAU cohort
|
||||
assert not (user_id and threepid)
|
||||
|
||||
# If the user is already part of the MAU cohort or a trial user
|
||||
if user_id:
|
||||
timestamp = yield self.store.user_last_seen_monthly_active(user_id)
|
||||
if timestamp:
|
||||
return
|
||||
|
||||
is_trial = yield self.store.is_trial_user(user_id)
|
||||
if is_trial:
|
||||
return
|
||||
elif threepid:
|
||||
# If the user does not exist yet, but is signing up with a
|
||||
# reserved threepid then pass auth check
|
||||
if is_threepid_reserved(self.hs.config, threepid):
|
||||
return
|
||||
# Else if there is no room in the MAU bucket, bail
|
||||
current_mau = yield self.store.get_monthly_active_count()
|
||||
if current_mau >= self.hs.config.max_mau_value:
|
||||
raise ResourceLimitError(
|
||||
403, "Monthly Active User Limit Exceeded",
|
||||
|
||||
admin_uri=self.hs.config.admin_uri,
|
||||
errcode=Codes.RESOURCE_LIMIT_EXCEED,
|
||||
admin_contact=self.hs.config.admin_contact,
|
||||
errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
|
||||
limit_type="monthly_active_user"
|
||||
)
|
||||
|
|
|
@ -78,6 +78,7 @@ class EventTypes(object):
|
|||
Name = "m.room.name"
|
||||
|
||||
ServerACL = "m.room.server_acl"
|
||||
Pinned = "m.room.pinned_events"
|
||||
|
||||
|
||||
class RejectedReason(object):
|
||||
|
@ -108,3 +109,6 @@ DEFAULT_ROOM_VERSION = RoomVersions.V1
|
|||
# vdh-test-version is a placeholder to get room versioning support working and tested
|
||||
# until we have a working v2.
|
||||
KNOWN_ROOM_VERSIONS = {RoomVersions.V1, RoomVersions.VDH_TEST}
|
||||
|
||||
ServerNoticeMsgType = "m.server_notice"
|
||||
ServerNoticeLimitReached = "m.server_notice.usage_limit_reached"
|
||||
|
|
|
@ -56,7 +56,7 @@ class Codes(object):
|
|||
SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||
CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||
CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||
RESOURCE_LIMIT_EXCEED = "M_RESOURCE_LIMIT_EXCEED"
|
||||
RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||
UNSUPPORTED_ROOM_VERSION = "M_UNSUPPORTED_ROOM_VERSION"
|
||||
INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
|
||||
|
||||
|
@ -238,11 +238,11 @@ class ResourceLimitError(SynapseError):
|
|||
"""
|
||||
def __init__(
|
||||
self, code, msg,
|
||||
errcode=Codes.RESOURCE_LIMIT_EXCEED,
|
||||
admin_uri=None,
|
||||
errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
|
||||
admin_contact=None,
|
||||
limit_type=None,
|
||||
):
|
||||
self.admin_uri = admin_uri
|
||||
self.admin_contact = admin_contact
|
||||
self.limit_type = limit_type
|
||||
super(ResourceLimitError, self).__init__(code, msg, errcode=errcode)
|
||||
|
||||
|
@ -250,7 +250,7 @@ class ResourceLimitError(SynapseError):
|
|||
return cs_error(
|
||||
self.msg,
|
||||
self.errcode,
|
||||
admin_uri=self.admin_uri,
|
||||
admin_contact=self.admin_contact,
|
||||
limit_type=self.limit_type
|
||||
)
|
||||
|
||||
|
|
|
@ -251,6 +251,7 @@ class FilterCollection(object):
|
|||
"include_leave", False
|
||||
)
|
||||
self.event_fields = filter_json.get("event_fields", [])
|
||||
self.event_format = filter_json.get("event_format", "client")
|
||||
|
||||
def __repr__(self):
|
||||
return "<FilterCollection %s>" % (json.dumps(self._filter_json),)
|
||||
|
|
|
@ -51,10 +51,7 @@ class AppserviceSlaveStore(
|
|||
|
||||
|
||||
class AppserviceServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = AppserviceSlaveStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = AppserviceSlaveStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -74,10 +74,7 @@ class ClientReaderSlavedStore(
|
|||
|
||||
|
||||
class ClientReaderServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = ClientReaderSlavedStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = ClientReaderSlavedStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -90,10 +90,7 @@ class EventCreatorSlavedStore(
|
|||
|
||||
|
||||
class EventCreatorServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = EventCreatorSlavedStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = EventCreatorSlavedStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -72,10 +72,7 @@ class FederationReaderSlavedStore(
|
|||
|
||||
|
||||
class FederationReaderServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = FederationReaderSlavedStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = FederationReaderSlavedStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -78,10 +78,7 @@ class FederationSenderSlaveStore(
|
|||
|
||||
|
||||
class FederationSenderServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = FederationSenderSlaveStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = FederationSenderSlaveStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -148,10 +148,7 @@ class FrontendProxySlavedStore(
|
|||
|
||||
|
||||
class FrontendProxyServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = FrontendProxySlavedStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = FrontendProxySlavedStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -62,7 +62,7 @@ from synapse.rest.key.v1.server_key_resource import LocalKey
|
|||
from synapse.rest.key.v2 import KeyApiV2Resource
|
||||
from synapse.rest.media.v0.content_repository import ContentRepoResource
|
||||
from synapse.server import HomeServer
|
||||
from synapse.storage import are_all_users_on_domain
|
||||
from synapse.storage import DataStore, are_all_users_on_domain
|
||||
from synapse.storage.engines import IncorrectDatabaseSetup, create_engine
|
||||
from synapse.storage.prepare_database import UpgradeDatabaseException, prepare_database
|
||||
from synapse.util.caches import CACHE_SIZE_FACTOR
|
||||
|
@ -111,6 +111,8 @@ def build_resource_for_web_client(hs):
|
|||
|
||||
|
||||
class SynapseHomeServer(HomeServer):
|
||||
DATASTORE_CLASS = DataStore
|
||||
|
||||
def _listener_http(self, config, listener_config):
|
||||
port = listener_config["port"]
|
||||
bind_addresses = listener_config["bind_addresses"]
|
||||
|
@ -356,13 +358,13 @@ def setup(config_options):
|
|||
logger.info("Preparing database: %s...", config.database_config['name'])
|
||||
|
||||
try:
|
||||
db_conn = hs.get_db_conn(run_new_connection=False)
|
||||
prepare_database(db_conn, database_engine, config=config)
|
||||
database_engine.on_new_connection(db_conn)
|
||||
with hs.get_db_conn(run_new_connection=False) as db_conn:
|
||||
prepare_database(db_conn, database_engine, config=config)
|
||||
database_engine.on_new_connection(db_conn)
|
||||
|
||||
hs.run_startup_checks(db_conn, database_engine)
|
||||
hs.run_startup_checks(db_conn, database_engine)
|
||||
|
||||
db_conn.commit()
|
||||
db_conn.commit()
|
||||
except UpgradeDatabaseException:
|
||||
sys.stderr.write(
|
||||
"\nFailed to upgrade database.\n"
|
||||
|
|
|
@ -60,10 +60,7 @@ class MediaRepositorySlavedStore(
|
|||
|
||||
|
||||
class MediaRepositoryServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = MediaRepositorySlavedStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = MediaRepositorySlavedStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -78,10 +78,7 @@ class PusherSlaveStore(
|
|||
|
||||
|
||||
class PusherServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = PusherSlaveStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = PusherSlaveStore
|
||||
|
||||
def remove_pusher(self, app_id, push_key, user_id):
|
||||
self.get_tcp_replication().send_remove_pusher(app_id, push_key, user_id)
|
||||
|
|
|
@ -249,10 +249,7 @@ class SynchrotronApplicationService(object):
|
|||
|
||||
|
||||
class SynchrotronServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = SynchrotronSlavedStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = SynchrotronSlavedStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -94,10 +94,7 @@ class UserDirectorySlaveStore(
|
|||
|
||||
|
||||
class UserDirectoryServer(HomeServer):
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = UserDirectorySlaveStore(self.get_db_conn(), self)
|
||||
logger.info("Finished setting up.")
|
||||
DATASTORE_CLASS = UserDirectorySlaveStore
|
||||
|
||||
def _listen_http(self, listener_config):
|
||||
port = listener_config["port"]
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
from six.moves import urllib
|
||||
|
||||
from prometheus_client import Counter
|
||||
|
||||
|
@ -98,7 +99,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||
def query_user(self, service, user_id):
|
||||
if service.url is None:
|
||||
defer.returnValue(False)
|
||||
uri = service.url + ("/users/%s" % urllib.quote(user_id))
|
||||
uri = service.url + ("/users/%s" % urllib.parse.quote(user_id))
|
||||
response = None
|
||||
try:
|
||||
response = yield self.get_json(uri, {
|
||||
|
@ -119,7 +120,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||
def query_alias(self, service, alias):
|
||||
if service.url is None:
|
||||
defer.returnValue(False)
|
||||
uri = service.url + ("/rooms/%s" % urllib.quote(alias))
|
||||
uri = service.url + ("/rooms/%s" % urllib.parse.quote(alias))
|
||||
response = None
|
||||
try:
|
||||
response = yield self.get_json(uri, {
|
||||
|
@ -153,7 +154,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||
service.url,
|
||||
APP_SERVICE_PREFIX,
|
||||
kind,
|
||||
urllib.quote(protocol)
|
||||
urllib.parse.quote(protocol)
|
||||
)
|
||||
try:
|
||||
response = yield self.get_json(uri, fields)
|
||||
|
@ -188,7 +189,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||
uri = "%s%s/thirdparty/protocol/%s" % (
|
||||
service.url,
|
||||
APP_SERVICE_PREFIX,
|
||||
urllib.quote(protocol)
|
||||
urllib.parse.quote(protocol)
|
||||
)
|
||||
try:
|
||||
info = yield self.get_json(uri, {})
|
||||
|
@ -228,7 +229,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
|||
txn_id = str(txn_id)
|
||||
|
||||
uri = service.url + ("/transactions/%s" %
|
||||
urllib.quote(txn_id))
|
||||
urllib.parse.quote(txn_id))
|
||||
try:
|
||||
yield self.put_json(
|
||||
uri=uri,
|
||||
|
|
|
@ -77,10 +77,15 @@ class ServerConfig(Config):
|
|||
self.max_mau_value = config.get(
|
||||
"max_mau_value", 0,
|
||||
)
|
||||
|
||||
self.mau_limits_reserved_threepids = config.get(
|
||||
"mau_limit_reserved_threepids", []
|
||||
)
|
||||
|
||||
self.mau_trial_days = config.get(
|
||||
"mau_trial_days", 0,
|
||||
)
|
||||
|
||||
# Options to disable HS
|
||||
self.hs_disabled = config.get("hs_disabled", False)
|
||||
self.hs_disabled_message = config.get("hs_disabled_message", "")
|
||||
|
@ -88,7 +93,7 @@ class ServerConfig(Config):
|
|||
|
||||
# Admin uri to direct users at should their instance become blocked
|
||||
# due to resource constraints
|
||||
self.admin_uri = config.get("admin_uri", None)
|
||||
self.admin_contact = config.get("admin_contact", None)
|
||||
|
||||
# FIXME: federation_domain_whitelist needs sytests
|
||||
self.federation_domain_whitelist = None
|
||||
|
@ -352,7 +357,7 @@ class ServerConfig(Config):
|
|||
# Homeserver blocking
|
||||
#
|
||||
# How to reach the server admin, used in ResourceLimitError
|
||||
# admin_uri: 'mailto:admin@server.com'
|
||||
# admin_contact: 'mailto:admin@server.com'
|
||||
#
|
||||
# Global block config
|
||||
#
|
||||
|
@ -365,6 +370,7 @@ class ServerConfig(Config):
|
|||
# Enables monthly active user checking
|
||||
# limit_usage_by_mau: False
|
||||
# max_mau_value: 50
|
||||
# mau_trial_days: 2
|
||||
#
|
||||
# Sometimes the server admin will want to ensure certain accounts are
|
||||
# never blocked by mau checking. These accounts are specified here.
|
||||
|
@ -398,6 +404,23 @@ class ServerConfig(Config):
|
|||
" service on the given port.")
|
||||
|
||||
|
||||
def is_threepid_reserved(config, threepid):
|
||||
"""Check the threepid against the reserved threepid config
|
||||
Args:
|
||||
config(ServerConfig) - to access server config attributes
|
||||
threepid(dict) - The threepid to test for
|
||||
|
||||
Returns:
|
||||
boolean Is the threepid undertest reserved_user
|
||||
"""
|
||||
|
||||
for tp in config.mau_limits_reserved_threepids:
|
||||
if (threepid['medium'] == tp['medium']
|
||||
and threepid['address'] == tp['address']):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def read_gc_thresholds(thresholds):
|
||||
"""Reads the three integer thresholds for garbage collection. Ensures that
|
||||
the thresholds are integers if thresholds are supplied.
|
||||
|
|
|
@ -13,17 +13,20 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
import six
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.defer import DeferredList
|
||||
|
||||
from synapse.api.constants import MAX_DEPTH
|
||||
from synapse.api.constants import MAX_DEPTH, EventTypes, Membership
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.crypto.event_signing import check_event_content_hash
|
||||
from synapse.events import FrozenEvent
|
||||
from synapse.events.utils import prune_event
|
||||
from synapse.http.servlet import assert_params_in_dict
|
||||
from synapse.types import get_domain_from_id
|
||||
from synapse.util import logcontext, unwrapFirstError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -133,34 +136,25 @@ class FederationBase(object):
|
|||
* throws a SynapseError if the signature check failed.
|
||||
The deferreds run their callbacks in the sentinel logcontext.
|
||||
"""
|
||||
|
||||
redacted_pdus = [
|
||||
prune_event(pdu)
|
||||
for pdu in pdus
|
||||
]
|
||||
|
||||
deferreds = self.keyring.verify_json_objects_for_server([
|
||||
(p.origin, p.get_pdu_json())
|
||||
for p in redacted_pdus
|
||||
])
|
||||
deferreds = _check_sigs_on_pdus(self.keyring, pdus)
|
||||
|
||||
ctx = logcontext.LoggingContext.current_context()
|
||||
|
||||
def callback(_, pdu, redacted):
|
||||
def callback(_, pdu):
|
||||
with logcontext.PreserveLoggingContext(ctx):
|
||||
if not check_event_content_hash(pdu):
|
||||
logger.warn(
|
||||
"Event content has been tampered, redacting %s: %s",
|
||||
pdu.event_id, pdu.get_pdu_json()
|
||||
)
|
||||
return redacted
|
||||
return prune_event(pdu)
|
||||
|
||||
if self.spam_checker.check_event_for_spam(pdu):
|
||||
logger.warn(
|
||||
"Event contains spam, redacting %s: %s",
|
||||
pdu.event_id, pdu.get_pdu_json()
|
||||
)
|
||||
return redacted
|
||||
return prune_event(pdu)
|
||||
|
||||
return pdu
|
||||
|
||||
|
@ -173,16 +167,116 @@ class FederationBase(object):
|
|||
)
|
||||
return failure
|
||||
|
||||
for deferred, pdu, redacted in zip(deferreds, pdus, redacted_pdus):
|
||||
for deferred, pdu in zip(deferreds, pdus):
|
||||
deferred.addCallbacks(
|
||||
callback, errback,
|
||||
callbackArgs=[pdu, redacted],
|
||||
callbackArgs=[pdu],
|
||||
errbackArgs=[pdu],
|
||||
)
|
||||
|
||||
return deferreds
|
||||
|
||||
|
||||
class PduToCheckSig(namedtuple("PduToCheckSig", [
|
||||
"pdu", "redacted_pdu_json", "event_id_domain", "sender_domain", "deferreds",
|
||||
])):
|
||||
pass
|
||||
|
||||
|
||||
def _check_sigs_on_pdus(keyring, pdus):
|
||||
"""Check that the given events are correctly signed
|
||||
|
||||
Args:
|
||||
keyring (synapse.crypto.Keyring): keyring object to do the checks
|
||||
pdus (Collection[EventBase]): the events to be checked
|
||||
|
||||
Returns:
|
||||
List[Deferred]: a Deferred for each event in pdus, which will either succeed if
|
||||
the signatures are valid, or fail (with a SynapseError) if not.
|
||||
"""
|
||||
|
||||
# (currently this is written assuming the v1 room structure; we'll probably want a
|
||||
# separate function for checking v2 rooms)
|
||||
|
||||
# we want to check that the event is signed by:
|
||||
#
|
||||
# (a) the server which created the event_id
|
||||
#
|
||||
# (b) the sender's server.
|
||||
#
|
||||
# - except in the case of invites created from a 3pid invite, which are exempt
|
||||
# from this check, because the sender has to match that of the original 3pid
|
||||
# invite, but the event may come from a different HS, for reasons that I don't
|
||||
# entirely grok (why do the senders have to match? and if they do, why doesn't the
|
||||
# joining server ask the inviting server to do the switcheroo with
|
||||
# exchange_third_party_invite?).
|
||||
#
|
||||
# That's pretty awful, since redacting such an invite will render it invalid
|
||||
# (because it will then look like a regular invite without a valid signature),
|
||||
# and signatures are *supposed* to be valid whether or not an event has been
|
||||
# redacted. But this isn't the worst of the ways that 3pid invites are broken.
|
||||
#
|
||||
# let's start by getting the domain for each pdu, and flattening the event back
|
||||
# to JSON.
|
||||
pdus_to_check = [
|
||||
PduToCheckSig(
|
||||
pdu=p,
|
||||
redacted_pdu_json=prune_event(p).get_pdu_json(),
|
||||
event_id_domain=get_domain_from_id(p.event_id),
|
||||
sender_domain=get_domain_from_id(p.sender),
|
||||
deferreds=[],
|
||||
)
|
||||
for p in pdus
|
||||
]
|
||||
|
||||
# first make sure that the event is signed by the event_id's domain
|
||||
deferreds = keyring.verify_json_objects_for_server([
|
||||
(p.event_id_domain, p.redacted_pdu_json)
|
||||
for p in pdus_to_check
|
||||
])
|
||||
|
||||
for p, d in zip(pdus_to_check, deferreds):
|
||||
p.deferreds.append(d)
|
||||
|
||||
# now let's look for events where the sender's domain is different to the
|
||||
# event id's domain (normally only the case for joins/leaves), and add additional
|
||||
# checks.
|
||||
pdus_to_check_sender = [
|
||||
p for p in pdus_to_check
|
||||
if p.sender_domain != p.event_id_domain and not _is_invite_via_3pid(p.pdu)
|
||||
]
|
||||
|
||||
more_deferreds = keyring.verify_json_objects_for_server([
|
||||
(p.sender_domain, p.redacted_pdu_json)
|
||||
for p in pdus_to_check_sender
|
||||
])
|
||||
|
||||
for p, d in zip(pdus_to_check_sender, more_deferreds):
|
||||
p.deferreds.append(d)
|
||||
|
||||
# replace lists of deferreds with single Deferreds
|
||||
return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check]
|
||||
|
||||
|
||||
def _flatten_deferred_list(deferreds):
|
||||
"""Given a list of one or more deferreds, either return the single deferred, or
|
||||
combine into a DeferredList.
|
||||
"""
|
||||
if len(deferreds) > 1:
|
||||
return DeferredList(deferreds, fireOnOneErrback=True, consumeErrors=True)
|
||||
else:
|
||||
assert len(deferreds) == 1
|
||||
return deferreds[0]
|
||||
|
||||
|
||||
def _is_invite_via_3pid(event):
|
||||
return (
|
||||
event.type == EventTypes.Member
|
||||
and event.membership == Membership.INVITE
|
||||
and "third_party_invite" in event.content
|
||||
)
|
||||
|
||||
|
||||
def event_from_pdu_json(pdu_json, outlier=False):
|
||||
"""Construct a FrozenEvent from an event json received over federation
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ class FederationServer(FederationBase):
|
|||
|
||||
@defer.inlineCallbacks
|
||||
@log_function
|
||||
def on_incoming_transaction(self, transaction_data):
|
||||
def on_incoming_transaction(self, origin, transaction_data):
|
||||
# keep this as early as possible to make the calculated origin ts as
|
||||
# accurate as possible.
|
||||
request_time = self._clock.time_msec()
|
||||
|
@ -108,34 +108,33 @@ class FederationServer(FederationBase):
|
|||
|
||||
if not transaction.transaction_id:
|
||||
raise Exception("Transaction missing transaction_id")
|
||||
if not transaction.origin:
|
||||
raise Exception("Transaction missing origin")
|
||||
|
||||
logger.debug("[%s] Got transaction", transaction.transaction_id)
|
||||
|
||||
# use a linearizer to ensure that we don't process the same transaction
|
||||
# multiple times in parallel.
|
||||
with (yield self._transaction_linearizer.queue(
|
||||
(transaction.origin, transaction.transaction_id),
|
||||
(origin, transaction.transaction_id),
|
||||
)):
|
||||
result = yield self._handle_incoming_transaction(
|
||||
transaction, request_time,
|
||||
origin, transaction, request_time,
|
||||
)
|
||||
|
||||
defer.returnValue(result)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _handle_incoming_transaction(self, transaction, request_time):
|
||||
def _handle_incoming_transaction(self, origin, transaction, request_time):
|
||||
""" Process an incoming transaction and return the HTTP response
|
||||
|
||||
Args:
|
||||
origin (unicode): the server making the request
|
||||
transaction (Transaction): incoming transaction
|
||||
request_time (int): timestamp that the HTTP request arrived at
|
||||
|
||||
Returns:
|
||||
Deferred[(int, object)]: http response code and body
|
||||
"""
|
||||
response = yield self.transaction_actions.have_responded(transaction)
|
||||
response = yield self.transaction_actions.have_responded(origin, transaction)
|
||||
|
||||
if response:
|
||||
logger.debug(
|
||||
|
@ -149,7 +148,7 @@ class FederationServer(FederationBase):
|
|||
|
||||
received_pdus_counter.inc(len(transaction.pdus))
|
||||
|
||||
origin_host, _ = parse_server_name(transaction.origin)
|
||||
origin_host, _ = parse_server_name(origin)
|
||||
|
||||
pdus_by_room = {}
|
||||
|
||||
|
@ -190,7 +189,7 @@ class FederationServer(FederationBase):
|
|||
event_id = pdu.event_id
|
||||
try:
|
||||
yield self._handle_received_pdu(
|
||||
transaction.origin, pdu
|
||||
origin, pdu
|
||||
)
|
||||
pdu_results[event_id] = {}
|
||||
except FederationError as e:
|
||||
|
@ -212,7 +211,7 @@ class FederationServer(FederationBase):
|
|||
if hasattr(transaction, "edus"):
|
||||
for edu in (Edu(**x) for x in transaction.edus):
|
||||
yield self.received_edu(
|
||||
transaction.origin,
|
||||
origin,
|
||||
edu.edu_type,
|
||||
edu.content
|
||||
)
|
||||
|
@ -224,6 +223,7 @@ class FederationServer(FederationBase):
|
|||
logger.debug("Returning: %s", str(response))
|
||||
|
||||
yield self.transaction_actions.set_response(
|
||||
origin,
|
||||
transaction,
|
||||
200, response
|
||||
)
|
||||
|
@ -838,9 +838,9 @@ class ReplicationFederationHandlerRegistry(FederationHandlerRegistry):
|
|||
)
|
||||
|
||||
return self._send_edu(
|
||||
edu_type=edu_type,
|
||||
origin=origin,
|
||||
content=content,
|
||||
edu_type=edu_type,
|
||||
origin=origin,
|
||||
content=content,
|
||||
)
|
||||
|
||||
def on_query(self, query_type, args):
|
||||
|
@ -851,6 +851,6 @@ class ReplicationFederationHandlerRegistry(FederationHandlerRegistry):
|
|||
return handler(args)
|
||||
|
||||
return self._get_query_client(
|
||||
query_type=query_type,
|
||||
args=args,
|
||||
query_type=query_type,
|
||||
args=args,
|
||||
)
|
||||
|
|
|
@ -36,7 +36,7 @@ class TransactionActions(object):
|
|||
self.store = datastore
|
||||
|
||||
@log_function
|
||||
def have_responded(self, transaction):
|
||||
def have_responded(self, origin, transaction):
|
||||
""" Have we already responded to a transaction with the same id and
|
||||
origin?
|
||||
|
||||
|
@ -50,11 +50,11 @@ class TransactionActions(object):
|
|||
"transaction_id")
|
||||
|
||||
return self.store.get_received_txn_response(
|
||||
transaction.transaction_id, transaction.origin
|
||||
transaction.transaction_id, origin
|
||||
)
|
||||
|
||||
@log_function
|
||||
def set_response(self, transaction, code, response):
|
||||
def set_response(self, origin, transaction, code, response):
|
||||
""" Persist how we responded to a transaction.
|
||||
|
||||
Returns:
|
||||
|
@ -66,7 +66,7 @@ class TransactionActions(object):
|
|||
|
||||
return self.store.set_received_txn_response(
|
||||
transaction.transaction_id,
|
||||
transaction.origin,
|
||||
origin,
|
||||
code,
|
||||
response,
|
||||
)
|
||||
|
|
|
@ -32,7 +32,7 @@ Events are replicated via a separate events stream.
|
|||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
from six import iteritems, itervalues
|
||||
from six import iteritems
|
||||
|
||||
from sortedcontainers import SortedDict
|
||||
|
||||
|
@ -117,7 +117,7 @@ class FederationRemoteSendQueue(object):
|
|||
|
||||
user_ids = set(
|
||||
user_id
|
||||
for uids in itervalues(self.presence_changed)
|
||||
for uids in self.presence_changed.values()
|
||||
for user_id in uids
|
||||
)
|
||||
|
||||
|
|
|
@ -463,7 +463,19 @@ class TransactionQueue(object):
|
|||
# pending_transactions flag.
|
||||
|
||||
pending_pdus = self.pending_pdus_by_dest.pop(destination, [])
|
||||
|
||||
# We can only include at most 50 PDUs per transactions
|
||||
pending_pdus, leftover_pdus = pending_pdus[:50], pending_pdus[50:]
|
||||
if leftover_pdus:
|
||||
self.pending_pdus_by_dest[destination] = leftover_pdus
|
||||
|
||||
pending_edus = self.pending_edus_by_dest.pop(destination, [])
|
||||
|
||||
# We can only include at most 100 EDUs per transactions
|
||||
pending_edus, leftover_edus = pending_edus[:100], pending_edus[100:]
|
||||
if leftover_edus:
|
||||
self.pending_edus_by_dest[destination] = leftover_edus
|
||||
|
||||
pending_presence = self.pending_presence_by_dest.pop(destination, {})
|
||||
|
||||
pending_edus.extend(
|
||||
|
|
|
@ -353,7 +353,7 @@ class FederationSendServlet(BaseFederationServlet):
|
|||
|
||||
try:
|
||||
code, response = yield self.handler.on_incoming_transaction(
|
||||
transaction_data
|
||||
origin, transaction_data,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("on_incoming_transaction failed")
|
||||
|
|
|
@ -895,22 +895,24 @@ class AuthHandler(BaseHandler):
|
|||
|
||||
Args:
|
||||
password (unicode): Password to hash.
|
||||
stored_hash (unicode): Expected hash value.
|
||||
stored_hash (bytes): Expected hash value.
|
||||
|
||||
Returns:
|
||||
Deferred(bool): Whether self.hash(password) == stored_hash.
|
||||
"""
|
||||
|
||||
def _do_validate_hash():
|
||||
# Normalise the Unicode in the password
|
||||
pw = unicodedata.normalize("NFKC", password)
|
||||
|
||||
return bcrypt.checkpw(
|
||||
pw.encode('utf8') + self.hs.config.password_pepper.encode("utf8"),
|
||||
stored_hash.encode('utf8')
|
||||
stored_hash
|
||||
)
|
||||
|
||||
if stored_hash:
|
||||
if not isinstance(stored_hash, bytes):
|
||||
stored_hash = stored_hash.encode('ascii')
|
||||
|
||||
return make_deferred_yieldable(
|
||||
threads.deferToThreadPool(
|
||||
self.hs.get_reactor(),
|
||||
|
|
|
@ -330,7 +330,8 @@ class E2eKeysHandler(object):
|
|||
(algorithm, key_id, ex_json, key)
|
||||
)
|
||||
else:
|
||||
new_keys.append((algorithm, key_id, encode_canonical_json(key)))
|
||||
new_keys.append((
|
||||
algorithm, key_id, encode_canonical_json(key).decode('ascii')))
|
||||
|
||||
yield self.store.add_e2e_one_time_keys(
|
||||
user_id, device_id, time_now, new_keys
|
||||
|
@ -358,7 +359,7 @@ def _exception_to_failure(e):
|
|||
# Note that some Exceptions (notably twisted's ResponseFailed etc) don't
|
||||
# give a string for e.message, which json then fails to serialize.
|
||||
return {
|
||||
"status": 503, "message": str(e.message),
|
||||
"status": 503, "message": str(e),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -594,7 +594,7 @@ class FederationHandler(BaseHandler):
|
|||
|
||||
required_auth = set(
|
||||
a_id
|
||||
for event in events + state_events.values() + auth_events.values()
|
||||
for event in events + list(state_events.values()) + list(auth_events.values())
|
||||
for a_id, _ in event.auth_events
|
||||
)
|
||||
auth_events.update({
|
||||
|
@ -802,7 +802,7 @@ class FederationHandler(BaseHandler):
|
|||
)
|
||||
continue
|
||||
except NotRetryingDestination as e:
|
||||
logger.info(e.message)
|
||||
logger.info(str(e))
|
||||
continue
|
||||
except FederationDeniedError as e:
|
||||
logger.info(e)
|
||||
|
@ -1358,7 +1358,7 @@ class FederationHandler(BaseHandler):
|
|||
)
|
||||
|
||||
if state_groups:
|
||||
_, state = state_groups.items().pop()
|
||||
_, state = list(state_groups.items()).pop()
|
||||
results = state
|
||||
|
||||
if event.is_state():
|
||||
|
@ -1831,7 +1831,7 @@ class FederationHandler(BaseHandler):
|
|||
|
||||
room_version = yield self.store.get_room_version(event.room_id)
|
||||
|
||||
new_state = self.state_handler.resolve_events(
|
||||
new_state = yield self.state_handler.resolve_events(
|
||||
room_version,
|
||||
[list(local_view.values()), list(remote_view.values())],
|
||||
event
|
||||
|
|
|
@ -125,6 +125,7 @@ class RegistrationHandler(BaseHandler):
|
|||
guest_access_token=None,
|
||||
make_guest=False,
|
||||
admin=False,
|
||||
threepid=None,
|
||||
):
|
||||
"""Registers a new client on the server.
|
||||
|
||||
|
@ -145,7 +146,7 @@ class RegistrationHandler(BaseHandler):
|
|||
RegistrationError if there was a problem registering.
|
||||
"""
|
||||
|
||||
yield self.auth.check_auth_blocking()
|
||||
yield self.auth.check_auth_blocking(threepid=threepid)
|
||||
password_hash = None
|
||||
if password:
|
||||
password_hash = yield self.auth_handler().hash(password)
|
||||
|
|
|
@ -165,7 +165,7 @@ class RoomListHandler(BaseHandler):
|
|||
# Filter out rooms that we don't want to return
|
||||
rooms_to_scan = [
|
||||
r for r in sorted_rooms
|
||||
if r not in newly_unpublished and rooms_to_num_joined[room_id] > 0
|
||||
if r not in newly_unpublished and rooms_to_num_joined[r] > 0
|
||||
]
|
||||
|
||||
total_room_count = len(rooms_to_scan)
|
||||
|
|
|
@ -54,7 +54,7 @@ class SearchHandler(BaseHandler):
|
|||
batch_token = None
|
||||
if batch:
|
||||
try:
|
||||
b = decode_base64(batch)
|
||||
b = decode_base64(batch).decode('ascii')
|
||||
batch_group, batch_group_key, batch_token = b.split("\n")
|
||||
|
||||
assert batch_group is not None
|
||||
|
@ -258,18 +258,18 @@ class SearchHandler(BaseHandler):
|
|||
# it returns more from the same group (if applicable) rather
|
||||
# than reverting to searching all results again.
|
||||
if batch_group and batch_group_key:
|
||||
global_next_batch = encode_base64("%s\n%s\n%s" % (
|
||||
global_next_batch = encode_base64(("%s\n%s\n%s" % (
|
||||
batch_group, batch_group_key, pagination_token
|
||||
))
|
||||
)).encode('ascii'))
|
||||
else:
|
||||
global_next_batch = encode_base64("%s\n%s\n%s" % (
|
||||
global_next_batch = encode_base64(("%s\n%s\n%s" % (
|
||||
"all", "", pagination_token
|
||||
))
|
||||
)).encode('ascii'))
|
||||
|
||||
for room_id, group in room_groups.items():
|
||||
group["next_batch"] = encode_base64("%s\n%s\n%s" % (
|
||||
group["next_batch"] = encode_base64(("%s\n%s\n%s" % (
|
||||
"room_id", room_id, pagination_token
|
||||
))
|
||||
)).encode('ascii'))
|
||||
|
||||
allowed_events.extend(room_events)
|
||||
|
||||
|
|
|
@ -549,7 +549,7 @@ class SyncHandler(object):
|
|||
|
||||
member_ids = {
|
||||
state_key: event_id
|
||||
for (t, state_key), event_id in state_ids.iteritems()
|
||||
for (t, state_key), event_id in iteritems(state_ids)
|
||||
if t == EventTypes.Member
|
||||
}
|
||||
name_id = state_ids.get((EventTypes.Name, ''))
|
||||
|
@ -749,9 +749,16 @@ class SyncHandler(object):
|
|||
state_ids = {}
|
||||
if lazy_load_members:
|
||||
if types:
|
||||
# We're returning an incremental sync, with no "gap" since
|
||||
# the previous sync, so normally there would be no state to return
|
||||
# But we're lazy-loading, so the client might need some more
|
||||
# member events to understand the events in this timeline.
|
||||
# So we fish out all the member events corresponding to the
|
||||
# timeline here, and then dedupe any redundant ones below.
|
||||
|
||||
state_ids = yield self.store.get_state_ids_for_event(
|
||||
batch.events[0].event_id, types=types,
|
||||
filtered_types=filtered_types,
|
||||
filtered_types=None, # we only want members!
|
||||
)
|
||||
|
||||
if lazy_load_members and not include_redundant_members:
|
||||
|
@ -771,7 +778,7 @@ class SyncHandler(object):
|
|||
logger.debug("filtering state from %r...", state_ids)
|
||||
state_ids = {
|
||||
t: event_id
|
||||
for t, event_id in state_ids.iteritems()
|
||||
for t, event_id in iteritems(state_ids)
|
||||
if cache.get(t[1]) != event_id
|
||||
}
|
||||
logger.debug("...to %r", state_ids)
|
||||
|
@ -1572,6 +1579,19 @@ class SyncHandler(object):
|
|||
newly_joined_room=newly_joined,
|
||||
)
|
||||
|
||||
# When we join the room (or the client requests full_state), we should
|
||||
# send down any existing tags. Usually the user won't have tags in a
|
||||
# newly joined room, unless either a) they've joined before or b) the
|
||||
# tag was added by synapse e.g. for server notice rooms.
|
||||
if full_state:
|
||||
user_id = sync_result_builder.sync_config.user.to_string()
|
||||
tags = yield self.store.get_tags_for_room(user_id, room_id)
|
||||
|
||||
# If there aren't any tags, don't send the empty tags list down
|
||||
# sync
|
||||
if not tags:
|
||||
tags = None
|
||||
|
||||
account_data_events = []
|
||||
if tags is not None:
|
||||
account_data_events.append({
|
||||
|
@ -1726,17 +1746,17 @@ def _calculate_state(
|
|||
event_id_to_key = {
|
||||
e: key
|
||||
for key, e in itertools.chain(
|
||||
timeline_contains.items(),
|
||||
previous.items(),
|
||||
timeline_start.items(),
|
||||
current.items(),
|
||||
iteritems(timeline_contains),
|
||||
iteritems(previous),
|
||||
iteritems(timeline_start),
|
||||
iteritems(current),
|
||||
)
|
||||
}
|
||||
|
||||
c_ids = set(e for e in current.values())
|
||||
ts_ids = set(e for e in timeline_start.values())
|
||||
p_ids = set(e for e in previous.values())
|
||||
tc_ids = set(e for e in timeline_contains.values())
|
||||
c_ids = set(e for e in itervalues(current))
|
||||
ts_ids = set(e for e in itervalues(timeline_start))
|
||||
p_ids = set(e for e in itervalues(previous))
|
||||
tc_ids = set(e for e in itervalues(timeline_contains))
|
||||
|
||||
# If we are lazyloading room members, we explicitly add the membership events
|
||||
# for the senders in the timeline into the state block returned by /sync,
|
||||
|
@ -1750,7 +1770,7 @@ def _calculate_state(
|
|||
|
||||
if lazy_load_members:
|
||||
p_ids.difference_update(
|
||||
e for t, e in timeline_start.iteritems()
|
||||
e for t, e in iteritems(timeline_start)
|
||||
if t[0] == EventTypes.Member
|
||||
)
|
||||
|
||||
|
|
|
@ -13,24 +13,25 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
from six import StringIO
|
||||
from six import text_type
|
||||
from six.moves import urllib
|
||||
|
||||
import treq
|
||||
from canonicaljson import encode_canonical_json, json
|
||||
from prometheus_client import Counter
|
||||
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL.SSL import VERIFY_NONE
|
||||
from twisted.internet import defer, protocol, reactor, ssl, task
|
||||
from twisted.internet import defer, protocol, reactor, ssl
|
||||
from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
|
||||
from twisted.web._newclient import ResponseDone
|
||||
from twisted.web.client import (
|
||||
Agent,
|
||||
BrowserLikeRedirectAgent,
|
||||
ContentDecoderAgent,
|
||||
FileBodyProducer as TwistedFileBodyProducer,
|
||||
GzipDecoder,
|
||||
HTTPConnectionPool,
|
||||
PartialDownloadError,
|
||||
|
@ -83,18 +84,20 @@ class SimpleHttpClient(object):
|
|||
if hs.config.user_agent_suffix:
|
||||
self.user_agent = "%s %s" % (self.user_agent, hs.config.user_agent_suffix,)
|
||||
|
||||
self.user_agent = self.user_agent.encode('ascii')
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def request(self, method, uri, *args, **kwargs):
|
||||
def request(self, method, uri, data=b'', headers=None):
|
||||
# A small wrapper around self.agent.request() so we can easily attach
|
||||
# counters to it
|
||||
outgoing_requests_counter.labels(method).inc()
|
||||
|
||||
# log request but strip `access_token` (AS requests for example include this)
|
||||
logger.info("Sending request %s %s", method, redact_uri(uri))
|
||||
logger.info("Sending request %s %s", method, redact_uri(uri.encode('ascii')))
|
||||
|
||||
try:
|
||||
request_deferred = self.agent.request(
|
||||
method, uri, *args, **kwargs
|
||||
request_deferred = treq.request(
|
||||
method, uri, agent=self.agent, data=data, headers=headers
|
||||
)
|
||||
add_timeout_to_deferred(
|
||||
request_deferred, 60, self.hs.get_reactor(),
|
||||
|
@ -105,14 +108,14 @@ class SimpleHttpClient(object):
|
|||
incoming_responses_counter.labels(method, response.code).inc()
|
||||
logger.info(
|
||||
"Received response to %s %s: %s",
|
||||
method, redact_uri(uri), response.code
|
||||
method, redact_uri(uri.encode('ascii')), response.code
|
||||
)
|
||||
defer.returnValue(response)
|
||||
except Exception as e:
|
||||
incoming_responses_counter.labels(method, "ERR").inc()
|
||||
logger.info(
|
||||
"Error sending request to %s %s: %s %s",
|
||||
method, redact_uri(uri), type(e).__name__, e.message
|
||||
method, redact_uri(uri.encode('ascii')), type(e).__name__, e.args[0]
|
||||
)
|
||||
raise
|
||||
|
||||
|
@ -137,7 +140,8 @@ class SimpleHttpClient(object):
|
|||
# TODO: Do we ever want to log message contents?
|
||||
logger.debug("post_urlencoded_get_json args: %s", args)
|
||||
|
||||
query_bytes = urllib.urlencode(encode_urlencode_args(args), True)
|
||||
query_bytes = urllib.parse.urlencode(
|
||||
encode_urlencode_args(args), True).encode("utf8")
|
||||
|
||||
actual_headers = {
|
||||
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
||||
|
@ -148,15 +152,14 @@ class SimpleHttpClient(object):
|
|||
|
||||
response = yield self.request(
|
||||
"POST",
|
||||
uri.encode("ascii"),
|
||||
uri,
|
||||
headers=Headers(actual_headers),
|
||||
bodyProducer=FileBodyProducer(StringIO(query_bytes))
|
||||
data=query_bytes
|
||||
)
|
||||
|
||||
body = yield make_deferred_yieldable(readBody(response))
|
||||
|
||||
if 200 <= response.code < 300:
|
||||
defer.returnValue(json.loads(body))
|
||||
body = yield make_deferred_yieldable(treq.json_content(response))
|
||||
defer.returnValue(body)
|
||||
else:
|
||||
raise HttpResponseException(response.code, response.phrase, body)
|
||||
|
||||
|
@ -191,9 +194,9 @@ class SimpleHttpClient(object):
|
|||
|
||||
response = yield self.request(
|
||||
"POST",
|
||||
uri.encode("ascii"),
|
||||
uri,
|
||||
headers=Headers(actual_headers),
|
||||
bodyProducer=FileBodyProducer(StringIO(json_str))
|
||||
data=json_str
|
||||
)
|
||||
|
||||
body = yield make_deferred_yieldable(readBody(response))
|
||||
|
@ -248,7 +251,7 @@ class SimpleHttpClient(object):
|
|||
ValueError: if the response was not JSON
|
||||
"""
|
||||
if len(args):
|
||||
query_bytes = urllib.urlencode(args, True)
|
||||
query_bytes = urllib.parse.urlencode(args, True)
|
||||
uri = "%s?%s" % (uri, query_bytes)
|
||||
|
||||
json_str = encode_canonical_json(json_body)
|
||||
|
@ -262,9 +265,9 @@ class SimpleHttpClient(object):
|
|||
|
||||
response = yield self.request(
|
||||
"PUT",
|
||||
uri.encode("ascii"),
|
||||
uri,
|
||||
headers=Headers(actual_headers),
|
||||
bodyProducer=FileBodyProducer(StringIO(json_str))
|
||||
data=json_str
|
||||
)
|
||||
|
||||
body = yield make_deferred_yieldable(readBody(response))
|
||||
|
@ -293,7 +296,7 @@ class SimpleHttpClient(object):
|
|||
HttpResponseException on a non-2xx HTTP response.
|
||||
"""
|
||||
if len(args):
|
||||
query_bytes = urllib.urlencode(args, True)
|
||||
query_bytes = urllib.parse.urlencode(args, True)
|
||||
uri = "%s?%s" % (uri, query_bytes)
|
||||
|
||||
actual_headers = {
|
||||
|
@ -304,7 +307,7 @@ class SimpleHttpClient(object):
|
|||
|
||||
response = yield self.request(
|
||||
"GET",
|
||||
uri.encode("ascii"),
|
||||
uri,
|
||||
headers=Headers(actual_headers),
|
||||
)
|
||||
|
||||
|
@ -339,7 +342,7 @@ class SimpleHttpClient(object):
|
|||
|
||||
response = yield self.request(
|
||||
"GET",
|
||||
url.encode("ascii"),
|
||||
url,
|
||||
headers=Headers(actual_headers),
|
||||
)
|
||||
|
||||
|
@ -434,12 +437,12 @@ class CaptchaServerHttpClient(SimpleHttpClient):
|
|||
|
||||
@defer.inlineCallbacks
|
||||
def post_urlencoded_get_raw(self, url, args={}):
|
||||
query_bytes = urllib.urlencode(encode_urlencode_args(args), True)
|
||||
query_bytes = urllib.parse.urlencode(encode_urlencode_args(args), True)
|
||||
|
||||
response = yield self.request(
|
||||
"POST",
|
||||
url.encode("ascii"),
|
||||
bodyProducer=FileBodyProducer(StringIO(query_bytes)),
|
||||
url,
|
||||
data=query_bytes,
|
||||
headers=Headers({
|
||||
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
||||
b"User-Agent": [self.user_agent],
|
||||
|
@ -510,7 +513,7 @@ def encode_urlencode_args(args):
|
|||
|
||||
|
||||
def encode_urlencode_arg(arg):
|
||||
if isinstance(arg, unicode):
|
||||
if isinstance(arg, text_type):
|
||||
return arg.encode('utf-8')
|
||||
elif isinstance(arg, list):
|
||||
return [encode_urlencode_arg(i) for i in arg]
|
||||
|
@ -542,26 +545,3 @@ class InsecureInterceptableContextFactory(ssl.ContextFactory):
|
|||
|
||||
def creatorForNetloc(self, hostname, port):
|
||||
return self
|
||||
|
||||
|
||||
class FileBodyProducer(TwistedFileBodyProducer):
|
||||
"""Workaround for https://twistedmatrix.com/trac/ticket/8473
|
||||
|
||||
We override the pauseProducing and resumeProducing methods in twisted's
|
||||
FileBodyProducer so that they do not raise exceptions if the task has
|
||||
already completed.
|
||||
"""
|
||||
|
||||
def pauseProducing(self):
|
||||
try:
|
||||
super(FileBodyProducer, self).pauseProducing()
|
||||
except task.TaskDone:
|
||||
# task has already completed
|
||||
pass
|
||||
|
||||
def resumeProducing(self):
|
||||
try:
|
||||
super(FileBodyProducer, self).resumeProducing()
|
||||
except task.NotPaused:
|
||||
# task was not paused (probably because it had already completed)
|
||||
pass
|
||||
|
|
|
@ -17,19 +17,19 @@ import cgi
|
|||
import logging
|
||||
import random
|
||||
import sys
|
||||
import urllib
|
||||
|
||||
from six import string_types
|
||||
from six.moves.urllib import parse as urlparse
|
||||
from six import PY3, string_types
|
||||
from six.moves import urllib
|
||||
|
||||
from canonicaljson import encode_canonical_json, json
|
||||
import treq
|
||||
from canonicaljson import encode_canonical_json
|
||||
from prometheus_client import Counter
|
||||
from signedjson.sign import sign_json
|
||||
|
||||
from twisted.internet import defer, protocol, reactor
|
||||
from twisted.internet.error import DNSLookupError
|
||||
from twisted.web._newclient import ResponseDone
|
||||
from twisted.web.client import Agent, HTTPConnectionPool, readBody
|
||||
from twisted.web.client import Agent, HTTPConnectionPool
|
||||
from twisted.web.http_headers import Headers
|
||||
|
||||
import synapse.metrics
|
||||
|
@ -58,13 +58,18 @@ incoming_responses_counter = Counter("synapse_http_matrixfederationclient_respon
|
|||
MAX_LONG_RETRIES = 10
|
||||
MAX_SHORT_RETRIES = 3
|
||||
|
||||
if PY3:
|
||||
MAXINT = sys.maxsize
|
||||
else:
|
||||
MAXINT = sys.maxint
|
||||
|
||||
|
||||
class MatrixFederationEndpointFactory(object):
|
||||
def __init__(self, hs):
|
||||
self.tls_client_options_factory = hs.tls_client_options_factory
|
||||
|
||||
def endpointForURI(self, uri):
|
||||
destination = uri.netloc
|
||||
destination = uri.netloc.decode('ascii')
|
||||
|
||||
return matrix_federation_endpoint(
|
||||
reactor, destination, timeout=10,
|
||||
|
@ -93,26 +98,32 @@ class MatrixFederationHttpClient(object):
|
|||
)
|
||||
self.clock = hs.get_clock()
|
||||
self._store = hs.get_datastore()
|
||||
self.version_string = hs.version_string
|
||||
self.version_string = hs.version_string.encode('ascii')
|
||||
self._next_id = 1
|
||||
|
||||
def _create_url(self, destination, path_bytes, param_bytes, query_bytes):
|
||||
return urlparse.urlunparse(
|
||||
("matrix", destination, path_bytes, param_bytes, query_bytes, "")
|
||||
return urllib.parse.urlunparse(
|
||||
(b"matrix", destination, path_bytes, param_bytes, query_bytes, b"")
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _request(self, destination, method, path,
|
||||
body_callback, headers_dict={}, param_bytes=b"",
|
||||
query_bytes=b"", retry_on_dns_fail=True,
|
||||
json=None, json_callback=None,
|
||||
param_bytes=b"",
|
||||
query=None, retry_on_dns_fail=True,
|
||||
timeout=None, long_retries=False,
|
||||
ignore_backoff=False,
|
||||
backoff_on_404=False):
|
||||
""" Creates and sends a request to the given server
|
||||
"""
|
||||
Creates and sends a request to the given server.
|
||||
|
||||
Args:
|
||||
destination (str): The remote server to send the HTTP request to.
|
||||
method (str): HTTP method
|
||||
path (str): The HTTP path
|
||||
json (dict or None): JSON to send in the body.
|
||||
json_callback (func or None): A callback to generate the JSON.
|
||||
query (dict or None): Query arguments.
|
||||
ignore_backoff (bool): true to ignore the historical backoff data
|
||||
and try the request anyway.
|
||||
backoff_on_404 (bool): Back off if we get a 404
|
||||
|
@ -133,7 +144,7 @@ class MatrixFederationHttpClient(object):
|
|||
failures, connection failures, SSL failures.)
|
||||
"""
|
||||
if (
|
||||
self.hs.config.federation_domain_whitelist and
|
||||
self.hs.config.federation_domain_whitelist is not None and
|
||||
destination not in self.hs.config.federation_domain_whitelist
|
||||
):
|
||||
raise FederationDeniedError(destination)
|
||||
|
@ -146,22 +157,29 @@ class MatrixFederationHttpClient(object):
|
|||
ignore_backoff=ignore_backoff,
|
||||
)
|
||||
|
||||
destination = destination.encode("ascii")
|
||||
headers_dict = {}
|
||||
path_bytes = path.encode("ascii")
|
||||
with limiter:
|
||||
headers_dict[b"User-Agent"] = [self.version_string]
|
||||
headers_dict[b"Host"] = [destination]
|
||||
if query:
|
||||
query_bytes = encode_query_args(query)
|
||||
else:
|
||||
query_bytes = b""
|
||||
|
||||
url_bytes = self._create_url(
|
||||
destination, path_bytes, param_bytes, query_bytes
|
||||
)
|
||||
headers_dict = {
|
||||
"User-Agent": [self.version_string],
|
||||
"Host": [destination],
|
||||
}
|
||||
|
||||
with limiter:
|
||||
url = self._create_url(
|
||||
destination.encode("ascii"), path_bytes, param_bytes, query_bytes
|
||||
).decode('ascii')
|
||||
|
||||
txn_id = "%s-O-%s" % (method, self._next_id)
|
||||
self._next_id = (self._next_id + 1) % (sys.maxint - 1)
|
||||
self._next_id = (self._next_id + 1) % (MAXINT - 1)
|
||||
|
||||
outbound_logger.info(
|
||||
"{%s} [%s] Sending request: %s %s",
|
||||
txn_id, destination, method, url_bytes
|
||||
txn_id, destination, method, url
|
||||
)
|
||||
|
||||
# XXX: Would be much nicer to retry only at the transaction-layer
|
||||
|
@ -171,23 +189,33 @@ class MatrixFederationHttpClient(object):
|
|||
else:
|
||||
retries_left = MAX_SHORT_RETRIES
|
||||
|
||||
http_url_bytes = urlparse.urlunparse(
|
||||
("", "", path_bytes, param_bytes, query_bytes, "")
|
||||
)
|
||||
http_url = urllib.parse.urlunparse(
|
||||
(b"", b"", path_bytes, param_bytes, query_bytes, b"")
|
||||
).decode('ascii')
|
||||
|
||||
log_result = None
|
||||
try:
|
||||
while True:
|
||||
producer = None
|
||||
if body_callback:
|
||||
producer = body_callback(method, http_url_bytes, headers_dict)
|
||||
|
||||
try:
|
||||
request_deferred = self.agent.request(
|
||||
if json_callback:
|
||||
json = json_callback()
|
||||
|
||||
if json:
|
||||
data = encode_canonical_json(json)
|
||||
headers_dict["Content-Type"] = ["application/json"]
|
||||
self.sign_request(
|
||||
destination, method, http_url, headers_dict, json
|
||||
)
|
||||
else:
|
||||
data = None
|
||||
self.sign_request(destination, method, http_url, headers_dict)
|
||||
|
||||
request_deferred = treq.request(
|
||||
method,
|
||||
url_bytes,
|
||||
Headers(headers_dict),
|
||||
producer
|
||||
url,
|
||||
headers=Headers(headers_dict),
|
||||
data=data,
|
||||
agent=self.agent,
|
||||
)
|
||||
add_timeout_to_deferred(
|
||||
request_deferred,
|
||||
|
@ -218,7 +246,7 @@ class MatrixFederationHttpClient(object):
|
|||
txn_id,
|
||||
destination,
|
||||
method,
|
||||
url_bytes,
|
||||
url,
|
||||
_flatten_response_never_received(e),
|
||||
)
|
||||
|
||||
|
@ -252,7 +280,7 @@ class MatrixFederationHttpClient(object):
|
|||
# :'(
|
||||
# Update transactions table?
|
||||
with logcontext.PreserveLoggingContext():
|
||||
body = yield readBody(response)
|
||||
body = yield treq.content(response)
|
||||
raise HttpResponseException(
|
||||
response.code, response.phrase, body
|
||||
)
|
||||
|
@ -297,11 +325,11 @@ class MatrixFederationHttpClient(object):
|
|||
auth_headers = []
|
||||
|
||||
for key, sig in request["signatures"][self.server_name].items():
|
||||
auth_headers.append(bytes(
|
||||
auth_headers.append((
|
||||
"X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
|
||||
self.server_name, key, sig,
|
||||
)
|
||||
))
|
||||
)).encode('ascii')
|
||||
)
|
||||
|
||||
headers_dict[b"Authorization"] = auth_headers
|
||||
|
||||
|
@ -347,24 +375,14 @@ class MatrixFederationHttpClient(object):
|
|||
"""
|
||||
|
||||
if not json_data_callback:
|
||||
def json_data_callback():
|
||||
return data
|
||||
|
||||
def body_callback(method, url_bytes, headers_dict):
|
||||
json_data = json_data_callback()
|
||||
self.sign_request(
|
||||
destination, method, url_bytes, headers_dict, json_data
|
||||
)
|
||||
producer = _JsonProducer(json_data)
|
||||
return producer
|
||||
json_data_callback = lambda: data
|
||||
|
||||
response = yield self._request(
|
||||
destination,
|
||||
"PUT",
|
||||
path,
|
||||
body_callback=body_callback,
|
||||
headers_dict={"Content-Type": ["application/json"]},
|
||||
query_bytes=encode_query_args(args),
|
||||
json_callback=json_data_callback,
|
||||
query=args,
|
||||
long_retries=long_retries,
|
||||
timeout=timeout,
|
||||
ignore_backoff=ignore_backoff,
|
||||
|
@ -376,8 +394,8 @@ class MatrixFederationHttpClient(object):
|
|||
check_content_type_is_json(response.headers)
|
||||
|
||||
with logcontext.PreserveLoggingContext():
|
||||
body = yield readBody(response)
|
||||
defer.returnValue(json.loads(body))
|
||||
body = yield treq.json_content(response)
|
||||
defer.returnValue(body)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def post_json(self, destination, path, data={}, long_retries=False,
|
||||
|
@ -410,20 +428,12 @@ class MatrixFederationHttpClient(object):
|
|||
Fails with ``FederationDeniedError`` if this destination
|
||||
is not on our federation whitelist
|
||||
"""
|
||||
|
||||
def body_callback(method, url_bytes, headers_dict):
|
||||
self.sign_request(
|
||||
destination, method, url_bytes, headers_dict, data
|
||||
)
|
||||
return _JsonProducer(data)
|
||||
|
||||
response = yield self._request(
|
||||
destination,
|
||||
"POST",
|
||||
path,
|
||||
query_bytes=encode_query_args(args),
|
||||
body_callback=body_callback,
|
||||
headers_dict={"Content-Type": ["application/json"]},
|
||||
query=args,
|
||||
json=data,
|
||||
long_retries=long_retries,
|
||||
timeout=timeout,
|
||||
ignore_backoff=ignore_backoff,
|
||||
|
@ -434,9 +444,9 @@ class MatrixFederationHttpClient(object):
|
|||
check_content_type_is_json(response.headers)
|
||||
|
||||
with logcontext.PreserveLoggingContext():
|
||||
body = yield readBody(response)
|
||||
body = yield treq.json_content(response)
|
||||
|
||||
defer.returnValue(json.loads(body))
|
||||
defer.returnValue(body)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_json(self, destination, path, args=None, retry_on_dns_fail=True,
|
||||
|
@ -471,16 +481,11 @@ class MatrixFederationHttpClient(object):
|
|||
|
||||
logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
|
||||
|
||||
def body_callback(method, url_bytes, headers_dict):
|
||||
self.sign_request(destination, method, url_bytes, headers_dict)
|
||||
return None
|
||||
|
||||
response = yield self._request(
|
||||
destination,
|
||||
"GET",
|
||||
path,
|
||||
query_bytes=encode_query_args(args),
|
||||
body_callback=body_callback,
|
||||
query=args,
|
||||
retry_on_dns_fail=retry_on_dns_fail,
|
||||
timeout=timeout,
|
||||
ignore_backoff=ignore_backoff,
|
||||
|
@ -491,9 +496,9 @@ class MatrixFederationHttpClient(object):
|
|||
check_content_type_is_json(response.headers)
|
||||
|
||||
with logcontext.PreserveLoggingContext():
|
||||
body = yield readBody(response)
|
||||
body = yield treq.json_content(response)
|
||||
|
||||
defer.returnValue(json.loads(body))
|
||||
defer.returnValue(body)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def delete_json(self, destination, path, long_retries=False,
|
||||
|
@ -523,13 +528,11 @@ class MatrixFederationHttpClient(object):
|
|||
Fails with ``FederationDeniedError`` if this destination
|
||||
is not on our federation whitelist
|
||||
"""
|
||||
|
||||
response = yield self._request(
|
||||
destination,
|
||||
"DELETE",
|
||||
path,
|
||||
query_bytes=encode_query_args(args),
|
||||
headers_dict={"Content-Type": ["application/json"]},
|
||||
query=args,
|
||||
long_retries=long_retries,
|
||||
timeout=timeout,
|
||||
ignore_backoff=ignore_backoff,
|
||||
|
@ -540,9 +543,9 @@ class MatrixFederationHttpClient(object):
|
|||
check_content_type_is_json(response.headers)
|
||||
|
||||
with logcontext.PreserveLoggingContext():
|
||||
body = yield readBody(response)
|
||||
body = yield treq.json_content(response)
|
||||
|
||||
defer.returnValue(json.loads(body))
|
||||
defer.returnValue(body)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_file(self, destination, path, output_stream, args={},
|
||||
|
@ -569,26 +572,11 @@ class MatrixFederationHttpClient(object):
|
|||
Fails with ``FederationDeniedError`` if this destination
|
||||
is not on our federation whitelist
|
||||
"""
|
||||
|
||||
encoded_args = {}
|
||||
for k, vs in args.items():
|
||||
if isinstance(vs, string_types):
|
||||
vs = [vs]
|
||||
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
||||
|
||||
query_bytes = urllib.urlencode(encoded_args, True)
|
||||
logger.debug("Query bytes: %s Retry DNS: %s", query_bytes, retry_on_dns_fail)
|
||||
|
||||
def body_callback(method, url_bytes, headers_dict):
|
||||
self.sign_request(destination, method, url_bytes, headers_dict)
|
||||
return None
|
||||
|
||||
response = yield self._request(
|
||||
destination,
|
||||
"GET",
|
||||
path,
|
||||
query_bytes=query_bytes,
|
||||
body_callback=body_callback,
|
||||
query=args,
|
||||
retry_on_dns_fail=retry_on_dns_fail,
|
||||
ignore_backoff=ignore_backoff,
|
||||
)
|
||||
|
@ -639,30 +627,6 @@ def _readBodyToFile(response, stream, max_size):
|
|||
return d
|
||||
|
||||
|
||||
class _JsonProducer(object):
|
||||
""" Used by the twisted http client to create the HTTP body from json
|
||||
"""
|
||||
def __init__(self, jsn):
|
||||
self.reset(jsn)
|
||||
|
||||
def reset(self, jsn):
|
||||
self.body = encode_canonical_json(jsn)
|
||||
self.length = len(self.body)
|
||||
|
||||
def startProducing(self, consumer):
|
||||
consumer.write(self.body)
|
||||
return defer.succeed(None)
|
||||
|
||||
def pauseProducing(self):
|
||||
pass
|
||||
|
||||
def stopProducing(self):
|
||||
pass
|
||||
|
||||
def resumeProducing(self):
|
||||
pass
|
||||
|
||||
|
||||
def _flatten_response_never_received(e):
|
||||
if hasattr(e, "reasons"):
|
||||
reasons = ", ".join(
|
||||
|
@ -693,7 +657,7 @@ def check_content_type_is_json(headers):
|
|||
"No Content-Type header"
|
||||
)
|
||||
|
||||
c_type = c_type[0] # only the first header
|
||||
c_type = c_type[0].decode('ascii') # only the first header
|
||||
val, options = cgi.parse_header(c_type)
|
||||
if val != "application/json":
|
||||
raise RuntimeError(
|
||||
|
@ -711,6 +675,6 @@ def encode_query_args(args):
|
|||
vs = [vs]
|
||||
encoded_args[k] = [v.encode("UTF-8") for v in vs]
|
||||
|
||||
query_bytes = urllib.urlencode(encoded_args, True)
|
||||
query_bytes = urllib.parse.urlencode(encoded_args, True)
|
||||
|
||||
return query_bytes
|
||||
return query_bytes.encode('utf8')
|
||||
|
|
|
@ -204,14 +204,14 @@ class SynapseRequest(Request):
|
|||
self.start_time = time.time()
|
||||
self.request_metrics = RequestMetrics()
|
||||
self.request_metrics.start(
|
||||
self.start_time, name=servlet_name, method=self.method,
|
||||
self.start_time, name=servlet_name, method=self.method.decode('ascii'),
|
||||
)
|
||||
|
||||
self.site.access_logger.info(
|
||||
"%s - %s - Received request: %s %s",
|
||||
self.getClientIP(),
|
||||
self.site.site_tag,
|
||||
self.method,
|
||||
self.method.decode('ascii'),
|
||||
self.get_redacted_uri()
|
||||
)
|
||||
|
||||
|
|
|
@ -39,10 +39,11 @@ REQUIREMENTS = {
|
|||
"signedjson>=1.0.0": ["signedjson>=1.0.0"],
|
||||
"pynacl>=1.2.1": ["nacl>=1.2.1", "nacl.bindings"],
|
||||
"service_identity>=1.0.0": ["service_identity>=1.0.0"],
|
||||
"Twisted>=16.0.0": ["twisted>=16.0.0"],
|
||||
"Twisted>=17.1.0": ["twisted>=17.1.0"],
|
||||
"treq>=15.1": ["treq>=15.1"],
|
||||
|
||||
# We use crypto.get_elliptic_curve which is only supported in >=0.15
|
||||
"pyopenssl>=0.15": ["OpenSSL>=0.15"],
|
||||
# Twisted has required pyopenssl 16.0 since about Twisted 16.6.
|
||||
"pyopenssl>=16.0.0": ["OpenSSL>=16.0.0"],
|
||||
|
||||
"pyyaml": ["yaml"],
|
||||
"pyasn1": ["pyasn1"],
|
||||
|
@ -78,6 +79,9 @@ CONDITIONAL_REQUIREMENTS = {
|
|||
"affinity": {
|
||||
"affinity": ["affinity"],
|
||||
},
|
||||
"postgres": {
|
||||
"psycopg2>=2.6": ["psycopg2"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -590,9 +590,9 @@ class ClientReplicationStreamProtocol(BaseReplicationStreamProtocol):
|
|||
pending_commands = LaterGauge(
|
||||
"synapse_replication_tcp_protocol_pending_commands",
|
||||
"",
|
||||
["name", "conn_id"],
|
||||
["name"],
|
||||
lambda: {
|
||||
(p.name, p.conn_id): len(p.pending_commands) for p in connected_connections
|
||||
(p.name,): len(p.pending_commands) for p in connected_connections
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -607,9 +607,9 @@ def transport_buffer_size(protocol):
|
|||
transport_send_buffer = LaterGauge(
|
||||
"synapse_replication_tcp_protocol_transport_send_buffer",
|
||||
"",
|
||||
["name", "conn_id"],
|
||||
["name"],
|
||||
lambda: {
|
||||
(p.name, p.conn_id): transport_buffer_size(p) for p in connected_connections
|
||||
(p.name,): transport_buffer_size(p) for p in connected_connections
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -632,9 +632,9 @@ def transport_kernel_read_buffer_size(protocol, read=True):
|
|||
tcp_transport_kernel_send_buffer = LaterGauge(
|
||||
"synapse_replication_tcp_protocol_transport_kernel_send_buffer",
|
||||
"",
|
||||
["name", "conn_id"],
|
||||
["name"],
|
||||
lambda: {
|
||||
(p.name, p.conn_id): transport_kernel_read_buffer_size(p, False)
|
||||
(p.name,): transport_kernel_read_buffer_size(p, False)
|
||||
for p in connected_connections
|
||||
},
|
||||
)
|
||||
|
@ -643,9 +643,9 @@ tcp_transport_kernel_send_buffer = LaterGauge(
|
|||
tcp_transport_kernel_read_buffer = LaterGauge(
|
||||
"synapse_replication_tcp_protocol_transport_kernel_read_buffer",
|
||||
"",
|
||||
["name", "conn_id"],
|
||||
["name"],
|
||||
lambda: {
|
||||
(p.name, p.conn_id): transport_kernel_read_buffer_size(p, True)
|
||||
(p.name,): transport_kernel_read_buffer_size(p, True)
|
||||
for p in connected_connections
|
||||
},
|
||||
)
|
||||
|
@ -654,9 +654,9 @@ tcp_transport_kernel_read_buffer = LaterGauge(
|
|||
tcp_inbound_commands = LaterGauge(
|
||||
"synapse_replication_tcp_protocol_inbound_commands",
|
||||
"",
|
||||
["command", "name", "conn_id"],
|
||||
["command", "name"],
|
||||
lambda: {
|
||||
(k[0], p.name, p.conn_id): count
|
||||
(k[0], p.name,): count
|
||||
for p in connected_connections
|
||||
for k, count in iteritems(p.inbound_commands_counter)
|
||||
},
|
||||
|
@ -665,9 +665,9 @@ tcp_inbound_commands = LaterGauge(
|
|||
tcp_outbound_commands = LaterGauge(
|
||||
"synapse_replication_tcp_protocol_outbound_commands",
|
||||
"",
|
||||
["command", "name", "conn_id"],
|
||||
["command", "name"],
|
||||
lambda: {
|
||||
(k[0], p.name, p.conn_id): count
|
||||
(k[0], p.name,): count
|
||||
for p in connected_connections
|
||||
for k, count in iteritems(p.outbound_commands_counter)
|
||||
},
|
||||
|
|
|
@ -531,7 +531,7 @@ class RoomEventServlet(ClientV1RestServlet):
|
|||
|
||||
@defer.inlineCallbacks
|
||||
def on_GET(self, request, room_id, event_id):
|
||||
requester = yield self.auth.get_user_by_req(request)
|
||||
requester = yield self.auth.get_user_by_req(request, allow_guest=True)
|
||||
event = yield self.event_handler.get_event(requester.user, room_id, event_id)
|
||||
|
||||
time_now = self.clock.time_msec()
|
||||
|
|
|
@ -23,6 +23,7 @@ from twisted.internet import defer
|
|||
import synapse.util.stringutils as stringutils
|
||||
from synapse.api.constants import LoginType
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.config.server import is_threepid_reserved
|
||||
from synapse.http.servlet import assert_params_in_dict, parse_json_object_from_request
|
||||
from synapse.rest.client.v1.base import ClientV1RestServlet
|
||||
from synapse.types import create_requester
|
||||
|
@ -281,12 +282,20 @@ class RegisterRestServlet(ClientV1RestServlet):
|
|||
register_json["user"].encode("utf-8")
|
||||
if "user" in register_json else None
|
||||
)
|
||||
threepid = None
|
||||
if session.get(LoginType.EMAIL_IDENTITY):
|
||||
threepid = session["threepidCreds"]
|
||||
|
||||
handler = self.handlers.registration_handler
|
||||
(user_id, token) = yield handler.register(
|
||||
localpart=desired_user_id,
|
||||
password=password
|
||||
password=password,
|
||||
threepid=threepid,
|
||||
)
|
||||
# Necessary due to auth checks prior to the threepid being
|
||||
# written to the db
|
||||
if is_threepid_reserved(self.hs.config, threepid):
|
||||
yield self.store.upsert_monthly_active_user(user_id)
|
||||
|
||||
if session[LoginType.EMAIL_IDENTITY]:
|
||||
logger.debug("Binding emails %s to %s" % (
|
||||
|
|
|
@ -53,7 +53,9 @@ class EmailPasswordRequestTokenRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, "email", body['email']):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED,
|
||||
403,
|
||||
"Your email domain is not authorized on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
existingUid = yield self.hs.get_datastore().get_user_id_by_threepid(
|
||||
|
@ -89,7 +91,9 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, "msisdn", msisdn):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED,
|
||||
403,
|
||||
"Account phone numbers are not authorized on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
existingUid = yield self.datastore.get_user_id_by_threepid(
|
||||
|
@ -241,7 +245,9 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, "email", body['email']):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED,
|
||||
403,
|
||||
"Your email domain is not authorized on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
existingUid = yield self.datastore.get_user_id_by_threepid(
|
||||
|
@ -276,7 +282,9 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, "msisdn", msisdn):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED,
|
||||
403,
|
||||
"Account phone numbers are not authorized on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
existingUid = yield self.datastore.get_user_id_by_threepid(
|
||||
|
|
|
@ -26,6 +26,7 @@ import synapse
|
|||
import synapse.types
|
||||
from synapse.api.constants import LoginType
|
||||
from synapse.api.errors import Codes, SynapseError, UnrecognizedRequestError
|
||||
from synapse.config.server import is_threepid_reserved
|
||||
from synapse.http.servlet import (
|
||||
RestServlet,
|
||||
assert_params_in_dict,
|
||||
|
@ -74,7 +75,9 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, "email", body['email']):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED,
|
||||
403,
|
||||
"Your email domain is not authorized to register on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
existingUid = yield self.hs.get_datastore().get_user_id_by_threepid(
|
||||
|
@ -114,7 +117,9 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, "msisdn", msisdn):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed", Codes.THREEPID_DENIED,
|
||||
403,
|
||||
"Phone numbers are not authorized to register on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
existingUid = yield self.hs.get_datastore().get_user_id_by_threepid(
|
||||
|
@ -372,7 +377,9 @@ class RegisterRestServlet(RestServlet):
|
|||
|
||||
if not check_3pid_allowed(self.hs, medium, address):
|
||||
raise SynapseError(
|
||||
403, "Third party identifier is not allowed",
|
||||
403,
|
||||
"Third party identifiers (email/phone numbers)" +
|
||||
" are not authorized on this server",
|
||||
Codes.THREEPID_DENIED,
|
||||
)
|
||||
|
||||
|
@ -395,12 +402,21 @@ class RegisterRestServlet(RestServlet):
|
|||
if desired_username is not None:
|
||||
desired_username = desired_username.lower()
|
||||
|
||||
threepid = None
|
||||
if auth_result:
|
||||
threepid = auth_result.get(LoginType.EMAIL_IDENTITY)
|
||||
|
||||
(registered_user_id, _) = yield self.registration_handler.register(
|
||||
localpart=desired_username,
|
||||
password=new_password,
|
||||
guest_access_token=guest_access_token,
|
||||
generate_token=False,
|
||||
threepid=threepid,
|
||||
)
|
||||
# Necessary due to auth checks prior to the threepid being
|
||||
# written to the db
|
||||
if is_threepid_reserved(self.hs.config, threepid):
|
||||
yield self.store.upsert_monthly_active_user(registered_user_id)
|
||||
|
||||
# remember that we've now registered that user account, and with
|
||||
# what user ID (since the user may not have specified)
|
||||
|
|
|
@ -25,6 +25,7 @@ from synapse.api.errors import SynapseError
|
|||
from synapse.api.filtering import DEFAULT_FILTER_COLLECTION, FilterCollection
|
||||
from synapse.events.utils import (
|
||||
format_event_for_client_v2_without_room_id,
|
||||
format_event_raw,
|
||||
serialize_event,
|
||||
)
|
||||
from synapse.handlers.presence import format_user_presence_state
|
||||
|
@ -175,17 +176,28 @@ class SyncRestServlet(RestServlet):
|
|||
|
||||
@staticmethod
|
||||
def encode_response(time_now, sync_result, access_token_id, filter):
|
||||
if filter.event_format == 'client':
|
||||
event_formatter = format_event_for_client_v2_without_room_id
|
||||
elif filter.event_format == 'federation':
|
||||
event_formatter = format_event_raw
|
||||
else:
|
||||
raise Exception("Unknown event format %s" % (filter.event_format, ))
|
||||
|
||||
joined = SyncRestServlet.encode_joined(
|
||||
sync_result.joined, time_now, access_token_id, filter.event_fields
|
||||
sync_result.joined, time_now, access_token_id,
|
||||
filter.event_fields,
|
||||
event_formatter,
|
||||
)
|
||||
|
||||
invited = SyncRestServlet.encode_invited(
|
||||
sync_result.invited, time_now, access_token_id,
|
||||
event_formatter,
|
||||
)
|
||||
|
||||
archived = SyncRestServlet.encode_archived(
|
||||
sync_result.archived, time_now, access_token_id,
|
||||
filter.event_fields,
|
||||
event_formatter,
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -228,7 +240,7 @@ class SyncRestServlet(RestServlet):
|
|||
}
|
||||
|
||||
@staticmethod
|
||||
def encode_joined(rooms, time_now, token_id, event_fields):
|
||||
def encode_joined(rooms, time_now, token_id, event_fields, event_formatter):
|
||||
"""
|
||||
Encode the joined rooms in a sync result
|
||||
|
||||
|
@ -240,7 +252,9 @@ class SyncRestServlet(RestServlet):
|
|||
token_id(int): ID of the user's auth token - used for namespacing
|
||||
of transaction IDs
|
||||
event_fields(list<str>): List of event fields to include. If empty,
|
||||
all fields will be returned.
|
||||
all fields will be returned.
|
||||
event_formatter (func[dict]): function to convert from federation format
|
||||
to client format
|
||||
Returns:
|
||||
dict[str, dict[str, object]]: the joined rooms list, in our
|
||||
response format
|
||||
|
@ -248,13 +262,14 @@ class SyncRestServlet(RestServlet):
|
|||
joined = {}
|
||||
for room in rooms:
|
||||
joined[room.room_id] = SyncRestServlet.encode_room(
|
||||
room, time_now, token_id, only_fields=event_fields
|
||||
room, time_now, token_id, joined=True, only_fields=event_fields,
|
||||
event_formatter=event_formatter,
|
||||
)
|
||||
|
||||
return joined
|
||||
|
||||
@staticmethod
|
||||
def encode_invited(rooms, time_now, token_id):
|
||||
def encode_invited(rooms, time_now, token_id, event_formatter):
|
||||
"""
|
||||
Encode the invited rooms in a sync result
|
||||
|
||||
|
@ -264,7 +279,9 @@ class SyncRestServlet(RestServlet):
|
|||
time_now(int): current time - used as a baseline for age
|
||||
calculations
|
||||
token_id(int): ID of the user's auth token - used for namespacing
|
||||
of transaction IDs
|
||||
of transaction IDs
|
||||
event_formatter (func[dict]): function to convert from federation format
|
||||
to client format
|
||||
|
||||
Returns:
|
||||
dict[str, dict[str, object]]: the invited rooms list, in our
|
||||
|
@ -274,7 +291,7 @@ class SyncRestServlet(RestServlet):
|
|||
for room in rooms:
|
||||
invite = serialize_event(
|
||||
room.invite, time_now, token_id=token_id,
|
||||
event_format=format_event_for_client_v2_without_room_id,
|
||||
event_format=event_formatter,
|
||||
is_invite=True,
|
||||
)
|
||||
unsigned = dict(invite.get("unsigned", {}))
|
||||
|
@ -288,7 +305,7 @@ class SyncRestServlet(RestServlet):
|
|||
return invited
|
||||
|
||||
@staticmethod
|
||||
def encode_archived(rooms, time_now, token_id, event_fields):
|
||||
def encode_archived(rooms, time_now, token_id, event_fields, event_formatter):
|
||||
"""
|
||||
Encode the archived rooms in a sync result
|
||||
|
||||
|
@ -300,7 +317,9 @@ class SyncRestServlet(RestServlet):
|
|||
token_id(int): ID of the user's auth token - used for namespacing
|
||||
of transaction IDs
|
||||
event_fields(list<str>): List of event fields to include. If empty,
|
||||
all fields will be returned.
|
||||
all fields will be returned.
|
||||
event_formatter (func[dict]): function to convert from federation format
|
||||
to client format
|
||||
Returns:
|
||||
dict[str, dict[str, object]]: The invited rooms list, in our
|
||||
response format
|
||||
|
@ -308,13 +327,18 @@ class SyncRestServlet(RestServlet):
|
|||
joined = {}
|
||||
for room in rooms:
|
||||
joined[room.room_id] = SyncRestServlet.encode_room(
|
||||
room, time_now, token_id, joined=False, only_fields=event_fields
|
||||
room, time_now, token_id, joined=False,
|
||||
only_fields=event_fields,
|
||||
event_formatter=event_formatter,
|
||||
)
|
||||
|
||||
return joined
|
||||
|
||||
@staticmethod
|
||||
def encode_room(room, time_now, token_id, joined=True, only_fields=None):
|
||||
def encode_room(
|
||||
room, time_now, token_id, joined,
|
||||
only_fields, event_formatter,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
room (JoinedSyncResult|ArchivedSyncResult): sync result for a
|
||||
|
@ -326,14 +350,15 @@ class SyncRestServlet(RestServlet):
|
|||
joined (bool): True if the user is joined to this room - will mean
|
||||
we handle ephemeral events
|
||||
only_fields(list<str>): Optional. The list of event fields to include.
|
||||
event_formatter (func[dict]): function to convert from federation format
|
||||
to client format
|
||||
Returns:
|
||||
dict[str, object]: the room, encoded in our response format
|
||||
"""
|
||||
def serialize(event):
|
||||
# TODO(mjark): Respect formatting requirements in the filter.
|
||||
return serialize_event(
|
||||
event, time_now, token_id=token_id,
|
||||
event_format=format_event_for_client_v2_without_room_id,
|
||||
event_format=event_formatter,
|
||||
only_event_fields=only_fields,
|
||||
)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
# partial one for unit test mocking.
|
||||
|
||||
# Imports required for the default HomeServer() implementation
|
||||
import abc
|
||||
import logging
|
||||
|
||||
from twisted.enterprise import adbapi
|
||||
|
@ -81,7 +82,6 @@ from synapse.server_notices.server_notices_manager import ServerNoticesManager
|
|||
from synapse.server_notices.server_notices_sender import ServerNoticesSender
|
||||
from synapse.server_notices.worker_server_notices_sender import WorkerServerNoticesSender
|
||||
from synapse.state import StateHandler, StateResolutionHandler
|
||||
from synapse.storage import DataStore
|
||||
from synapse.streams.events import EventSources
|
||||
from synapse.util import Clock
|
||||
from synapse.util.distributor import Distributor
|
||||
|
@ -111,6 +111,8 @@ class HomeServer(object):
|
|||
config (synapse.config.homeserver.HomeserverConfig):
|
||||
"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
DEPENDENCIES = [
|
||||
'http_client',
|
||||
'db_pool',
|
||||
|
@ -172,6 +174,11 @@ class HomeServer(object):
|
|||
'room_context_handler',
|
||||
]
|
||||
|
||||
# This is overridden in derived application classes
|
||||
# (such as synapse.app.homeserver.SynapseHomeServer) and gives the class to be
|
||||
# instantiated during setup() for future return by get_datastore()
|
||||
DATASTORE_CLASS = abc.abstractproperty()
|
||||
|
||||
def __init__(self, hostname, reactor=None, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
|
@ -188,13 +195,16 @@ class HomeServer(object):
|
|||
self.distributor = Distributor()
|
||||
self.ratelimiter = Ratelimiter()
|
||||
|
||||
self.datastore = None
|
||||
|
||||
# Other kwargs are explicit dependencies
|
||||
for depname in kwargs:
|
||||
setattr(self, depname, kwargs[depname])
|
||||
|
||||
def setup(self):
|
||||
logger.info("Setting up.")
|
||||
self.datastore = DataStore(self.get_db_conn(), self)
|
||||
with self.get_db_conn() as conn:
|
||||
self.datastore = self.DATASTORE_CLASS(conn, self)
|
||||
logger.info("Finished setting up.")
|
||||
|
||||
def get_reactor(self):
|
||||
|
|
203
synapse/server_notices/resource_limits_server_notices.py
Normal file
203
synapse/server_notices/resource_limits_server_notices.py
Normal file
|
@ -0,0 +1,203 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 New Vector Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
|
||||
from six import iteritems
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.constants import (
|
||||
EventTypes,
|
||||
ServerNoticeLimitReached,
|
||||
ServerNoticeMsgType,
|
||||
)
|
||||
from synapse.api.errors import AuthError, ResourceLimitError, SynapseError
|
||||
from synapse.server_notices.server_notices_manager import SERVER_NOTICE_ROOM_TAG
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceLimitsServerNotices(object):
|
||||
""" Keeps track of whether the server has reached it's resource limit and
|
||||
ensures that the client is kept up to date.
|
||||
"""
|
||||
def __init__(self, hs):
|
||||
"""
|
||||
Args:
|
||||
hs (synapse.server.HomeServer):
|
||||
"""
|
||||
self._server_notices_manager = hs.get_server_notices_manager()
|
||||
self._store = hs.get_datastore()
|
||||
self._auth = hs.get_auth()
|
||||
self._config = hs.config
|
||||
self._resouce_limited = False
|
||||
self._message_handler = hs.get_message_handler()
|
||||
self._state = hs.get_state_handler()
|
||||
|
||||
self._notifier = hs.get_notifier()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def maybe_send_server_notice_to_user(self, user_id):
|
||||
"""Check if we need to send a notice to this user, this will be true in
|
||||
two cases.
|
||||
1. The server has reached its limit does not reflect this
|
||||
2. The room state indicates that the server has reached its limit when
|
||||
actually the server is fine
|
||||
|
||||
Args:
|
||||
user_id (str): user to check
|
||||
|
||||
Returns:
|
||||
Deferred
|
||||
"""
|
||||
if self._config.hs_disabled is True:
|
||||
return
|
||||
|
||||
if self._config.limit_usage_by_mau is False:
|
||||
return
|
||||
|
||||
if not self._server_notices_manager.is_enabled():
|
||||
# Don't try and send server notices unles they've been enabled
|
||||
return
|
||||
|
||||
timestamp = yield self._store.user_last_seen_monthly_active(user_id)
|
||||
if timestamp is None:
|
||||
# This user will be blocked from receiving the notice anyway.
|
||||
# In practice, not sure we can ever get here
|
||||
return
|
||||
|
||||
# Determine current state of room
|
||||
|
||||
room_id = yield self._server_notices_manager.get_notice_room_for_user(user_id)
|
||||
|
||||
if not room_id:
|
||||
logger.warn("Failed to get server notices room")
|
||||
return
|
||||
|
||||
yield self._check_and_set_tags(user_id, room_id)
|
||||
currently_blocked, ref_events = yield self._is_room_currently_blocked(room_id)
|
||||
|
||||
try:
|
||||
# Normally should always pass in user_id if you have it, but in
|
||||
# this case are checking what would happen to other users if they
|
||||
# were to arrive.
|
||||
try:
|
||||
yield self._auth.check_auth_blocking()
|
||||
is_auth_blocking = False
|
||||
except ResourceLimitError as e:
|
||||
is_auth_blocking = True
|
||||
event_content = e.msg
|
||||
event_limit_type = e.limit_type
|
||||
|
||||
if currently_blocked and not is_auth_blocking:
|
||||
# Room is notifying of a block, when it ought not to be.
|
||||
# Remove block notification
|
||||
content = {
|
||||
"pinned": ref_events
|
||||
}
|
||||
yield self._server_notices_manager.send_notice(
|
||||
user_id, content, EventTypes.Pinned, '',
|
||||
)
|
||||
|
||||
elif not currently_blocked and is_auth_blocking:
|
||||
# Room is not notifying of a block, when it ought to be.
|
||||
# Add block notification
|
||||
content = {
|
||||
'body': event_content,
|
||||
'msgtype': ServerNoticeMsgType,
|
||||
'server_notice_type': ServerNoticeLimitReached,
|
||||
'admin_contact': self._config.admin_contact,
|
||||
'limit_type': event_limit_type
|
||||
}
|
||||
event = yield self._server_notices_manager.send_notice(
|
||||
user_id, content, EventTypes.Message,
|
||||
)
|
||||
|
||||
content = {
|
||||
"pinned": [
|
||||
event.event_id,
|
||||
]
|
||||
}
|
||||
yield self._server_notices_manager.send_notice(
|
||||
user_id, content, EventTypes.Pinned, '',
|
||||
)
|
||||
|
||||
except SynapseError as e:
|
||||
logger.error("Error sending resource limits server notice: %s", e)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _check_and_set_tags(self, user_id, room_id):
|
||||
"""
|
||||
Since server notices rooms were originally not with tags,
|
||||
important to check that tags have been set correctly
|
||||
Args:
|
||||
user_id(str): the user in question
|
||||
room_id(str): the server notices room for that user
|
||||
"""
|
||||
tags = yield self._store.get_tags_for_room(user_id, room_id)
|
||||
need_to_set_tag = True
|
||||
if tags:
|
||||
if SERVER_NOTICE_ROOM_TAG in tags:
|
||||
# tag already present, nothing to do here
|
||||
need_to_set_tag = False
|
||||
if need_to_set_tag:
|
||||
max_id = yield self._store.add_tag_to_room(
|
||||
user_id, room_id, SERVER_NOTICE_ROOM_TAG, {}
|
||||
)
|
||||
self._notifier.on_new_event(
|
||||
"account_data_key", max_id, users=[user_id]
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _is_room_currently_blocked(self, room_id):
|
||||
"""
|
||||
Determines if the room is currently blocked
|
||||
|
||||
Args:
|
||||
room_id(str): The room id of the server notices room
|
||||
|
||||
Returns:
|
||||
|
||||
bool: Is the room currently blocked
|
||||
list: The list of pinned events that are unrelated to limit blocking
|
||||
This list can be used as a convenience in the case where the block
|
||||
is to be lifted and the remaining pinned event references need to be
|
||||
preserved
|
||||
"""
|
||||
currently_blocked = False
|
||||
pinned_state_event = None
|
||||
try:
|
||||
pinned_state_event = yield self._state.get_current_state(
|
||||
room_id, event_type=EventTypes.Pinned
|
||||
)
|
||||
except AuthError:
|
||||
# The user has yet to join the server notices room
|
||||
pass
|
||||
|
||||
referenced_events = []
|
||||
if pinned_state_event is not None:
|
||||
referenced_events = list(pinned_state_event.content.get('pinned', []))
|
||||
|
||||
events = yield self._store.get_events(referenced_events)
|
||||
for event_id, event in iteritems(events):
|
||||
if event.type != EventTypes.Message:
|
||||
continue
|
||||
if event.content.get("msgtype") == ServerNoticeMsgType:
|
||||
currently_blocked = True
|
||||
# remove event in case we need to disable blocking later on.
|
||||
if event_id in referenced_events:
|
||||
referenced_events.remove(event.event_id)
|
||||
|
||||
defer.returnValue((currently_blocked, referenced_events))
|
|
@ -22,6 +22,8 @@ from synapse.util.caches.descriptors import cachedInlineCallbacks
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SERVER_NOTICE_ROOM_TAG = "m.server_notice"
|
||||
|
||||
|
||||
class ServerNoticesManager(object):
|
||||
def __init__(self, hs):
|
||||
|
@ -37,6 +39,8 @@ class ServerNoticesManager(object):
|
|||
self._event_creation_handler = hs.get_event_creation_handler()
|
||||
self._is_mine_id = hs.is_mine_id
|
||||
|
||||
self._notifier = hs.get_notifier()
|
||||
|
||||
def is_enabled(self):
|
||||
"""Checks if server notices are enabled on this server.
|
||||
|
||||
|
@ -46,7 +50,10 @@ class ServerNoticesManager(object):
|
|||
return self._config.server_notices_mxid is not None
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def send_notice(self, user_id, event_content):
|
||||
def send_notice(
|
||||
self, user_id, event_content,
|
||||
type=EventTypes.Message, state_key=None
|
||||
):
|
||||
"""Send a notice to the given user
|
||||
|
||||
Creates the server notices room, if none exists.
|
||||
|
@ -54,9 +61,11 @@ class ServerNoticesManager(object):
|
|||
Args:
|
||||
user_id (str): mxid of user to send event to.
|
||||
event_content (dict): content of event to send
|
||||
type(EventTypes): type of event
|
||||
is_state_event(bool): Is the event a state event
|
||||
|
||||
Returns:
|
||||
Deferred[None]
|
||||
Deferred[FrozenEvent]
|
||||
"""
|
||||
room_id = yield self.get_notice_room_for_user(user_id)
|
||||
|
||||
|
@ -65,15 +74,20 @@ class ServerNoticesManager(object):
|
|||
|
||||
logger.info("Sending server notice to %s", user_id)
|
||||
|
||||
yield self._event_creation_handler.create_and_send_nonmember_event(
|
||||
requester, {
|
||||
"type": EventTypes.Message,
|
||||
"room_id": room_id,
|
||||
"sender": system_mxid,
|
||||
"content": event_content,
|
||||
},
|
||||
ratelimit=False,
|
||||
event_dict = {
|
||||
"type": type,
|
||||
"room_id": room_id,
|
||||
"sender": system_mxid,
|
||||
"content": event_content,
|
||||
}
|
||||
|
||||
if state_key is not None:
|
||||
event_dict['state_key'] = state_key
|
||||
|
||||
res = yield self._event_creation_handler.create_and_send_nonmember_event(
|
||||
requester, event_dict, ratelimit=False,
|
||||
)
|
||||
defer.returnValue(res)
|
||||
|
||||
@cachedInlineCallbacks()
|
||||
def get_notice_room_for_user(self, user_id):
|
||||
|
@ -142,5 +156,12 @@ class ServerNoticesManager(object):
|
|||
)
|
||||
room_id = info['room_id']
|
||||
|
||||
max_id = yield self._store.add_tag_to_room(
|
||||
user_id, room_id, SERVER_NOTICE_ROOM_TAG, {},
|
||||
)
|
||||
self._notifier.on_new_event(
|
||||
"account_data_key", max_id, users=[user_id]
|
||||
)
|
||||
|
||||
logger.info("Created server notices room %s for %s", room_id, user_id)
|
||||
defer.returnValue(room_id)
|
||||
|
|
|
@ -12,7 +12,12 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.server_notices.consent_server_notices import ConsentServerNotices
|
||||
from synapse.server_notices.resource_limits_server_notices import (
|
||||
ResourceLimitsServerNotices,
|
||||
)
|
||||
|
||||
|
||||
class ServerNoticesSender(object):
|
||||
|
@ -25,34 +30,34 @@ class ServerNoticesSender(object):
|
|||
Args:
|
||||
hs (synapse.server.HomeServer):
|
||||
"""
|
||||
# todo: it would be nice to make this more dynamic
|
||||
self._consent_server_notices = ConsentServerNotices(hs)
|
||||
self._server_notices = (
|
||||
ConsentServerNotices(hs),
|
||||
ResourceLimitsServerNotices(hs)
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_user_syncing(self, user_id):
|
||||
"""Called when the user performs a sync operation.
|
||||
|
||||
Args:
|
||||
user_id (str): mxid of user who synced
|
||||
|
||||
Returns:
|
||||
Deferred
|
||||
"""
|
||||
return self._consent_server_notices.maybe_send_server_notice_to_user(
|
||||
user_id,
|
||||
)
|
||||
for sn in self._server_notices:
|
||||
yield sn.maybe_send_server_notice_to_user(
|
||||
user_id,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_user_ip(self, user_id):
|
||||
"""Called on the master when a worker process saw a client request.
|
||||
|
||||
Args:
|
||||
user_id (str): mxid
|
||||
|
||||
Returns:
|
||||
Deferred
|
||||
"""
|
||||
# The synchrotrons use a stubbed version of ServerNoticesSender, so
|
||||
# we check for notices to send to the user in on_user_ip as well as
|
||||
# in on_user_syncing
|
||||
return self._consent_server_notices.maybe_send_server_notice_to_user(
|
||||
user_id,
|
||||
)
|
||||
for sn in self._server_notices:
|
||||
yield sn.maybe_send_server_notice_to_user(
|
||||
user_id,
|
||||
)
|
||||
|
|
|
@ -385,6 +385,7 @@ class StateHandler(object):
|
|||
ev_ids, get_prev_content=False, check_redacted=False,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def resolve_events(self, room_version, state_sets, event):
|
||||
logger.info(
|
||||
"Resolving state for %s with %d groups", event.room_id, len(state_sets)
|
||||
|
@ -401,15 +402,17 @@ class StateHandler(object):
|
|||
}
|
||||
|
||||
with Measure(self.clock, "state._resolve_events"):
|
||||
new_state = resolve_events_with_state_map(
|
||||
room_version, state_set_ids, state_map,
|
||||
new_state = yield resolve_events_with_factory(
|
||||
room_version, state_set_ids,
|
||||
event_map=state_map,
|
||||
state_map_factory=self._state_map_factory
|
||||
)
|
||||
|
||||
new_state = {
|
||||
key: state_map[ev_id] for key, ev_id in iteritems(new_state)
|
||||
}
|
||||
|
||||
return new_state
|
||||
defer.returnValue(new_state)
|
||||
|
||||
|
||||
class StateResolutionHandler(object):
|
||||
|
@ -589,31 +592,6 @@ def _make_state_cache_entry(
|
|||
)
|
||||
|
||||
|
||||
def resolve_events_with_state_map(room_version, state_sets, state_map):
|
||||
"""
|
||||
Args:
|
||||
room_version(str): Version of the room
|
||||
state_sets(list): List of dicts of (type, state_key) -> event_id,
|
||||
which are the different state groups to resolve.
|
||||
state_map(dict): a dict from event_id to event, for all events in
|
||||
state_sets.
|
||||
|
||||
Returns
|
||||
dict[(str, str), str]:
|
||||
a map from (type, state_key) to event_id.
|
||||
"""
|
||||
if room_version in (RoomVersions.V1, RoomVersions.VDH_TEST,):
|
||||
return v1.resolve_events_with_state_map(
|
||||
state_sets, state_map,
|
||||
)
|
||||
else:
|
||||
# This should only happen if we added a version but forgot to add it to
|
||||
# the list above.
|
||||
raise Exception(
|
||||
"No state resolution algorithm defined for version %r" % (room_version,)
|
||||
)
|
||||
|
||||
|
||||
def resolve_events_with_factory(room_version, state_sets, event_map, state_map_factory):
|
||||
"""
|
||||
Args:
|
||||
|
|
|
@ -30,34 +30,6 @@ logger = logging.getLogger(__name__)
|
|||
POWER_KEY = (EventTypes.PowerLevels, "")
|
||||
|
||||
|
||||
def resolve_events_with_state_map(state_sets, state_map):
|
||||
"""
|
||||
Args:
|
||||
state_sets(list): List of dicts of (type, state_key) -> event_id,
|
||||
which are the different state groups to resolve.
|
||||
state_map(dict): a dict from event_id to event, for all events in
|
||||
state_sets.
|
||||
|
||||
Returns
|
||||
dict[(str, str), str]:
|
||||
a map from (type, state_key) to event_id.
|
||||
"""
|
||||
if len(state_sets) == 1:
|
||||
return state_sets[0]
|
||||
|
||||
unconflicted_state, conflicted_state = _seperate(
|
||||
state_sets,
|
||||
)
|
||||
|
||||
auth_events = _create_auth_events_from_maps(
|
||||
unconflicted_state, conflicted_state, state_map
|
||||
)
|
||||
|
||||
return _resolve_with_state(
|
||||
unconflicted_state, conflicted_state, auth_events, state_map
|
||||
)
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def resolve_events_with_factory(state_sets, event_map, state_map_factory):
|
||||
"""
|
||||
|
|
|
@ -17,9 +17,10 @@ import sys
|
|||
import threading
|
||||
import time
|
||||
|
||||
from six import iteritems, iterkeys, itervalues
|
||||
from six import PY2, iteritems, iterkeys, itervalues
|
||||
from six.moves import intern, range
|
||||
|
||||
from canonicaljson import json
|
||||
from prometheus_client import Histogram
|
||||
|
||||
from twisted.internet import defer
|
||||
|
@ -1216,3 +1217,32 @@ class _RollbackButIsFineException(Exception):
|
|||
something went wrong.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def db_to_json(db_content):
|
||||
"""
|
||||
Take some data from a database row and return a JSON-decoded object.
|
||||
|
||||
Args:
|
||||
db_content (memoryview|buffer|bytes|bytearray|unicode)
|
||||
"""
|
||||
# psycopg2 on Python 3 returns memoryview objects, which we need to
|
||||
# cast to bytes to decode
|
||||
if isinstance(db_content, memoryview):
|
||||
db_content = db_content.tobytes()
|
||||
|
||||
# psycopg2 on Python 2 returns buffer objects, which we need to cast to
|
||||
# bytes to decode
|
||||
if PY2 and isinstance(db_content, buffer):
|
||||
db_content = bytes(db_content)
|
||||
|
||||
# Decode it to a Unicode string before feeding it to json.loads, so we
|
||||
# consistenty get a Unicode-containing object out.
|
||||
if isinstance(db_content, (bytes, bytearray)):
|
||||
db_content = db_content.decode('utf8')
|
||||
|
||||
try:
|
||||
return json.loads(db_content)
|
||||
except Exception:
|
||||
logging.warning("Tried to decode '%r' as JSON and failed", db_content)
|
||||
raise
|
||||
|
|
|
@ -169,7 +169,7 @@ class DeviceInboxStore(BackgroundUpdateStore):
|
|||
local_by_user_then_device = {}
|
||||
for user_id, messages_by_device in messages_by_user_then_device.items():
|
||||
messages_json_for_user = {}
|
||||
devices = messages_by_device.keys()
|
||||
devices = list(messages_by_device.keys())
|
||||
if len(devices) == 1 and devices[0] == "*":
|
||||
# Handle wildcard device_ids.
|
||||
sql = (
|
||||
|
|
|
@ -24,7 +24,7 @@ from synapse.api.errors import StoreError
|
|||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.util.caches.descriptors import cached, cachedInlineCallbacks, cachedList
|
||||
|
||||
from ._base import Cache, SQLBaseStore
|
||||
from ._base import Cache, SQLBaseStore, db_to_json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -411,7 +411,7 @@ class DeviceStore(SQLBaseStore):
|
|||
if device is not None:
|
||||
key_json = device.get("key_json", None)
|
||||
if key_json:
|
||||
result["keys"] = json.loads(key_json)
|
||||
result["keys"] = db_to_json(key_json)
|
||||
device_display_name = device.get("device_display_name", None)
|
||||
if device_display_name:
|
||||
result["device_display_name"] = device_display_name
|
||||
|
@ -466,7 +466,7 @@ class DeviceStore(SQLBaseStore):
|
|||
retcol="content",
|
||||
desc="_get_cached_user_device",
|
||||
)
|
||||
defer.returnValue(json.loads(content))
|
||||
defer.returnValue(db_to_json(content))
|
||||
|
||||
@cachedInlineCallbacks()
|
||||
def _get_cached_devices_for_user(self, user_id):
|
||||
|
@ -479,7 +479,7 @@ class DeviceStore(SQLBaseStore):
|
|||
desc="_get_cached_devices_for_user",
|
||||
)
|
||||
defer.returnValue({
|
||||
device["device_id"]: json.loads(device["content"])
|
||||
device["device_id"]: db_to_json(device["content"])
|
||||
for device in devices
|
||||
})
|
||||
|
||||
|
@ -511,7 +511,7 @@ class DeviceStore(SQLBaseStore):
|
|||
|
||||
key_json = device.get("key_json", None)
|
||||
if key_json:
|
||||
result["keys"] = json.loads(key_json)
|
||||
result["keys"] = db_to_json(key_json)
|
||||
device_display_name = device.get("device_display_name", None)
|
||||
if device_display_name:
|
||||
result["device_display_name"] = device_display_name
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
# limitations under the License.
|
||||
from six import iteritems
|
||||
|
||||
from canonicaljson import encode_canonical_json, json
|
||||
from canonicaljson import encode_canonical_json
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.util.caches.descriptors import cached
|
||||
|
||||
from ._base import SQLBaseStore
|
||||
from ._base import SQLBaseStore, db_to_json
|
||||
|
||||
|
||||
class EndToEndKeyStore(SQLBaseStore):
|
||||
|
@ -90,7 +90,7 @@ class EndToEndKeyStore(SQLBaseStore):
|
|||
|
||||
for user_id, device_keys in iteritems(results):
|
||||
for device_id, device_info in iteritems(device_keys):
|
||||
device_info["keys"] = json.loads(device_info.pop("key_json"))
|
||||
device_info["keys"] = db_to_json(device_info.pop("key_json"))
|
||||
|
||||
defer.returnValue(results)
|
||||
|
||||
|
|
|
@ -41,13 +41,18 @@ class PostgresEngine(object):
|
|||
db_conn.set_isolation_level(
|
||||
self.module.extensions.ISOLATION_LEVEL_REPEATABLE_READ
|
||||
)
|
||||
|
||||
# Set the bytea output to escape, vs the default of hex
|
||||
cursor = db_conn.cursor()
|
||||
cursor.execute("SET bytea_output TO escape")
|
||||
|
||||
# Asynchronous commit, don't wait for the server to call fsync before
|
||||
# ending the transaction.
|
||||
# https://www.postgresql.org/docs/current/static/wal-async-commit.html
|
||||
if not self.synchronous_commit:
|
||||
cursor = db_conn.cursor()
|
||||
cursor.execute("SET synchronous_commit TO OFF")
|
||||
cursor.close()
|
||||
|
||||
cursor.close()
|
||||
|
||||
def is_deadlock(self, error):
|
||||
if isinstance(error, self.module.DatabaseError):
|
||||
|
|
|
@ -19,7 +19,7 @@ import logging
|
|||
from collections import OrderedDict, deque, namedtuple
|
||||
from functools import wraps
|
||||
|
||||
from six import iteritems
|
||||
from six import iteritems, text_type
|
||||
from six.moves import range
|
||||
|
||||
from canonicaljson import json
|
||||
|
@ -1220,7 +1220,7 @@ class EventsStore(EventFederationStore, EventsWorkerStore, BackgroundUpdateStore
|
|||
"sender": event.sender,
|
||||
"contains_url": (
|
||||
"url" in event.content
|
||||
and isinstance(event.content["url"], basestring)
|
||||
and isinstance(event.content["url"], text_type)
|
||||
),
|
||||
}
|
||||
for event, _ in events_and_contexts
|
||||
|
@ -1529,7 +1529,7 @@ class EventsStore(EventFederationStore, EventsWorkerStore, BackgroundUpdateStore
|
|||
|
||||
contains_url = "url" in content
|
||||
if contains_url:
|
||||
contains_url &= isinstance(content["url"], basestring)
|
||||
contains_url &= isinstance(content["url"], text_type)
|
||||
except (KeyError, AttributeError):
|
||||
# If the event is missing a necessary field then
|
||||
# skip over it.
|
||||
|
@ -1910,9 +1910,9 @@ class EventsStore(EventFederationStore, EventsWorkerStore, BackgroundUpdateStore
|
|||
(room_id,)
|
||||
)
|
||||
rows = txn.fetchall()
|
||||
max_depth = max(row[0] for row in rows)
|
||||
max_depth = max(row[1] for row in rows)
|
||||
|
||||
if max_depth <= token.topological:
|
||||
if max_depth < token.topological:
|
||||
# We need to ensure we don't delete all the events from the database
|
||||
# otherwise we wouldn't be able to send any events (due to not
|
||||
# having any backwards extremeties)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
@ -265,7 +266,7 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
"""
|
||||
with Measure(self._clock, "_fetch_event_list"):
|
||||
try:
|
||||
event_id_lists = zip(*event_list)[0]
|
||||
event_id_lists = list(zip(*event_list))[0]
|
||||
event_ids = [
|
||||
item for sublist in event_id_lists for item in sublist
|
||||
]
|
||||
|
@ -299,14 +300,14 @@ class EventsWorkerStore(SQLBaseStore):
|
|||
logger.exception("do_fetch")
|
||||
|
||||
# We only want to resolve deferreds from the main thread
|
||||
def fire(evs):
|
||||
def fire(evs, exc):
|
||||
for _, d in evs:
|
||||
if not d.called:
|
||||
with PreserveLoggingContext():
|
||||
d.errback(e)
|
||||
d.errback(exc)
|
||||
|
||||
with PreserveLoggingContext():
|
||||
self.hs.get_reactor().callFromThread(fire, event_list)
|
||||
self.hs.get_reactor().callFromThread(fire, event_list, e)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _enqueue_events(self, events, check_redacted=True, allow_rejected=False):
|
||||
|
|
|
@ -13,14 +13,14 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from canonicaljson import encode_canonical_json, json
|
||||
from canonicaljson import encode_canonical_json
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.util.caches.descriptors import cachedInlineCallbacks
|
||||
|
||||
from ._base import SQLBaseStore
|
||||
from ._base import SQLBaseStore, db_to_json
|
||||
|
||||
|
||||
class FilteringStore(SQLBaseStore):
|
||||
|
@ -44,7 +44,7 @@ class FilteringStore(SQLBaseStore):
|
|||
desc="get_user_filter",
|
||||
)
|
||||
|
||||
defer.returnValue(json.loads(bytes(def_json).decode("utf-8")))
|
||||
defer.returnValue(db_to_json(def_json))
|
||||
|
||||
def add_user_filter(self, user_localpart, user_filter):
|
||||
def_json = encode_canonical_json(user_filter)
|
||||
|
|
|
@ -36,7 +36,6 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
|
||||
@defer.inlineCallbacks
|
||||
def initialise_reserved_users(self, threepids):
|
||||
# TODO Why can't I do this in init?
|
||||
store = self.hs.get_datastore()
|
||||
reserved_user_list = []
|
||||
|
||||
|
@ -147,6 +146,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
return count
|
||||
return self.runInteraction("count_users", _count_users)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def upsert_monthly_active_user(self, user_id):
|
||||
"""
|
||||
Updates or inserts monthly active user member
|
||||
|
@ -155,7 +155,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
Deferred[bool]: True if a new entry was created, False if an
|
||||
existing one was updated.
|
||||
"""
|
||||
is_insert = self._simple_upsert(
|
||||
is_insert = yield self._simple_upsert(
|
||||
desc="upsert_monthly_active_user",
|
||||
table="monthly_active_users",
|
||||
keyvalues={
|
||||
|
@ -199,7 +199,16 @@ class MonthlyActiveUsersStore(SQLBaseStore):
|
|||
Args:
|
||||
user_id(str): the user_id to query
|
||||
"""
|
||||
|
||||
if self.hs.config.limit_usage_by_mau:
|
||||
# Trial users and guests should not be included as part of MAU group
|
||||
is_guest = yield self.is_guest(user_id)
|
||||
if is_guest:
|
||||
return
|
||||
is_trial = yield self.is_trial_user(user_id)
|
||||
if is_trial:
|
||||
return
|
||||
|
||||
last_seen_timestamp = yield self.user_last_seen_monthly_active(user_id)
|
||||
now = self.hs.get_clock().time_msec()
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import types
|
||||
|
||||
import six
|
||||
|
||||
from canonicaljson import encode_canonical_json, json
|
||||
|
||||
|
@ -27,6 +28,11 @@ from ._base import SQLBaseStore
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if six.PY2:
|
||||
db_binary_type = buffer
|
||||
else:
|
||||
db_binary_type = memoryview
|
||||
|
||||
|
||||
class PusherWorkerStore(SQLBaseStore):
|
||||
def _decode_pushers_rows(self, rows):
|
||||
|
@ -34,18 +40,18 @@ class PusherWorkerStore(SQLBaseStore):
|
|||
dataJson = r['data']
|
||||
r['data'] = None
|
||||
try:
|
||||
if isinstance(dataJson, types.BufferType):
|
||||
if isinstance(dataJson, db_binary_type):
|
||||
dataJson = str(dataJson).decode("UTF8")
|
||||
|
||||
r['data'] = json.loads(dataJson)
|
||||
except Exception as e:
|
||||
logger.warn(
|
||||
"Invalid JSON in data for pusher %d: %s, %s",
|
||||
r['id'], dataJson, e.message,
|
||||
r['id'], dataJson, e.args[0],
|
||||
)
|
||||
pass
|
||||
|
||||
if isinstance(r['pushkey'], types.BufferType):
|
||||
if isinstance(r['pushkey'], db_binary_type):
|
||||
r['pushkey'] = str(r['pushkey']).decode("UTF8")
|
||||
|
||||
return rows
|
||||
|
|
|
@ -26,6 +26,11 @@ from synapse.util.caches.descriptors import cached, cachedInlineCallbacks
|
|||
|
||||
|
||||
class RegistrationWorkerStore(SQLBaseStore):
|
||||
def __init__(self, db_conn, hs):
|
||||
super(RegistrationWorkerStore, self).__init__(db_conn, hs)
|
||||
|
||||
self.config = hs.config
|
||||
|
||||
@cached()
|
||||
def get_user_by_id(self, user_id):
|
||||
return self._simple_select_one(
|
||||
|
@ -36,12 +41,33 @@ class RegistrationWorkerStore(SQLBaseStore):
|
|||
retcols=[
|
||||
"name", "password_hash", "is_guest",
|
||||
"consent_version", "consent_server_notice_sent",
|
||||
"appservice_id",
|
||||
"appservice_id", "creation_ts",
|
||||
],
|
||||
allow_none=True,
|
||||
desc="get_user_by_id",
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def is_trial_user(self, user_id):
|
||||
"""Checks if user is in the "trial" period, i.e. within the first
|
||||
N days of registration defined by `mau_trial_days` config
|
||||
|
||||
Args:
|
||||
user_id (str)
|
||||
|
||||
Returns:
|
||||
Deferred[bool]
|
||||
"""
|
||||
|
||||
info = yield self.get_user_by_id(user_id)
|
||||
if not info:
|
||||
defer.returnValue(False)
|
||||
|
||||
now = self.clock.time_msec()
|
||||
trial_duration_ms = self.config.mau_trial_days * 24 * 60 * 60 * 1000
|
||||
is_trial = (now - info["creation_ts"] * 1000) < trial_duration_ms
|
||||
defer.returnValue(is_trial)
|
||||
|
||||
@cached()
|
||||
def get_user_by_access_token(self, token):
|
||||
"""Get a user from the given access token.
|
||||
|
|
|
@ -18,14 +18,14 @@ from collections import namedtuple
|
|||
|
||||
import six
|
||||
|
||||
from canonicaljson import encode_canonical_json, json
|
||||
from canonicaljson import encode_canonical_json
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.util.caches.descriptors import cached
|
||||
|
||||
from ._base import SQLBaseStore
|
||||
from ._base import SQLBaseStore, db_to_json
|
||||
|
||||
# py2 sqlite has buffer hardcoded as only binary type, so we must use it,
|
||||
# despite being deprecated and removed in favor of memoryview
|
||||
|
@ -95,7 +95,8 @@ class TransactionStore(SQLBaseStore):
|
|||
)
|
||||
|
||||
if result and result["response_code"]:
|
||||
return result["response_code"], json.loads(str(result["response_json"]))
|
||||
return result["response_code"], db_to_json(result["response_json"])
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
|
@ -457,8 +457,8 @@ class AuthTestCase(unittest.TestCase):
|
|||
|
||||
with self.assertRaises(ResourceLimitError) as e:
|
||||
yield self.auth.check_auth_blocking()
|
||||
self.assertEquals(e.exception.admin_uri, self.hs.config.admin_uri)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEED)
|
||||
self.assertEquals(e.exception.admin_contact, self.hs.config.admin_contact)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
|
||||
self.assertEquals(e.exception.code, 403)
|
||||
|
||||
# Ensure does not throw an error
|
||||
|
@ -467,12 +467,38 @@ class AuthTestCase(unittest.TestCase):
|
|||
)
|
||||
yield self.auth.check_auth_blocking()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_reserved_threepid(self):
|
||||
self.hs.config.limit_usage_by_mau = True
|
||||
self.hs.config.max_mau_value = 1
|
||||
self.store.get_monthly_active_count = lambda: defer.succeed(2)
|
||||
threepid = {'medium': 'email', 'address': 'reserved@server.com'}
|
||||
unknown_threepid = {'medium': 'email', 'address': 'unreserved@server.com'}
|
||||
self.hs.config.mau_limits_reserved_threepids = [threepid]
|
||||
|
||||
yield self.store.register(user_id='user1', token="123", password_hash=None)
|
||||
with self.assertRaises(ResourceLimitError):
|
||||
yield self.auth.check_auth_blocking()
|
||||
|
||||
with self.assertRaises(ResourceLimitError):
|
||||
yield self.auth.check_auth_blocking(threepid=unknown_threepid)
|
||||
|
||||
yield self.auth.check_auth_blocking(threepid=threepid)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_hs_disabled(self):
|
||||
self.hs.config.hs_disabled = True
|
||||
self.hs.config.hs_disabled_message = "Reason for being disabled"
|
||||
with self.assertRaises(ResourceLimitError) as e:
|
||||
yield self.auth.check_auth_blocking()
|
||||
self.assertEquals(e.exception.admin_uri, self.hs.config.admin_uri)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEED)
|
||||
self.assertEquals(e.exception.admin_contact, self.hs.config.admin_contact)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
|
||||
self.assertEquals(e.exception.code, 403)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_server_notices_mxid_special_cased(self):
|
||||
self.hs.config.hs_disabled = True
|
||||
user = "@user:server"
|
||||
self.hs.config.server_notices_mxid = user
|
||||
self.hs.config.hs_disabled_message = "Reason for being disabled"
|
||||
yield self.auth.check_auth_blocking(user)
|
||||
|
|
|
@ -47,7 +47,7 @@ class FrontendProxyTests(HomeserverTestCase):
|
|||
self.assertEqual(len(self.reactor.tcpServers), 1)
|
||||
site = self.reactor.tcpServers[0][1]
|
||||
self.resource = (
|
||||
site.resource.children["_matrix"].children["client"].children["r0"]
|
||||
site.resource.children[b"_matrix"].children[b"client"].children[b"r0"]
|
||||
)
|
||||
|
||||
request, channel = self.make_request("PUT", "presence/a/status")
|
||||
|
@ -77,7 +77,7 @@ class FrontendProxyTests(HomeserverTestCase):
|
|||
self.assertEqual(len(self.reactor.tcpServers), 1)
|
||||
site = self.reactor.tcpServers[0][1]
|
||||
self.resource = (
|
||||
site.resource.children["_matrix"].children["client"].children["r0"]
|
||||
site.resource.children[b"_matrix"].children[b"client"].children[b"r0"]
|
||||
)
|
||||
|
||||
request, channel = self.make_request("PUT", "presence/a/status")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2016 OpenMarket Ltd
|
||||
# Copyright 2018 New Vector Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,79 +14,79 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
import synapse.api.errors
|
||||
import synapse.handlers.device
|
||||
import synapse.storage
|
||||
|
||||
from tests import unittest, utils
|
||||
from tests import unittest
|
||||
|
||||
user1 = "@boris:aaa"
|
||||
user2 = "@theresa:bbb"
|
||||
|
||||
|
||||
class DeviceTestCase(unittest.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeviceTestCase, self).__init__(*args, **kwargs)
|
||||
self.store = None # type: synapse.storage.DataStore
|
||||
self.handler = None # type: synapse.handlers.device.DeviceHandler
|
||||
self.clock = None # type: utils.MockClock
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def setUp(self):
|
||||
hs = yield utils.setup_test_homeserver(self.addCleanup)
|
||||
class DeviceTestCase(unittest.HomeserverTestCase):
|
||||
def make_homeserver(self, reactor, clock):
|
||||
hs = self.setup_test_homeserver("server", http_client=None)
|
||||
self.handler = hs.get_device_handler()
|
||||
self.store = hs.get_datastore()
|
||||
self.clock = hs.get_clock()
|
||||
return hs
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
# These tests assume that it starts 1000 seconds in.
|
||||
self.reactor.advance(1000)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_device_is_created_if_doesnt_exist(self):
|
||||
res = yield self.handler.check_device_registered(
|
||||
user_id="@boris:foo",
|
||||
device_id="fco",
|
||||
initial_device_display_name="display name",
|
||||
res = self.get_success(
|
||||
self.handler.check_device_registered(
|
||||
user_id="@boris:foo",
|
||||
device_id="fco",
|
||||
initial_device_display_name="display name",
|
||||
)
|
||||
)
|
||||
self.assertEqual(res, "fco")
|
||||
|
||||
dev = yield self.handler.store.get_device("@boris:foo", "fco")
|
||||
dev = self.get_success(self.handler.store.get_device("@boris:foo", "fco"))
|
||||
self.assertEqual(dev["display_name"], "display name")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_device_is_preserved_if_exists(self):
|
||||
res1 = yield self.handler.check_device_registered(
|
||||
user_id="@boris:foo",
|
||||
device_id="fco",
|
||||
initial_device_display_name="display name",
|
||||
res1 = self.get_success(
|
||||
self.handler.check_device_registered(
|
||||
user_id="@boris:foo",
|
||||
device_id="fco",
|
||||
initial_device_display_name="display name",
|
||||
)
|
||||
)
|
||||
self.assertEqual(res1, "fco")
|
||||
|
||||
res2 = yield self.handler.check_device_registered(
|
||||
user_id="@boris:foo",
|
||||
device_id="fco",
|
||||
initial_device_display_name="new display name",
|
||||
res2 = self.get_success(
|
||||
self.handler.check_device_registered(
|
||||
user_id="@boris:foo",
|
||||
device_id="fco",
|
||||
initial_device_display_name="new display name",
|
||||
)
|
||||
)
|
||||
self.assertEqual(res2, "fco")
|
||||
|
||||
dev = yield self.handler.store.get_device("@boris:foo", "fco")
|
||||
dev = self.get_success(self.handler.store.get_device("@boris:foo", "fco"))
|
||||
self.assertEqual(dev["display_name"], "display name")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_device_id_is_made_up_if_unspecified(self):
|
||||
device_id = yield self.handler.check_device_registered(
|
||||
user_id="@theresa:foo",
|
||||
device_id=None,
|
||||
initial_device_display_name="display",
|
||||
device_id = self.get_success(
|
||||
self.handler.check_device_registered(
|
||||
user_id="@theresa:foo",
|
||||
device_id=None,
|
||||
initial_device_display_name="display",
|
||||
)
|
||||
)
|
||||
|
||||
dev = yield self.handler.store.get_device("@theresa:foo", device_id)
|
||||
dev = self.get_success(self.handler.store.get_device("@theresa:foo", device_id))
|
||||
self.assertEqual(dev["display_name"], "display")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_get_devices_by_user(self):
|
||||
yield self._record_users()
|
||||
self._record_users()
|
||||
|
||||
res = self.get_success(self.handler.get_devices_by_user(user1))
|
||||
|
||||
res = yield self.handler.get_devices_by_user(user1)
|
||||
self.assertEqual(3, len(res))
|
||||
device_map = {d["device_id"]: d for d in res}
|
||||
self.assertDictContainsSubset(
|
||||
|
@ -119,11 +120,10 @@ class DeviceTestCase(unittest.TestCase):
|
|||
device_map["abc"],
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_get_device(self):
|
||||
yield self._record_users()
|
||||
self._record_users()
|
||||
|
||||
res = yield self.handler.get_device(user1, "abc")
|
||||
res = self.get_success(self.handler.get_device(user1, "abc"))
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"user_id": user1,
|
||||
|
@ -135,59 +135,66 @@ class DeviceTestCase(unittest.TestCase):
|
|||
res,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_delete_device(self):
|
||||
yield self._record_users()
|
||||
self._record_users()
|
||||
|
||||
# delete the device
|
||||
yield self.handler.delete_device(user1, "abc")
|
||||
self.get_success(self.handler.delete_device(user1, "abc"))
|
||||
|
||||
# check the device was deleted
|
||||
with self.assertRaises(synapse.api.errors.NotFoundError):
|
||||
yield self.handler.get_device(user1, "abc")
|
||||
res = self.handler.get_device(user1, "abc")
|
||||
self.pump()
|
||||
self.assertIsInstance(
|
||||
self.failureResultOf(res).value, synapse.api.errors.NotFoundError
|
||||
)
|
||||
|
||||
# we'd like to check the access token was invalidated, but that's a
|
||||
# bit of a PITA.
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_update_device(self):
|
||||
yield self._record_users()
|
||||
self._record_users()
|
||||
|
||||
update = {"display_name": "new display"}
|
||||
yield self.handler.update_device(user1, "abc", update)
|
||||
self.get_success(self.handler.update_device(user1, "abc", update))
|
||||
|
||||
res = yield self.handler.get_device(user1, "abc")
|
||||
res = self.get_success(self.handler.get_device(user1, "abc"))
|
||||
self.assertEqual(res["display_name"], "new display")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_update_unknown_device(self):
|
||||
update = {"display_name": "new_display"}
|
||||
with self.assertRaises(synapse.api.errors.NotFoundError):
|
||||
yield self.handler.update_device("user_id", "unknown_device_id", update)
|
||||
res = self.handler.update_device("user_id", "unknown_device_id", update)
|
||||
self.pump()
|
||||
self.assertIsInstance(
|
||||
self.failureResultOf(res).value, synapse.api.errors.NotFoundError
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _record_users(self):
|
||||
# check this works for both devices which have a recorded client_ip,
|
||||
# and those which don't.
|
||||
yield self._record_user(user1, "xyz", "display 0")
|
||||
yield self._record_user(user1, "fco", "display 1", "token1", "ip1")
|
||||
yield self._record_user(user1, "abc", "display 2", "token2", "ip2")
|
||||
yield self._record_user(user1, "abc", "display 2", "token3", "ip3")
|
||||
self._record_user(user1, "xyz", "display 0")
|
||||
self._record_user(user1, "fco", "display 1", "token1", "ip1")
|
||||
self._record_user(user1, "abc", "display 2", "token2", "ip2")
|
||||
self._record_user(user1, "abc", "display 2", "token3", "ip3")
|
||||
|
||||
yield self._record_user(user2, "def", "dispkay", "token4", "ip4")
|
||||
self._record_user(user2, "def", "dispkay", "token4", "ip4")
|
||||
|
||||
self.reactor.advance(10000)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _record_user(
|
||||
self, user_id, device_id, display_name, access_token=None, ip=None
|
||||
):
|
||||
device_id = yield self.handler.check_device_registered(
|
||||
user_id=user_id,
|
||||
device_id=device_id,
|
||||
initial_device_display_name=display_name,
|
||||
device_id = self.get_success(
|
||||
self.handler.check_device_registered(
|
||||
user_id=user_id,
|
||||
device_id=device_id,
|
||||
initial_device_display_name=display_name,
|
||||
)
|
||||
)
|
||||
|
||||
if ip is not None:
|
||||
yield self.store.insert_client_ip(
|
||||
user_id, access_token, ip, "user_agent", device_id
|
||||
self.get_success(
|
||||
self.store.insert_client_ip(
|
||||
user_id, access_token, ip, "user_agent", device_id
|
||||
)
|
||||
)
|
||||
self.clock.advance_time(1000)
|
||||
self.reactor.advance(1000)
|
||||
|
|
|
@ -51,7 +51,7 @@ class SyncTestCase(tests.unittest.TestCase):
|
|||
self.hs.config.hs_disabled = True
|
||||
with self.assertRaises(ResourceLimitError) as e:
|
||||
yield self.sync_handler.wait_for_sync_for_user(sync_config)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEED)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
|
||||
|
||||
self.hs.config.hs_disabled = False
|
||||
|
||||
|
@ -59,7 +59,7 @@ class SyncTestCase(tests.unittest.TestCase):
|
|||
|
||||
with self.assertRaises(ResourceLimitError) as e:
|
||||
yield self.sync_handler.wait_for_sync_for_user(sync_config)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEED)
|
||||
self.assertEquals(e.exception.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)
|
||||
|
||||
def _generate_sync_config(self, user_id):
|
||||
return SyncConfig(
|
||||
|
|
|
@ -33,7 +33,7 @@ from ..utils import (
|
|||
)
|
||||
|
||||
|
||||
def _expect_edu(destination, edu_type, content, origin="test"):
|
||||
def _expect_edu_transaction(edu_type, content, origin="test"):
|
||||
return {
|
||||
"origin": origin,
|
||||
"origin_server_ts": 1000000,
|
||||
|
@ -42,10 +42,8 @@ def _expect_edu(destination, edu_type, content, origin="test"):
|
|||
}
|
||||
|
||||
|
||||
def _make_edu_json(origin, edu_type, content):
|
||||
return json.dumps(_expect_edu("test", edu_type, content, origin=origin)).encode(
|
||||
'utf8'
|
||||
)
|
||||
def _make_edu_transaction_json(edu_type, content):
|
||||
return json.dumps(_expect_edu_transaction(edu_type, content)).encode('utf8')
|
||||
|
||||
|
||||
class TypingNotificationsTestCase(unittest.TestCase):
|
||||
|
@ -190,8 +188,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||
call(
|
||||
"farm",
|
||||
path="/_matrix/federation/v1/send/1000000/",
|
||||
data=_expect_edu(
|
||||
"farm",
|
||||
data=_expect_edu_transaction(
|
||||
"m.typing",
|
||||
content={
|
||||
"room_id": self.room_id,
|
||||
|
@ -221,11 +218,10 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||
|
||||
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||
|
||||
yield self.mock_federation_resource.trigger(
|
||||
(code, response) = yield self.mock_federation_resource.trigger(
|
||||
"PUT",
|
||||
"/_matrix/federation/v1/send/1000000/",
|
||||
_make_edu_json(
|
||||
"farm",
|
||||
_make_edu_transaction_json(
|
||||
"m.typing",
|
||||
content={
|
||||
"room_id": self.room_id,
|
||||
|
@ -233,7 +229,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||
"typing": True,
|
||||
},
|
||||
),
|
||||
federation_auth=True,
|
||||
federation_auth_origin=b'farm',
|
||||
)
|
||||
|
||||
self.on_new_event.assert_has_calls(
|
||||
|
@ -264,8 +260,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
|||
call(
|
||||
"farm",
|
||||
path="/_matrix/federation/v1/send/1000000/",
|
||||
data=_expect_edu(
|
||||
"farm",
|
||||
data=_expect_edu_transaction(
|
||||
"m.typing",
|
||||
content={
|
||||
"room_id": self.room_id,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2016 OpenMarket Ltd
|
||||
# Copyright 2018 New Vector Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -11,89 +12,91 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import tempfile
|
||||
|
||||
from mock import Mock, NonCallableMock
|
||||
|
||||
from twisted.internet import defer, reactor
|
||||
from twisted.internet.defer import Deferred
|
||||
import attr
|
||||
|
||||
from synapse.replication.tcp.client import (
|
||||
ReplicationClientFactory,
|
||||
ReplicationClientHandler,
|
||||
)
|
||||
from synapse.replication.tcp.resource import ReplicationStreamProtocolFactory
|
||||
from synapse.util.logcontext import PreserveLoggingContext, make_deferred_yieldable
|
||||
|
||||
from tests import unittest
|
||||
from tests.utils import setup_test_homeserver
|
||||
|
||||
|
||||
class TestReplicationClientHandler(ReplicationClientHandler):
|
||||
"""Overrides on_rdata so that we can wait for it to happen"""
|
||||
class BaseSlavedStoreTestCase(unittest.HomeserverTestCase):
|
||||
def make_homeserver(self, reactor, clock):
|
||||
|
||||
def __init__(self, store):
|
||||
super(TestReplicationClientHandler, self).__init__(store)
|
||||
self._rdata_awaiters = []
|
||||
|
||||
def await_replication(self):
|
||||
d = Deferred()
|
||||
self._rdata_awaiters.append(d)
|
||||
return make_deferred_yieldable(d)
|
||||
|
||||
def on_rdata(self, stream_name, token, rows):
|
||||
awaiters = self._rdata_awaiters
|
||||
self._rdata_awaiters = []
|
||||
super(TestReplicationClientHandler, self).on_rdata(stream_name, token, rows)
|
||||
with PreserveLoggingContext():
|
||||
for a in awaiters:
|
||||
a.callback(None)
|
||||
|
||||
|
||||
class BaseSlavedStoreTestCase(unittest.TestCase):
|
||||
@defer.inlineCallbacks
|
||||
def setUp(self):
|
||||
self.hs = yield setup_test_homeserver(
|
||||
self.addCleanup,
|
||||
hs = self.setup_test_homeserver(
|
||||
"blue",
|
||||
http_client=None,
|
||||
federation_client=Mock(),
|
||||
ratelimiter=NonCallableMock(spec_set=["send_message"]),
|
||||
)
|
||||
self.hs.get_ratelimiter().send_message.return_value = (True, 0)
|
||||
|
||||
hs.get_ratelimiter().send_message.return_value = (True, 0)
|
||||
|
||||
return hs
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
|
||||
self.master_store = self.hs.get_datastore()
|
||||
self.slaved_store = self.STORE_TYPE(self.hs.get_db_conn(), self.hs)
|
||||
self.event_id = 0
|
||||
|
||||
server_factory = ReplicationStreamProtocolFactory(self.hs)
|
||||
# XXX: mktemp is unsafe and should never be used. but we're just a test.
|
||||
path = tempfile.mktemp(prefix="base_slaved_store_test_case_socket")
|
||||
listener = reactor.listenUNIX(path, server_factory)
|
||||
self.addCleanup(listener.stopListening)
|
||||
self.streamer = server_factory.streamer
|
||||
|
||||
self.replication_handler = TestReplicationClientHandler(self.slaved_store)
|
||||
self.replication_handler = ReplicationClientHandler(self.slaved_store)
|
||||
client_factory = ReplicationClientFactory(
|
||||
self.hs, "client_name", self.replication_handler
|
||||
)
|
||||
client_connector = reactor.connectUNIX(path, client_factory)
|
||||
self.addCleanup(client_factory.stopTrying)
|
||||
self.addCleanup(client_connector.disconnect)
|
||||
|
||||
server = server_factory.buildProtocol(None)
|
||||
client = client_factory.buildProtocol(None)
|
||||
|
||||
@attr.s
|
||||
class FakeTransport(object):
|
||||
|
||||
other = attr.ib()
|
||||
disconnecting = False
|
||||
buffer = attr.ib(default=b'')
|
||||
|
||||
def registerProducer(self, producer, streaming):
|
||||
|
||||
self.producer = producer
|
||||
|
||||
def _produce():
|
||||
self.producer.resumeProducing()
|
||||
reactor.callLater(0.1, _produce)
|
||||
|
||||
reactor.callLater(0.0, _produce)
|
||||
|
||||
def write(self, byt):
|
||||
self.buffer = self.buffer + byt
|
||||
|
||||
if getattr(self.other, "transport") is not None:
|
||||
self.other.dataReceived(self.buffer)
|
||||
self.buffer = b""
|
||||
|
||||
def writeSequence(self, seq):
|
||||
for x in seq:
|
||||
self.write(x)
|
||||
|
||||
client.makeConnection(FakeTransport(server))
|
||||
server.makeConnection(FakeTransport(client))
|
||||
|
||||
def replicate(self):
|
||||
"""Tell the master side of replication that something has happened, and then
|
||||
wait for the replication to occur.
|
||||
"""
|
||||
# xxx: should we be more specific in what we wait for?
|
||||
d = self.replication_handler.await_replication()
|
||||
self.streamer.on_notifier_poke()
|
||||
return d
|
||||
self.pump(0.1)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def check(self, method, args, expected_result=None):
|
||||
master_result = yield getattr(self.master_store, method)(*args)
|
||||
slaved_result = yield getattr(self.slaved_store, method)(*args)
|
||||
master_result = self.get_success(getattr(self.master_store, method)(*args))
|
||||
slaved_result = self.get_success(getattr(self.slaved_store, method)(*args))
|
||||
if expected_result is not None:
|
||||
self.assertEqual(master_result, expected_result)
|
||||
self.assertEqual(slaved_result, expected_result)
|
||||
|
|
|
@ -12,9 +12,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
|
||||
|
||||
from ._base import BaseSlavedStoreTestCase
|
||||
|
@ -27,16 +24,19 @@ class SlavedAccountDataStoreTestCase(BaseSlavedStoreTestCase):
|
|||
|
||||
STORE_TYPE = SlavedAccountDataStore
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_user_account_data(self):
|
||||
yield self.master_store.add_account_data_for_user(USER_ID, TYPE, {"a": 1})
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
self.get_success(
|
||||
self.master_store.add_account_data_for_user(USER_ID, TYPE, {"a": 1})
|
||||
)
|
||||
self.replicate()
|
||||
self.check(
|
||||
"get_global_account_data_by_type_for_user", [TYPE, USER_ID], {"a": 1}
|
||||
)
|
||||
|
||||
yield self.master_store.add_account_data_for_user(USER_ID, TYPE, {"a": 2})
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
self.get_success(
|
||||
self.master_store.add_account_data_for_user(USER_ID, TYPE, {"a": 2})
|
||||
)
|
||||
self.replicate()
|
||||
self.check(
|
||||
"get_global_account_data_by_type_for_user", [TYPE, USER_ID], {"a": 2}
|
||||
)
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.events import FrozenEvent, _EventInternalMetadata
|
||||
from synapse.events.snapshot import EventContext
|
||||
from synapse.replication.slave.storage.events import SlavedEventStore
|
||||
|
@ -55,70 +53,66 @@ class SlavedEventStoreTestCase(BaseSlavedStoreTestCase):
|
|||
def tearDown(self):
|
||||
[unpatch() for unpatch in self.unpatches]
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_get_latest_event_ids_in_room(self):
|
||||
create = yield self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
yield self.replicate()
|
||||
yield self.check("get_latest_event_ids_in_room", (ROOM_ID,), [create.event_id])
|
||||
create = self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
self.replicate()
|
||||
self.check("get_latest_event_ids_in_room", (ROOM_ID,), [create.event_id])
|
||||
|
||||
join = yield self.persist(
|
||||
join = self.persist(
|
||||
type="m.room.member",
|
||||
key=USER_ID,
|
||||
membership="join",
|
||||
prev_events=[(create.event_id, {})],
|
||||
)
|
||||
yield self.replicate()
|
||||
yield self.check("get_latest_event_ids_in_room", (ROOM_ID,), [join.event_id])
|
||||
self.replicate()
|
||||
self.check("get_latest_event_ids_in_room", (ROOM_ID,), [join.event_id])
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_redactions(self):
|
||||
yield self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
yield self.persist(type="m.room.member", key=USER_ID, membership="join")
|
||||
self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
self.persist(type="m.room.member", key=USER_ID, membership="join")
|
||||
|
||||
msg = yield self.persist(type="m.room.message", msgtype="m.text", body="Hello")
|
||||
yield self.replicate()
|
||||
yield self.check("get_event", [msg.event_id], msg)
|
||||
msg = self.persist(type="m.room.message", msgtype="m.text", body="Hello")
|
||||
self.replicate()
|
||||
self.check("get_event", [msg.event_id], msg)
|
||||
|
||||
redaction = yield self.persist(type="m.room.redaction", redacts=msg.event_id)
|
||||
yield self.replicate()
|
||||
redaction = self.persist(type="m.room.redaction", redacts=msg.event_id)
|
||||
self.replicate()
|
||||
|
||||
msg_dict = msg.get_dict()
|
||||
msg_dict["content"] = {}
|
||||
msg_dict["unsigned"]["redacted_by"] = redaction.event_id
|
||||
msg_dict["unsigned"]["redacted_because"] = redaction
|
||||
redacted = FrozenEvent(msg_dict, msg.internal_metadata.get_dict())
|
||||
yield self.check("get_event", [msg.event_id], redacted)
|
||||
self.check("get_event", [msg.event_id], redacted)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_backfilled_redactions(self):
|
||||
yield self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
yield self.persist(type="m.room.member", key=USER_ID, membership="join")
|
||||
self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
self.persist(type="m.room.member", key=USER_ID, membership="join")
|
||||
|
||||
msg = yield self.persist(type="m.room.message", msgtype="m.text", body="Hello")
|
||||
yield self.replicate()
|
||||
yield self.check("get_event", [msg.event_id], msg)
|
||||
msg = self.persist(type="m.room.message", msgtype="m.text", body="Hello")
|
||||
self.replicate()
|
||||
self.check("get_event", [msg.event_id], msg)
|
||||
|
||||
redaction = yield self.persist(
|
||||
redaction = self.persist(
|
||||
type="m.room.redaction", redacts=msg.event_id, backfill=True
|
||||
)
|
||||
yield self.replicate()
|
||||
self.replicate()
|
||||
|
||||
msg_dict = msg.get_dict()
|
||||
msg_dict["content"] = {}
|
||||
msg_dict["unsigned"]["redacted_by"] = redaction.event_id
|
||||
msg_dict["unsigned"]["redacted_because"] = redaction
|
||||
redacted = FrozenEvent(msg_dict, msg.internal_metadata.get_dict())
|
||||
yield self.check("get_event", [msg.event_id], redacted)
|
||||
self.check("get_event", [msg.event_id], redacted)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_invites(self):
|
||||
yield self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
yield self.check("get_invited_rooms_for_user", [USER_ID_2], [])
|
||||
event = yield self.persist(
|
||||
type="m.room.member", key=USER_ID_2, membership="invite"
|
||||
)
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
self.check("get_invited_rooms_for_user", [USER_ID_2], [])
|
||||
event = self.persist(type="m.room.member", key=USER_ID_2, membership="invite")
|
||||
|
||||
self.replicate()
|
||||
|
||||
self.check(
|
||||
"get_invited_rooms_for_user",
|
||||
[USER_ID_2],
|
||||
[
|
||||
|
@ -132,37 +126,34 @@ class SlavedEventStoreTestCase(BaseSlavedStoreTestCase):
|
|||
],
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_push_actions_for_user(self):
|
||||
yield self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
yield self.persist(type="m.room.join", key=USER_ID, membership="join")
|
||||
yield self.persist(
|
||||
self.persist(type="m.room.create", key="", creator=USER_ID)
|
||||
self.persist(type="m.room.join", key=USER_ID, membership="join")
|
||||
self.persist(
|
||||
type="m.room.join", sender=USER_ID, key=USER_ID_2, membership="join"
|
||||
)
|
||||
event1 = yield self.persist(
|
||||
type="m.room.message", msgtype="m.text", body="hello"
|
||||
)
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
event1 = self.persist(type="m.room.message", msgtype="m.text", body="hello")
|
||||
self.replicate()
|
||||
self.check(
|
||||
"get_unread_event_push_actions_by_room_for_user",
|
||||
[ROOM_ID, USER_ID_2, event1.event_id],
|
||||
{"highlight_count": 0, "notify_count": 0},
|
||||
)
|
||||
|
||||
yield self.persist(
|
||||
self.persist(
|
||||
type="m.room.message",
|
||||
msgtype="m.text",
|
||||
body="world",
|
||||
push_actions=[(USER_ID_2, ["notify"])],
|
||||
)
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
self.replicate()
|
||||
self.check(
|
||||
"get_unread_event_push_actions_by_room_for_user",
|
||||
[ROOM_ID, USER_ID_2, event1.event_id],
|
||||
{"highlight_count": 0, "notify_count": 1},
|
||||
)
|
||||
|
||||
yield self.persist(
|
||||
self.persist(
|
||||
type="m.room.message",
|
||||
msgtype="m.text",
|
||||
body="world",
|
||||
|
@ -170,8 +161,8 @@ class SlavedEventStoreTestCase(BaseSlavedStoreTestCase):
|
|||
(USER_ID_2, ["notify", {"set_tweak": "highlight", "value": True}])
|
||||
],
|
||||
)
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
self.replicate()
|
||||
self.check(
|
||||
"get_unread_event_push_actions_by_room_for_user",
|
||||
[ROOM_ID, USER_ID_2, event1.event_id],
|
||||
{"highlight_count": 1, "notify_count": 2},
|
||||
|
@ -179,7 +170,6 @@ class SlavedEventStoreTestCase(BaseSlavedStoreTestCase):
|
|||
|
||||
event_id = 0
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def persist(
|
||||
self,
|
||||
sender=USER_ID,
|
||||
|
@ -206,8 +196,8 @@ class SlavedEventStoreTestCase(BaseSlavedStoreTestCase):
|
|||
depth = self.event_id
|
||||
|
||||
if not prev_events:
|
||||
latest_event_ids = yield self.master_store.get_latest_event_ids_in_room(
|
||||
room_id
|
||||
latest_event_ids = self.get_success(
|
||||
self.master_store.get_latest_event_ids_in_room(room_id)
|
||||
)
|
||||
prev_events = [(ev_id, {}) for ev_id in latest_event_ids]
|
||||
|
||||
|
@ -240,19 +230,23 @@ class SlavedEventStoreTestCase(BaseSlavedStoreTestCase):
|
|||
)
|
||||
else:
|
||||
state_handler = self.hs.get_state_handler()
|
||||
context = yield state_handler.compute_event_context(event)
|
||||
context = self.get_success(state_handler.compute_event_context(event))
|
||||
|
||||
yield self.master_store.add_push_actions_to_staging(
|
||||
self.master_store.add_push_actions_to_staging(
|
||||
event.event_id, {user_id: actions for user_id, actions in push_actions}
|
||||
)
|
||||
|
||||
ordering = None
|
||||
if backfill:
|
||||
yield self.master_store.persist_events([(event, context)], backfilled=True)
|
||||
self.get_success(
|
||||
self.master_store.persist_events([(event, context)], backfilled=True)
|
||||
)
|
||||
else:
|
||||
ordering, _ = yield self.master_store.persist_event(event, context)
|
||||
ordering, _ = self.get_success(
|
||||
self.master_store.persist_event(event, context)
|
||||
)
|
||||
|
||||
if ordering:
|
||||
event.internal_metadata.stream_ordering = ordering
|
||||
|
||||
defer.returnValue(event)
|
||||
return event
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.replication.slave.storage.receipts import SlavedReceiptsStore
|
||||
|
||||
from ._base import BaseSlavedStoreTestCase
|
||||
|
@ -27,13 +25,10 @@ class SlavedReceiptTestCase(BaseSlavedStoreTestCase):
|
|||
|
||||
STORE_TYPE = SlavedReceiptsStore
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_receipt(self):
|
||||
yield self.check("get_receipts_for_user", [USER_ID, "m.read"], {})
|
||||
yield self.master_store.insert_receipt(
|
||||
ROOM_ID, "m.read", USER_ID, [EVENT_ID], {}
|
||||
)
|
||||
yield self.replicate()
|
||||
yield self.check(
|
||||
"get_receipts_for_user", [USER_ID, "m.read"], {ROOM_ID: EVENT_ID}
|
||||
self.check("get_receipts_for_user", [USER_ID, "m.read"], {})
|
||||
self.get_success(
|
||||
self.master_store.insert_receipt(ROOM_ID, "m.read", USER_ID, [EVENT_ID], {})
|
||||
)
|
||||
self.replicate()
|
||||
self.check("get_receipts_for_user", [USER_ID, "m.read"], {ROOM_ID: EVENT_ID})
|
||||
|
|
|
@ -22,39 +22,24 @@ from six.moves.urllib import parse as urlparse
|
|||
|
||||
from twisted.internet import defer
|
||||
|
||||
import synapse.rest.client.v1.room
|
||||
from synapse.api.constants import Membership
|
||||
from synapse.http.server import JsonResource
|
||||
from synapse.types import UserID
|
||||
from synapse.util import Clock
|
||||
from synapse.rest.client.v1 import room
|
||||
|
||||
from tests import unittest
|
||||
from tests.server import (
|
||||
ThreadedMemoryReactorClock,
|
||||
make_request,
|
||||
render,
|
||||
setup_test_homeserver,
|
||||
)
|
||||
|
||||
from .utils import RestHelper
|
||||
|
||||
PATH_PREFIX = b"/_matrix/client/api/v1"
|
||||
|
||||
|
||||
class RoomBase(unittest.TestCase):
|
||||
class RoomBase(unittest.HomeserverTestCase):
|
||||
rmcreator_id = None
|
||||
|
||||
def setUp(self):
|
||||
servlets = [room.register_servlets, room.register_deprecated_servlets]
|
||||
|
||||
self.clock = ThreadedMemoryReactorClock()
|
||||
self.hs_clock = Clock(self.clock)
|
||||
def make_homeserver(self, reactor, clock):
|
||||
|
||||
self.hs = setup_test_homeserver(
|
||||
self.addCleanup,
|
||||
self.hs = self.setup_test_homeserver(
|
||||
"red",
|
||||
http_client=None,
|
||||
clock=self.hs_clock,
|
||||
reactor=self.clock,
|
||||
federation_client=Mock(),
|
||||
ratelimiter=NonCallableMock(spec_set=["send_message"]),
|
||||
)
|
||||
|
@ -63,42 +48,21 @@ class RoomBase(unittest.TestCase):
|
|||
|
||||
self.hs.get_federation_handler = Mock(return_value=Mock())
|
||||
|
||||
def get_user_by_access_token(token=None, allow_guest=False):
|
||||
return {
|
||||
"user": UserID.from_string(self.helper.auth_user_id),
|
||||
"token_id": 1,
|
||||
"is_guest": False,
|
||||
}
|
||||
|
||||
def get_user_by_req(request, allow_guest=False, rights="access"):
|
||||
return synapse.types.create_requester(
|
||||
UserID.from_string(self.helper.auth_user_id), 1, False, None
|
||||
)
|
||||
|
||||
self.hs.get_auth().get_user_by_req = get_user_by_req
|
||||
self.hs.get_auth().get_user_by_access_token = get_user_by_access_token
|
||||
self.hs.get_auth().get_access_token_from_request = Mock(return_value=b"1234")
|
||||
|
||||
def _insert_client_ip(*args, **kwargs):
|
||||
return defer.succeed(None)
|
||||
|
||||
self.hs.get_datastore().insert_client_ip = _insert_client_ip
|
||||
|
||||
self.resource = JsonResource(self.hs)
|
||||
synapse.rest.client.v1.room.register_servlets(self.hs, self.resource)
|
||||
synapse.rest.client.v1.room.register_deprecated_servlets(self.hs, self.resource)
|
||||
self.helper = RestHelper(self.hs, self.resource, self.user_id)
|
||||
return self.hs
|
||||
|
||||
|
||||
class RoomPermissionsTestCase(RoomBase):
|
||||
""" Tests room permissions. """
|
||||
|
||||
user_id = b"@sid1:red"
|
||||
rmcreator_id = b"@notme:red"
|
||||
user_id = "@sid1:red"
|
||||
rmcreator_id = "@notme:red"
|
||||
|
||||
def setUp(self):
|
||||
|
||||
super(RoomPermissionsTestCase, self).setUp()
|
||||
def prepare(self, reactor, clock, hs):
|
||||
|
||||
self.helper.auth_user_id = self.rmcreator_id
|
||||
# create some rooms under the name rmcreator_id
|
||||
|
@ -114,22 +78,20 @@ class RoomPermissionsTestCase(RoomBase):
|
|||
self.created_rmid_msg_path = (
|
||||
"rooms/%s/send/m.room.message/a1" % (self.created_rmid)
|
||||
).encode('ascii')
|
||||
request, channel = make_request(
|
||||
b"PUT",
|
||||
self.created_rmid_msg_path,
|
||||
b'{"msgtype":"m.text","body":"test msg"}',
|
||||
request, channel = self.make_request(
|
||||
"PUT", self.created_rmid_msg_path, b'{"msgtype":"m.text","body":"test msg"}'
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(channel.result["code"], b"200", channel.result)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, channel.result)
|
||||
|
||||
# set topic for public room
|
||||
request, channel = make_request(
|
||||
b"PUT",
|
||||
request, channel = self.make_request(
|
||||
"PUT",
|
||||
("rooms/%s/state/m.room.topic" % self.created_public_rmid).encode('ascii'),
|
||||
b'{"topic":"Public Room Topic"}',
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(channel.result["code"], b"200", channel.result)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, channel.result)
|
||||
|
||||
# auth as user_id now
|
||||
self.helper.auth_user_id = self.user_id
|
||||
|
@ -140,128 +102,128 @@ class RoomPermissionsTestCase(RoomBase):
|
|||
seq = iter(range(100))
|
||||
|
||||
def send_msg_path():
|
||||
return b"/rooms/%s/send/m.room.message/mid%s" % (
|
||||
return "/rooms/%s/send/m.room.message/mid%s" % (
|
||||
self.created_rmid,
|
||||
str(next(seq)).encode('ascii'),
|
||||
str(next(seq)),
|
||||
)
|
||||
|
||||
# send message in uncreated room, expect 403
|
||||
request, channel = make_request(
|
||||
b"PUT",
|
||||
b"/rooms/%s/send/m.room.message/mid2" % (self.uncreated_rmid,),
|
||||
request, channel = self.make_request(
|
||||
"PUT",
|
||||
"/rooms/%s/send/m.room.message/mid2" % (self.uncreated_rmid,),
|
||||
msg_content,
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# send message in created room not joined (no state), expect 403
|
||||
request, channel = make_request(b"PUT", send_msg_path(), msg_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", send_msg_path(), msg_content)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# send message in created room and invited, expect 403
|
||||
self.helper.invite(
|
||||
room=self.created_rmid, src=self.rmcreator_id, targ=self.user_id
|
||||
)
|
||||
request, channel = make_request(b"PUT", send_msg_path(), msg_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", send_msg_path(), msg_content)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# send message in created room and joined, expect 200
|
||||
self.helper.join(room=self.created_rmid, user=self.user_id)
|
||||
request, channel = make_request(b"PUT", send_msg_path(), msg_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", send_msg_path(), msg_content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
# send message in created room and left, expect 403
|
||||
self.helper.leave(room=self.created_rmid, user=self.user_id)
|
||||
request, channel = make_request(b"PUT", send_msg_path(), msg_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", send_msg_path(), msg_content)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_topic_perms(self):
|
||||
topic_content = b'{"topic":"My Topic Name"}'
|
||||
topic_path = b"/rooms/%s/state/m.room.topic" % self.created_rmid
|
||||
topic_path = "/rooms/%s/state/m.room.topic" % self.created_rmid
|
||||
|
||||
# set/get topic in uncreated room, expect 403
|
||||
request, channel = make_request(
|
||||
b"PUT", b"/rooms/%s/state/m.room.topic" % self.uncreated_rmid, topic_content
|
||||
request, channel = self.make_request(
|
||||
"PUT", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid, topic_content
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = make_request(
|
||||
b"GET", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
request, channel = self.make_request(
|
||||
"GET", "/rooms/%s/state/m.room.topic" % self.uncreated_rmid
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# set/get topic in created PRIVATE room not joined, expect 403
|
||||
request, channel = make_request(b"PUT", topic_path, topic_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = make_request(b"GET", topic_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", topic_path, topic_content)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", topic_path)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# set topic in created PRIVATE room and invited, expect 403
|
||||
self.helper.invite(
|
||||
room=self.created_rmid, src=self.rmcreator_id, targ=self.user_id
|
||||
)
|
||||
request, channel = make_request(b"PUT", topic_path, topic_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", topic_path, topic_content)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# get topic in created PRIVATE room and invited, expect 403
|
||||
request, channel = make_request(b"GET", topic_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", topic_path)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# set/get topic in created PRIVATE room and joined, expect 200
|
||||
self.helper.join(room=self.created_rmid, user=self.user_id)
|
||||
|
||||
# Only room ops can set topic by default
|
||||
self.helper.auth_user_id = self.rmcreator_id
|
||||
request, channel = make_request(b"PUT", topic_path, topic_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", topic_path, topic_content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
self.helper.auth_user_id = self.user_id
|
||||
|
||||
request, channel = make_request(b"GET", topic_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.assert_dict(json.loads(topic_content), channel.json_body)
|
||||
request, channel = self.make_request("GET", topic_path)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
self.assert_dict(json.loads(topic_content.decode('utf8')), channel.json_body)
|
||||
|
||||
# set/get topic in created PRIVATE room and left, expect 403
|
||||
self.helper.leave(room=self.created_rmid, user=self.user_id)
|
||||
request, channel = make_request(b"PUT", topic_path, topic_content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = make_request(b"GET", topic_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", topic_path, topic_content)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", topic_path)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
# get topic in PUBLIC room, not joined, expect 403
|
||||
request, channel = make_request(
|
||||
b"GET", b"/rooms/%s/state/m.room.topic" % self.created_public_rmid
|
||||
request, channel = self.make_request(
|
||||
"GET", "/rooms/%s/state/m.room.topic" % self.created_public_rmid
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
# set topic in PUBLIC room, not joined, expect 403
|
||||
request, channel = make_request(
|
||||
b"PUT",
|
||||
b"/rooms/%s/state/m.room.topic" % self.created_public_rmid,
|
||||
request, channel = self.make_request(
|
||||
"PUT",
|
||||
"/rooms/%s/state/m.room.topic" % self.created_public_rmid,
|
||||
topic_content,
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
def _test_get_membership(self, room=None, members=[], expect_code=None):
|
||||
for member in members:
|
||||
path = b"/rooms/%s/state/m.room.member/%s" % (room, member)
|
||||
request, channel = make_request(b"GET", path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(expect_code, int(channel.result["code"]))
|
||||
path = "/rooms/%s/state/m.room.member/%s" % (room, member)
|
||||
request, channel = self.make_request("GET", path)
|
||||
self.render(request)
|
||||
self.assertEquals(expect_code, channel.code)
|
||||
|
||||
def test_membership_basic_room_perms(self):
|
||||
# === room does not exist ===
|
||||
|
@ -428,217 +390,211 @@ class RoomPermissionsTestCase(RoomBase):
|
|||
class RoomsMemberListTestCase(RoomBase):
|
||||
""" Tests /rooms/$room_id/members/list REST events."""
|
||||
|
||||
user_id = b"@sid1:red"
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def test_get_member_list(self):
|
||||
room_id = self.helper.create_room_as(self.user_id)
|
||||
request, channel = make_request(b"GET", b"/rooms/%s/members" % room_id)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", "/rooms/%s/members" % room_id)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_get_member_list_no_room(self):
|
||||
request, channel = make_request(b"GET", b"/rooms/roomdoesnotexist/members")
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", "/rooms/roomdoesnotexist/members")
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_get_member_list_no_permission(self):
|
||||
room_id = self.helper.create_room_as(b"@some_other_guy:red")
|
||||
request, channel = make_request(b"GET", b"/rooms/%s/members" % room_id)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
room_id = self.helper.create_room_as("@some_other_guy:red")
|
||||
request, channel = self.make_request("GET", "/rooms/%s/members" % room_id)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_get_member_list_mixed_memberships(self):
|
||||
room_creator = b"@some_other_guy:red"
|
||||
room_creator = "@some_other_guy:red"
|
||||
room_id = self.helper.create_room_as(room_creator)
|
||||
room_path = b"/rooms/%s/members" % room_id
|
||||
room_path = "/rooms/%s/members" % room_id
|
||||
self.helper.invite(room=room_id, src=room_creator, targ=self.user_id)
|
||||
# can't see list if you're just invited.
|
||||
request, channel = make_request(b"GET", room_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(403, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", room_path)
|
||||
self.render(request)
|
||||
self.assertEquals(403, channel.code, msg=channel.result["body"])
|
||||
|
||||
self.helper.join(room=room_id, user=self.user_id)
|
||||
# can see list now joined
|
||||
request, channel = make_request(b"GET", room_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", room_path)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
self.helper.leave(room=room_id, user=self.user_id)
|
||||
# can see old list once left
|
||||
request, channel = make_request(b"GET", room_path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", room_path)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
|
||||
class RoomsCreateTestCase(RoomBase):
|
||||
""" Tests /rooms and /rooms/$room_id REST events. """
|
||||
|
||||
user_id = b"@sid1:red"
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def test_post_room_no_keys(self):
|
||||
# POST with no config keys, expect new room id
|
||||
request, channel = make_request(b"POST", b"/createRoom", b"{}")
|
||||
request, channel = self.make_request("POST", "/createRoom", "{}")
|
||||
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), channel.result)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, channel.result)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
|
||||
def test_post_room_visibility_key(self):
|
||||
# POST with visibility config key, expect new room id
|
||||
request, channel = make_request(
|
||||
b"POST", b"/createRoom", b'{"visibility":"private"}'
|
||||
request, channel = self.make_request(
|
||||
"POST", "/createRoom", b'{"visibility":"private"}'
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]))
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
|
||||
def test_post_room_custom_key(self):
|
||||
# POST with custom config keys, expect new room id
|
||||
request, channel = make_request(b"POST", b"/createRoom", b'{"custom":"stuff"}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]))
|
||||
request, channel = self.make_request(
|
||||
"POST", "/createRoom", b'{"custom":"stuff"}'
|
||||
)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
|
||||
def test_post_room_known_and_unknown_keys(self):
|
||||
# POST with custom + known config keys, expect new room id
|
||||
request, channel = make_request(
|
||||
b"POST", b"/createRoom", b'{"visibility":"private","custom":"things"}'
|
||||
request, channel = self.make_request(
|
||||
"POST", "/createRoom", b'{"visibility":"private","custom":"things"}'
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]))
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code)
|
||||
self.assertTrue("room_id" in channel.json_body)
|
||||
|
||||
def test_post_room_invalid_content(self):
|
||||
# POST with invalid content / paths, expect 400
|
||||
request, channel = make_request(b"POST", b"/createRoom", b'{"visibili')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]))
|
||||
request, channel = self.make_request("POST", "/createRoom", b'{"visibili')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code)
|
||||
|
||||
request, channel = make_request(b"POST", b"/createRoom", b'["hello"]')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]))
|
||||
request, channel = self.make_request("POST", "/createRoom", b'["hello"]')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code)
|
||||
|
||||
|
||||
class RoomTopicTestCase(RoomBase):
|
||||
""" Tests /rooms/$room_id/topic REST events. """
|
||||
|
||||
user_id = b"@sid1:red"
|
||||
|
||||
def setUp(self):
|
||||
|
||||
super(RoomTopicTestCase, self).setUp()
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
# create the room
|
||||
self.room_id = self.helper.create_room_as(self.user_id)
|
||||
self.path = b"/rooms/%s/state/m.room.topic" % (self.room_id,)
|
||||
self.path = "/rooms/%s/state/m.room.topic" % (self.room_id,)
|
||||
|
||||
def test_invalid_puts(self):
|
||||
# missing keys or invalid json
|
||||
request, channel = make_request(b"PUT", self.path, '{}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, '{}')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", self.path, '{"_name":"bob"}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, '{"_name":"bo"}')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", self.path, '{"nao')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, '{"nao')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(
|
||||
b"PUT", self.path, '[{"_name":"bob"},{"_name":"jill"}]'
|
||||
request, channel = self.make_request(
|
||||
"PUT", self.path, '[{"_name":"bo"},{"_name":"jill"}]'
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", self.path, 'text only')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, 'text only')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", self.path, '')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, '')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
# valid key, wrong type
|
||||
content = '{"topic":["Topic name"]}'
|
||||
request, channel = make_request(b"PUT", self.path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_rooms_topic(self):
|
||||
# nothing should be there
|
||||
request, channel = make_request(b"GET", self.path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(404, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", self.path)
|
||||
self.render(request)
|
||||
self.assertEquals(404, channel.code, msg=channel.result["body"])
|
||||
|
||||
# valid put
|
||||
content = '{"topic":"Topic name"}'
|
||||
request, channel = make_request(b"PUT", self.path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
# valid get
|
||||
request, channel = make_request(b"GET", self.path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", self.path)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
self.assert_dict(json.loads(content), channel.json_body)
|
||||
|
||||
def test_rooms_topic_with_extra_keys(self):
|
||||
# valid put with extra keys
|
||||
content = '{"topic":"Seasons","subtopic":"Summer"}'
|
||||
request, channel = make_request(b"PUT", self.path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", self.path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
# valid get
|
||||
request, channel = make_request(b"GET", self.path)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", self.path)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
self.assert_dict(json.loads(content), channel.json_body)
|
||||
|
||||
|
||||
class RoomMemberStateTestCase(RoomBase):
|
||||
""" Tests /rooms/$room_id/members/$user_id/state REST events. """
|
||||
|
||||
user_id = b"@sid1:red"
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def setUp(self):
|
||||
|
||||
super(RoomMemberStateTestCase, self).setUp()
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.room_id = self.helper.create_room_as(self.user_id)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_invalid_puts(self):
|
||||
path = "/rooms/%s/state/m.room.member/%s" % (self.room_id, self.user_id)
|
||||
# missing keys or invalid json
|
||||
request, channel = make_request(b"PUT", path, '{}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, '{}')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, '{"_name":"bob"}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, '{"_name":"bo"}')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, '{"nao')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, '{"nao')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(
|
||||
b"PUT", path, b'[{"_name":"bob"},{"_name":"jill"}]'
|
||||
request, channel = self.make_request(
|
||||
"PUT", path, b'[{"_name":"bo"},{"_name":"jill"}]'
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, 'text only')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, 'text only')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, '')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, '')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
# valid keys, wrong types
|
||||
content = '{"membership":["%s","%s","%s"]}' % (
|
||||
|
@ -646,9 +602,9 @@ class RoomMemberStateTestCase(RoomBase):
|
|||
Membership.JOIN,
|
||||
Membership.LEAVE,
|
||||
)
|
||||
request, channel = make_request(b"PUT", path, content.encode('ascii'))
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, content.encode('ascii'))
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_rooms_members_self(self):
|
||||
path = "/rooms/%s/state/m.room.member/%s" % (
|
||||
|
@ -658,13 +614,13 @@ class RoomMemberStateTestCase(RoomBase):
|
|||
|
||||
# valid join message (NOOP since we made the room)
|
||||
content = '{"membership":"%s"}' % Membership.JOIN
|
||||
request, channel = make_request(b"PUT", path, content.encode('ascii'))
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, content.encode('ascii'))
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"GET", path, None)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", path, None)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
expected_response = {"membership": Membership.JOIN}
|
||||
self.assertEquals(expected_response, channel.json_body)
|
||||
|
@ -678,13 +634,13 @@ class RoomMemberStateTestCase(RoomBase):
|
|||
|
||||
# valid invite message
|
||||
content = '{"membership":"%s"}' % Membership.INVITE
|
||||
request, channel = make_request(b"PUT", path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"GET", path, None)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", path, None)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
self.assertEquals(json.loads(content), channel.json_body)
|
||||
|
||||
def test_rooms_members_other_custom_keys(self):
|
||||
|
@ -699,13 +655,13 @@ class RoomMemberStateTestCase(RoomBase):
|
|||
Membership.INVITE,
|
||||
"Join us!",
|
||||
)
|
||||
request, channel = make_request(b"PUT", path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"GET", path, None)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("GET", path, None)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
self.assertEquals(json.loads(content), channel.json_body)
|
||||
|
||||
|
||||
|
@ -714,60 +670,58 @@ class RoomMessagesTestCase(RoomBase):
|
|||
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def setUp(self):
|
||||
super(RoomMessagesTestCase, self).setUp()
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.room_id = self.helper.create_room_as(self.user_id)
|
||||
|
||||
def test_invalid_puts(self):
|
||||
path = "/rooms/%s/send/m.room.message/mid1" % (urlparse.quote(self.room_id))
|
||||
# missing keys or invalid json
|
||||
request, channel = make_request(b"PUT", path, '{}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, b'{}')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, '{"_name":"bob"}')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, b'{"_name":"bo"}')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, '{"nao')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, b'{"nao')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(
|
||||
b"PUT", path, '[{"_name":"bob"},{"_name":"jill"}]'
|
||||
request, channel = self.make_request(
|
||||
"PUT", path, b'[{"_name":"bo"},{"_name":"jill"}]'
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, 'text only')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, b'text only')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
request, channel = make_request(b"PUT", path, '')
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
request, channel = self.make_request("PUT", path, b'')
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
def test_rooms_messages_sent(self):
|
||||
path = "/rooms/%s/send/m.room.message/mid1" % (urlparse.quote(self.room_id))
|
||||
|
||||
content = '{"body":"test","msgtype":{"type":"a"}}'
|
||||
request, channel = make_request(b"PUT", path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(400, int(channel.result["code"]), msg=channel.result["body"])
|
||||
content = b'{"body":"test","msgtype":{"type":"a"}}'
|
||||
request, channel = self.make_request("PUT", path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(400, channel.code, msg=channel.result["body"])
|
||||
|
||||
# custom message types
|
||||
content = '{"body":"test","msgtype":"test.custom.text"}'
|
||||
request, channel = make_request(b"PUT", path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
content = b'{"body":"test","msgtype":"test.custom.text"}'
|
||||
request, channel = self.make_request("PUT", path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
# m.text message type
|
||||
path = "/rooms/%s/send/m.room.message/mid2" % (urlparse.quote(self.room_id))
|
||||
content = '{"body":"test2","msgtype":"m.text"}'
|
||||
request, channel = make_request(b"PUT", path, content)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]), msg=channel.result["body"])
|
||||
content = b'{"body":"test2","msgtype":"m.text"}'
|
||||
request, channel = self.make_request("PUT", path, content)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, msg=channel.result["body"])
|
||||
|
||||
|
||||
class RoomInitialSyncTestCase(RoomBase):
|
||||
|
@ -775,16 +729,16 @@ class RoomInitialSyncTestCase(RoomBase):
|
|||
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def setUp(self):
|
||||
super(RoomInitialSyncTestCase, self).setUp()
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
# create the room
|
||||
self.room_id = self.helper.create_room_as(self.user_id)
|
||||
|
||||
def test_initial_sync(self):
|
||||
request, channel = make_request(b"GET", "/rooms/%s/initialSync" % self.room_id)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]))
|
||||
request, channel = self.make_request(
|
||||
"GET", "/rooms/%s/initialSync" % self.room_id
|
||||
)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code)
|
||||
|
||||
self.assertEquals(self.room_id, channel.json_body["room_id"])
|
||||
self.assertEquals("join", channel.json_body["membership"])
|
||||
|
@ -821,17 +775,16 @@ class RoomMessageListTestCase(RoomBase):
|
|||
|
||||
user_id = "@sid1:red"
|
||||
|
||||
def setUp(self):
|
||||
super(RoomMessageListTestCase, self).setUp()
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.room_id = self.helper.create_room_as(self.user_id)
|
||||
|
||||
def test_topo_token_is_accepted(self):
|
||||
token = "t1-0_0_0_0_0_0_0_0_0"
|
||||
request, channel = make_request(
|
||||
b"GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
|
||||
request, channel = self.make_request(
|
||||
"GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]))
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code)
|
||||
self.assertTrue("start" in channel.json_body)
|
||||
self.assertEquals(token, channel.json_body['start'])
|
||||
self.assertTrue("chunk" in channel.json_body)
|
||||
|
@ -839,11 +792,11 @@ class RoomMessageListTestCase(RoomBase):
|
|||
|
||||
def test_stream_token_is_accepted_for_fwd_pagianation(self):
|
||||
token = "s0_0_0_0_0_0_0_0_0"
|
||||
request, channel = make_request(
|
||||
b"GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
|
||||
request, channel = self.make_request(
|
||||
"GET", "/rooms/%s/messages?access_token=x&from=%s" % (self.room_id, token)
|
||||
)
|
||||
render(request, self.resource, self.clock)
|
||||
self.assertEquals(200, int(channel.result["code"]))
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code)
|
||||
self.assertTrue("start" in channel.json_body)
|
||||
self.assertEquals(token, channel.json_body['start'])
|
||||
self.assertTrue("chunk" in channel.json_body)
|
||||
|
|
|
@ -240,7 +240,6 @@ class RestHelper(object):
|
|||
self.assertEquals(200, code)
|
||||
defer.returnValue(response)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def send(self, room_id, body=None, txn_id=None, tok=None, expect_code=200):
|
||||
if txn_id is None:
|
||||
txn_id = "m%s" % (str(time.time()))
|
||||
|
@ -248,9 +247,16 @@ class RestHelper(object):
|
|||
body = "body_text_here"
|
||||
|
||||
path = "/_matrix/client/r0/rooms/%s/send/m.room.message/%s" % (room_id, txn_id)
|
||||
content = '{"msgtype":"m.text","body":"%s"}' % body
|
||||
content = {"msgtype": "m.text", "body": body}
|
||||
if tok:
|
||||
path = path + "?access_token=%s" % tok
|
||||
|
||||
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
|
||||
self.assertEquals(expect_code, code, msg=str(response))
|
||||
request, channel = make_request("PUT", path, json.dumps(content).encode('utf8'))
|
||||
render(request, self.resource, self.hs.get_reactor())
|
||||
|
||||
assert int(channel.result["code"]) == expect_code, (
|
||||
"Expected: %d, got: %d, resp: %r"
|
||||
% (expect_code, int(channel.result["code"]), channel.result["body"])
|
||||
)
|
||||
|
||||
return channel.json_body
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue