mirror of
https://github.com/element-hq/synapse
synced 2024-06-30 12:33:33 +00:00
Compare commits
13 commits
185c8e733b
...
abdf8fb678
Author | SHA1 | Date | |
---|---|---|---|
abdf8fb678 | |||
adeedb7b7c | |||
7c5fb13f7b | |||
f8d57ce656 | |||
13ed84c573 | |||
4243c1f074 | |||
3239b7459c | |||
c99203d98c | |||
9104a9f0d0 | |||
a412a5829d | |||
7ef89b985d | |||
bdf82efea5 | |||
afaf2d9388 |
71
README.rst
71
README.rst
|
@ -1,21 +1,34 @@
|
|||
=========================================================================
|
||||
Synapse |support| |development| |documentation| |license| |pypi| |python|
|
||||
=========================================================================
|
||||
.. image:: https://github.com/element-hq/product/assets/87339233/7abf477a-5277-47f3-be44-ea44917d8ed7
|
||||
:height: 60px
|
||||
|
||||
Synapse is an open-source `Matrix <https://matrix.org/>`_ homeserver written and
|
||||
maintained by the Matrix.org Foundation. We began rapid development in 2014,
|
||||
reaching v1.0.0 in 2019. Development on Synapse and the Matrix protocol itself continues
|
||||
in earnest today.
|
||||
===========================================================================================================
|
||||
Element Synapse - Matrix homeserver implementation |support| |development| |documentation| |license| |pypi| |python|
|
||||
===========================================================================================================
|
||||
|
||||
Briefly, Matrix is an open standard for communications on the internet, supporting
|
||||
federation, encryption and VoIP. Matrix.org has more to say about the `goals of the
|
||||
Matrix project <https://matrix.org/docs/guides/introduction>`_, and the `formal specification
|
||||
<https://spec.matrix.org/>`_ describes the technical details.
|
||||
Synapse is an open source `Matrix <https://matrix.org>`_ homeserver
|
||||
implementation, written and maintained by `Element <https://element.io>`_.
|
||||
`Matrix <https://github.com/matrix-org>`_ is the open standard for
|
||||
secure and interoperable real time communications. You can directly run
|
||||
and manage the source code in this repository, available under an AGPL
|
||||
license. There is no support provided from Element unless you have a
|
||||
subscription.
|
||||
|
||||
Subscription alternative
|
||||
------------------------
|
||||
|
||||
Alternatively, for those that need an enterprise-ready solution, Element
|
||||
Server Suite (ESS) is `available as a subscription <https://element.io/pricing>`_.
|
||||
ESS builds on Synapse to offer a complete Matrix-based backend including the full
|
||||
`Admin Console product <https://element.io/enterprise-functionality/admin-console>`_,
|
||||
giving admins the power to easily manage an organization-wide
|
||||
deployment. It includes advanced identity management, auditing,
|
||||
moderation and data retention options as well as Long Term Support and
|
||||
SLAs. ESS can be used to support any Matrix-based frontend client.
|
||||
|
||||
.. contents::
|
||||
|
||||
Installing and configuration
|
||||
============================
|
||||
🛠️ Installing and configuration
|
||||
===============================
|
||||
|
||||
The Synapse documentation describes `how to install Synapse <https://element-hq.github.io/synapse/latest/setup/installation.html>`_. We recommend using
|
||||
`Docker images <https://element-hq.github.io/synapse/latest/setup/installation.html#docker-images-and-ansible-playbooks>`_ or `Debian packages from Matrix.org
|
||||
|
@ -105,8 +118,8 @@ Following this advice ensures that even if an XSS is found in Synapse, the
|
|||
impact to other applications will be minimal.
|
||||
|
||||
|
||||
Testing a new installation
|
||||
==========================
|
||||
🧪 Testing a new installation
|
||||
============================
|
||||
|
||||
The easiest way to try out your new Synapse installation is by connecting to it
|
||||
from a web client.
|
||||
|
@ -159,8 +172,20 @@ the form of::
|
|||
As when logging in, you will need to specify a "Custom server". Specify your
|
||||
desired ``localpart`` in the 'User name' box.
|
||||
|
||||
Troubleshooting and support
|
||||
===========================
|
||||
🎯 Troubleshooting and support
|
||||
=============================
|
||||
|
||||
🚀 Professional support
|
||||
----------------------
|
||||
|
||||
Enterprise quality support for Synapse including SLAs is available as part of an
|
||||
`Element Server Suite (ESS) <https://element.io/pricing>` subscription.
|
||||
|
||||
If you are an existing ESS subscriber then you can raise a `support request <https://ems.element.io/support>`
|
||||
and access the `knowledge base <https://ems-docs.element.io>`.
|
||||
|
||||
🤝 Community support
|
||||
-------------------
|
||||
|
||||
The `Admin FAQ <https://element-hq.github.io/synapse/latest/usage/administration/admin_faq.html>`_
|
||||
includes tips on dealing with some common problems. For more details, see
|
||||
|
@ -176,8 +201,8 @@ issues for support requests, only for bug reports and feature requests.
|
|||
.. |docs| replace:: ``docs``
|
||||
.. _docs: docs
|
||||
|
||||
Identity Servers
|
||||
================
|
||||
🪪 Identity Servers
|
||||
==================
|
||||
|
||||
Identity servers have the job of mapping email addresses and other 3rd Party
|
||||
IDs (3PIDs) to Matrix user IDs, as well as verifying the ownership of 3PIDs
|
||||
|
@ -206,8 +231,8 @@ an email address with your account, or send an invite to another user via their
|
|||
email address.
|
||||
|
||||
|
||||
Development
|
||||
===========
|
||||
🛠️ Development
|
||||
==============
|
||||
|
||||
We welcome contributions to Synapse from the community!
|
||||
The best place to get started is our
|
||||
|
@ -225,8 +250,8 @@ Alongside all that, join our developer community on Matrix:
|
|||
`#synapse-dev:matrix.org <https://matrix.to/#/#synapse-dev:matrix.org>`_, featuring real humans!
|
||||
|
||||
|
||||
.. |support| image:: https://img.shields.io/matrix/synapse:matrix.org?label=support&logo=matrix
|
||||
:alt: (get support on #synapse:matrix.org)
|
||||
.. |support| image:: https://img.shields.io/badge/matrix-community%20support-success
|
||||
:alt: (get community support in #synapse:matrix.org)
|
||||
:target: https://matrix.to/#/#synapse:matrix.org
|
||||
|
||||
.. |development| image:: https://img.shields.io/matrix/synapse-dev:matrix.org?label=development&logo=matrix
|
||||
|
|
1
changelog.d/17198.misc
Normal file
1
changelog.d/17198.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Remove unused `expire_access_token` option in the Synapse Docker config file. Contributed by @AaronDewes.
|
1
changelog.d/17276.feature
Normal file
1
changelog.d/17276.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Filter for public and empty rooms added to Admin-API [List Room API](https://element-hq.github.io/synapse/latest/admin_api/rooms.html#list-room-api).
|
1
changelog.d/17283.bugfix
Normal file
1
changelog.d/17283.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix a long-standing bug where an invalid 'from' parameter to [`/notifications`](https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3notifications) would result in an Internal Server Error.
|
2
changelog.d/17304.feature
Normal file
2
changelog.d/17304.feature
Normal file
|
@ -0,0 +1,2 @@
|
|||
`register_new_matrix_user` now supports a --exists-ok flag to allow registration of users that already exist in the database.
|
||||
This is useful for scripts that bootstrap user accounts with initial passwords.
|
1
changelog.d/17324.misc
Normal file
1
changelog.d/17324.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Update the README with Element branding, improve headers and fix the #synapse:matrix.org support room link rendering.
|
1
changelog.d/17331.misc
Normal file
1
changelog.d/17331.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Change path of the experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync implementation to `/org.matrix.simplified_msc3575/sync` since our simplified API is slightly incompatible with what's in the current MSC.
|
2
debian/changelog
vendored
2
debian/changelog
vendored
|
@ -1,6 +1,6 @@
|
|||
matrix-synapse-py3 (1.109.0+nmu1) UNRELEASED; urgency=medium
|
||||
|
||||
* `register_new_matrix_user` now supports a --password-file flag.
|
||||
* `register_new_matrix_user` now supports a --password-file and a --exists-ok flag.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 18 Jun 2024 13:29:36 +0100
|
||||
|
||||
|
|
3
debian/register_new_matrix_user.ronn
vendored
3
debian/register_new_matrix_user.ronn
vendored
|
@ -48,6 +48,9 @@ A sample YAML file accepted by `register_new_matrix_user` is described below:
|
|||
Shared secret as defined in server config file. This is an optional
|
||||
parameter as it can be also supplied via the YAML file.
|
||||
|
||||
* `--exists-ok`:
|
||||
Do not fail if the user already exists. The user account will be not updated in this case.
|
||||
|
||||
* `server_url`:
|
||||
URL of the home server. Defaults to 'https://localhost:8448'.
|
||||
|
||||
|
|
|
@ -176,7 +176,6 @@ app_service_config_files:
|
|||
{% endif %}
|
||||
|
||||
macaroon_secret_key: "{{ SYNAPSE_MACAROON_SECRET_KEY }}"
|
||||
expire_access_token: False
|
||||
|
||||
## Signing Keys ##
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ The following query parameters are available:
|
|||
- the room's name,
|
||||
- the local part of the room's canonical alias, or
|
||||
- the complete (local and server part) room's id (case sensitive).
|
||||
* `public_rooms` - Optional flag to filter public rooms. If `true`, only public rooms are queried. If `false`, public rooms are excluded from
|
||||
the query. When the flag is absent (the default), **both** public and non-public rooms are included in the search results.
|
||||
* `empty_rooms` - Optional flag to filter empty rooms. A room is empty if joined_members is zero. If `true`, only empty rooms are queried. If `false`, empty rooms are excluded from
|
||||
the query. When the flag is absent (the default), **both** empty and non-empty rooms are included in the search results.
|
||||
|
||||
Defaults to no filtering.
|
||||
|
||||
|
|
52
poetry.lock
generated
52
poetry.lock
generated
|
@ -35,13 +35,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
|
|||
|
||||
[[package]]
|
||||
name = "authlib"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients."
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Authlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:9637e4de1fb498310a56900b3e2043a206b03cb11c05422014b0302cbc814be3"},
|
||||
{file = "Authlib-1.3.0.tar.gz", hash = "sha256:959ea62a5b7b5123c5059758296122b57cd2585ae2ed1c0622c21b371ffdae06"},
|
||||
{file = "Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377"},
|
||||
{file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -2157,13 +2157,13 @@ rpds-py = ">=0.7.0"
|
|||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.31.0"
|
||||
version = "2.32.2"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||
{file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
|
||||
{file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -2598,22 +2598,22 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.4"
|
||||
version = "6.4.1"
|
||||
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
|
||||
optional = true
|
||||
python-versions = ">= 3.8"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"},
|
||||
{file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"},
|
||||
{file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"},
|
||||
{file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"},
|
||||
{file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"},
|
||||
{file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"},
|
||||
{file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"},
|
||||
{file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"},
|
||||
{file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"},
|
||||
{file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"},
|
||||
{file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"},
|
||||
{file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"},
|
||||
{file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2939,18 +2939,18 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.0.7"
|
||||
version = "2.2.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
|
||||
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ def request_registration(
|
|||
user_type: Optional[str] = None,
|
||||
_print: Callable[[str], None] = print,
|
||||
exit: Callable[[int], None] = sys.exit,
|
||||
exists_ok: bool = False,
|
||||
) -> None:
|
||||
url = "%s/_synapse/admin/v1/register" % (server_location.rstrip("/"),)
|
||||
|
||||
|
@ -97,6 +98,10 @@ def request_registration(
|
|||
r = requests.post(url, json=data)
|
||||
|
||||
if r.status_code != 200:
|
||||
response = r.json()
|
||||
if exists_ok and response["errcode"] == "M_USER_IN_USE":
|
||||
_print("User already exists. Skipping.")
|
||||
return
|
||||
_print("ERROR! Received %d %s" % (r.status_code, r.reason))
|
||||
if 400 <= r.status_code < 500:
|
||||
try:
|
||||
|
@ -115,6 +120,7 @@ def register_new_user(
|
|||
shared_secret: str,
|
||||
admin: Optional[bool],
|
||||
user_type: Optional[str],
|
||||
exists_ok: bool = False,
|
||||
) -> None:
|
||||
if not user:
|
||||
try:
|
||||
|
@ -154,7 +160,13 @@ def register_new_user(
|
|||
admin = False
|
||||
|
||||
request_registration(
|
||||
user, password, server_location, shared_secret, bool(admin), user_type
|
||||
user,
|
||||
password,
|
||||
server_location,
|
||||
shared_secret,
|
||||
bool(admin),
|
||||
user_type,
|
||||
exists_ok=exists_ok,
|
||||
)
|
||||
|
||||
|
||||
|
@ -173,6 +185,11 @@ def main() -> None:
|
|||
default=None,
|
||||
help="Local part of the new user. Will prompt if omitted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exists-ok",
|
||||
action="store_true",
|
||||
help="Do not fail if user already exists.",
|
||||
)
|
||||
password_group = parser.add_mutually_exclusive_group()
|
||||
password_group.add_argument(
|
||||
"-p",
|
||||
|
@ -192,6 +209,7 @@ def main() -> None:
|
|||
default=None,
|
||||
help="User type as specified in synapse.api.constants.UserTypes",
|
||||
)
|
||||
|
||||
admin_group = parser.add_mutually_exclusive_group()
|
||||
admin_group.add_argument(
|
||||
"-a",
|
||||
|
@ -281,7 +299,15 @@ def main() -> None:
|
|||
if args.admin or args.no_admin:
|
||||
admin = args.admin
|
||||
|
||||
register_new_user(args.user, password, server_url, secret, admin, args.user_type)
|
||||
register_new_user(
|
||||
args.user,
|
||||
password,
|
||||
server_url,
|
||||
secret,
|
||||
admin,
|
||||
args.user_type,
|
||||
exists_ok=args.exists_ok,
|
||||
)
|
||||
|
||||
|
||||
def _read_file(file_path: Any, config_path: str) -> str:
|
||||
|
|
|
@ -35,6 +35,7 @@ from synapse.http.servlet import (
|
|||
ResolveRoomIdMixin,
|
||||
RestServlet,
|
||||
assert_params_in_dict,
|
||||
parse_boolean,
|
||||
parse_enum,
|
||||
parse_integer,
|
||||
parse_json,
|
||||
|
@ -242,13 +243,23 @@ class ListRoomRestServlet(RestServlet):
|
|||
errcode=Codes.INVALID_PARAM,
|
||||
)
|
||||
|
||||
public_rooms = parse_boolean(request, "public_rooms")
|
||||
empty_rooms = parse_boolean(request, "empty_rooms")
|
||||
|
||||
direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS)
|
||||
reverse_order = True if direction == Direction.BACKWARDS else False
|
||||
|
||||
# Return list of rooms according to parameters
|
||||
rooms, total_rooms = await self.store.get_rooms_paginate(
|
||||
start, limit, order_by, reverse_order, search_term
|
||||
start,
|
||||
limit,
|
||||
order_by,
|
||||
reverse_order,
|
||||
search_term,
|
||||
public_rooms,
|
||||
empty_rooms,
|
||||
)
|
||||
|
||||
response = {
|
||||
# next_token should be opaque, so return a value the client can parse
|
||||
"offset": start,
|
||||
|
|
|
@ -32,6 +32,7 @@ from synapse.http.servlet import RestServlet, parse_integer, parse_string
|
|||
from synapse.http.site import SynapseRequest
|
||||
from synapse.types import JsonDict
|
||||
|
||||
from ...api.errors import SynapseError
|
||||
from ._base import client_patterns
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -56,7 +57,22 @@ class NotificationsServlet(RestServlet):
|
|||
requester = await self.auth.get_user_by_req(request)
|
||||
user_id = requester.user.to_string()
|
||||
|
||||
from_token = parse_string(request, "from", required=False)
|
||||
# While this is intended to be "string" to clients, the 'from' token
|
||||
# is actually based on a numeric ID. So it must parse to an int.
|
||||
from_token_str = parse_string(request, "from", required=False)
|
||||
if from_token_str is not None:
|
||||
# Parse to an integer.
|
||||
try:
|
||||
from_token = int(from_token_str)
|
||||
except ValueError:
|
||||
# If it doesn't parse to an integer, then this cannot possibly be a valid
|
||||
# pagination token, as we only hand out integers.
|
||||
raise SynapseError(
|
||||
400, 'Query parameter "from" contains unrecognised token'
|
||||
)
|
||||
else:
|
||||
from_token = None
|
||||
|
||||
limit = parse_integer(request, "limit", default=50)
|
||||
only = parse_string(request, "only", required=False)
|
||||
|
||||
|
|
|
@ -864,7 +864,7 @@ class SlidingSyncRestServlet(RestServlet):
|
|||
"""
|
||||
|
||||
PATTERNS = client_patterns(
|
||||
"/org.matrix.msc3575/sync$", releases=[], v1=False, unstable=True
|
||||
"/org.matrix.simplified_msc3575/sync$", releases=[], v1=False, unstable=True
|
||||
)
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
|
|
|
@ -1829,7 +1829,7 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, StreamWorkerStore, SQLBas
|
|||
async def get_push_actions_for_user(
|
||||
self,
|
||||
user_id: str,
|
||||
before: Optional[str] = None,
|
||||
before: Optional[int] = None,
|
||||
limit: int = 50,
|
||||
only_highlight: bool = False,
|
||||
) -> List[UserPushAction]:
|
||||
|
|
|
@ -606,6 +606,8 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
order_by: str,
|
||||
reverse_order: bool,
|
||||
search_term: Optional[str],
|
||||
public_rooms: Optional[bool],
|
||||
empty_rooms: Optional[bool],
|
||||
) -> Tuple[List[Dict[str, Any]], int]:
|
||||
"""Function to retrieve a paginated list of rooms as json.
|
||||
|
||||
|
@ -617,30 +619,49 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
search_term: a string to filter room names,
|
||||
canonical alias and room ids by.
|
||||
Room ID must match exactly. Canonical alias must match a substring of the local part.
|
||||
public_rooms: Optional flag to filter public and non-public rooms. If true, public rooms are queried.
|
||||
if false, public rooms are excluded from the query. When it is
|
||||
none (the default), both public rooms and none-public-rooms are queried.
|
||||
empty_rooms: Optional flag to filter empty and non-empty rooms.
|
||||
A room is empty if joined_members is zero.
|
||||
If true, empty rooms are queried.
|
||||
if false, empty rooms are excluded from the query. When it is
|
||||
none (the default), both empty rooms and none-empty rooms are queried.
|
||||
Returns:
|
||||
A list of room dicts and an integer representing the total number of
|
||||
rooms that exist given this query
|
||||
"""
|
||||
# Filter room names by a string
|
||||
where_statement = ""
|
||||
search_pattern: List[object] = []
|
||||
filter_ = []
|
||||
where_args = []
|
||||
if search_term:
|
||||
where_statement = """
|
||||
WHERE LOWER(state.name) LIKE ?
|
||||
OR LOWER(state.canonical_alias) LIKE ?
|
||||
OR state.room_id = ?
|
||||
"""
|
||||
filter_ = [
|
||||
"LOWER(state.name) LIKE ? OR "
|
||||
"LOWER(state.canonical_alias) LIKE ? OR "
|
||||
"state.room_id = ?"
|
||||
]
|
||||
|
||||
# Our postgres db driver converts ? -> %s in SQL strings as that's the
|
||||
# placeholder for postgres.
|
||||
# HOWEVER, if you put a % into your SQL then everything goes wibbly.
|
||||
# To get around this, we're going to surround search_term with %'s
|
||||
# before giving it to the database in python instead
|
||||
search_pattern = [
|
||||
"%" + search_term.lower() + "%",
|
||||
"#%" + search_term.lower() + "%:%",
|
||||
where_args = [
|
||||
f"%{search_term.lower()}%",
|
||||
f"#%{search_term.lower()}%:%",
|
||||
search_term,
|
||||
]
|
||||
if public_rooms is not None:
|
||||
filter_arg = "1" if public_rooms else "0"
|
||||
filter_.append(f"rooms.is_public = '{filter_arg}'")
|
||||
|
||||
if empty_rooms is not None:
|
||||
if empty_rooms:
|
||||
filter_.append("curr.joined_members = 0")
|
||||
else:
|
||||
filter_.append("curr.joined_members <> 0")
|
||||
|
||||
where_clause = "WHERE " + " AND ".join(filter_) if len(filter_) > 0 else ""
|
||||
|
||||
# Set ordering
|
||||
if RoomSortOrder(order_by) == RoomSortOrder.SIZE:
|
||||
|
@ -717,7 +738,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
LIMIT ?
|
||||
OFFSET ?
|
||||
""".format(
|
||||
where=where_statement,
|
||||
where=where_clause,
|
||||
order_by=order_by_column,
|
||||
direction="ASC" if order_by_asc else "DESC",
|
||||
)
|
||||
|
@ -726,10 +747,12 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
count_sql = """
|
||||
SELECT count(*) FROM (
|
||||
SELECT room_id FROM room_stats_state state
|
||||
INNER JOIN room_stats_current curr USING (room_id)
|
||||
INNER JOIN rooms USING (room_id)
|
||||
{where}
|
||||
) AS get_room_ids
|
||||
""".format(
|
||||
where=where_statement,
|
||||
where=where_clause,
|
||||
)
|
||||
|
||||
def _get_rooms_paginate_txn(
|
||||
|
@ -737,7 +760,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
) -> Tuple[List[Dict[str, Any]], int]:
|
||||
# Add the search term into the WHERE clause
|
||||
# and execute the data query
|
||||
txn.execute(info_sql, search_pattern + [limit, start])
|
||||
txn.execute(info_sql, where_args + [limit, start])
|
||||
|
||||
# Refactor room query data into a structured dictionary
|
||||
rooms = []
|
||||
|
@ -767,7 +790,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
|||
# Execute the count query
|
||||
|
||||
# Add the search term into the WHERE clause if present
|
||||
txn.execute(count_sql, search_pattern)
|
||||
txn.execute(count_sql, where_args)
|
||||
|
||||
room_count = cast(Tuple[int], txn.fetchone())
|
||||
return rooms, room_count[0]
|
||||
|
|
|
@ -688,7 +688,7 @@ class ModuleApiTestCase(BaseModuleApiTestCase):
|
|||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications?from=",
|
||||
"/notifications",
|
||||
access_token=tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200, channel.result)
|
||||
|
|
|
@ -1795,6 +1795,83 @@ class RoomTestCase(unittest.HomeserverTestCase):
|
|||
self.assertEqual(room_id, channel.json_body["rooms"][0].get("room_id"))
|
||||
self.assertEqual("ж", channel.json_body["rooms"][0].get("name"))
|
||||
|
||||
def test_filter_public_rooms(self) -> None:
|
||||
self.helper.create_room_as(
|
||||
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||
)
|
||||
self.helper.create_room_as(
|
||||
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||
)
|
||||
self.helper.create_room_as(
|
||||
self.admin_user, tok=self.admin_user_tok, is_public=False
|
||||
)
|
||||
|
||||
response = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/admin/v1/rooms",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, response.code, msg=response.json_body)
|
||||
self.assertEqual(3, response.json_body["total_rooms"])
|
||||
self.assertEqual(3, len(response.json_body["rooms"]))
|
||||
|
||||
response = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/admin/v1/rooms?public_rooms=true",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, response.code, msg=response.json_body)
|
||||
self.assertEqual(2, response.json_body["total_rooms"])
|
||||
self.assertEqual(2, len(response.json_body["rooms"]))
|
||||
|
||||
response = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/admin/v1/rooms?public_rooms=false",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, response.code, msg=response.json_body)
|
||||
self.assertEqual(1, response.json_body["total_rooms"])
|
||||
self.assertEqual(1, len(response.json_body["rooms"]))
|
||||
|
||||
def test_filter_empty_rooms(self) -> None:
|
||||
self.helper.create_room_as(
|
||||
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||
)
|
||||
self.helper.create_room_as(
|
||||
self.admin_user, tok=self.admin_user_tok, is_public=True
|
||||
)
|
||||
room_id = self.helper.create_room_as(
|
||||
self.admin_user, tok=self.admin_user_tok, is_public=False
|
||||
)
|
||||
self.helper.leave(room_id, self.admin_user, tok=self.admin_user_tok)
|
||||
|
||||
response = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/admin/v1/rooms",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, response.code, msg=response.json_body)
|
||||
self.assertEqual(3, response.json_body["total_rooms"])
|
||||
self.assertEqual(3, len(response.json_body["rooms"]))
|
||||
|
||||
response = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/admin/v1/rooms?empty_rooms=false",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, response.code, msg=response.json_body)
|
||||
self.assertEqual(2, response.json_body["total_rooms"])
|
||||
self.assertEqual(2, len(response.json_body["rooms"]))
|
||||
|
||||
response = self.make_request(
|
||||
"GET",
|
||||
"/_synapse/admin/v1/rooms?empty_rooms=true",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, response.code, msg=response.json_body)
|
||||
self.assertEqual(1, response.json_body["total_rooms"])
|
||||
self.assertEqual(1, len(response.json_body["rooms"]))
|
||||
|
||||
def test_single_room(self) -> None:
|
||||
"""Test that a single room can be requested correctly"""
|
||||
# Create two test rooms
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
# [This file includes modifications made by New Vector Limited]
|
||||
#
|
||||
#
|
||||
from typing import List, Optional, Tuple
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
@ -48,6 +49,14 @@ class HTTPPusherTests(HomeserverTestCase):
|
|||
self.sync_handler = homeserver.get_sync_handler()
|
||||
self.auth_handler = homeserver.get_auth_handler()
|
||||
|
||||
self.user_id = self.register_user("user", "pass")
|
||||
self.access_token = self.login("user", "pass")
|
||||
self.other_user_id = self.register_user("otheruser", "pass")
|
||||
self.other_access_token = self.login("otheruser", "pass")
|
||||
|
||||
# Create a room
|
||||
self.room_id = self.helper.create_room_as(self.user_id, tok=self.access_token)
|
||||
|
||||
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
|
||||
# Mock out the calls over federation.
|
||||
fed_transport_client = Mock(spec=["send_transaction"])
|
||||
|
@ -61,32 +70,22 @@ class HTTPPusherTests(HomeserverTestCase):
|
|||
"""
|
||||
Local users will get notified for invites
|
||||
"""
|
||||
|
||||
user_id = self.register_user("user", "pass")
|
||||
access_token = self.login("user", "pass")
|
||||
other_user_id = self.register_user("otheruser", "pass")
|
||||
other_access_token = self.login("otheruser", "pass")
|
||||
|
||||
# Create a room
|
||||
room = self.helper.create_room_as(user_id, tok=access_token)
|
||||
|
||||
# Check we start with no pushes
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications",
|
||||
access_token=other_access_token,
|
||||
)
|
||||
self.assertEqual(channel.code, 200, channel.result)
|
||||
self.assertEqual(len(channel.json_body["notifications"]), 0, channel.json_body)
|
||||
self._request_notifications(from_token=None, limit=1, expected_count=0)
|
||||
|
||||
# Send an invite
|
||||
self.helper.invite(room=room, src=user_id, targ=other_user_id, tok=access_token)
|
||||
self.helper.invite(
|
||||
room=self.room_id,
|
||||
src=self.user_id,
|
||||
targ=self.other_user_id,
|
||||
tok=self.access_token,
|
||||
)
|
||||
|
||||
# We should have a notification now
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications",
|
||||
access_token=other_access_token,
|
||||
access_token=self.other_access_token,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(len(channel.json_body["notifications"]), 1, channel.json_body)
|
||||
|
@ -95,3 +94,139 @@ class HTTPPusherTests(HomeserverTestCase):
|
|||
"invite",
|
||||
channel.json_body,
|
||||
)
|
||||
|
||||
def test_pagination_of_notifications(self) -> None:
|
||||
"""
|
||||
Check that pagination of notifications works.
|
||||
"""
|
||||
# Check we start with no pushes
|
||||
self._request_notifications(from_token=None, limit=1, expected_count=0)
|
||||
|
||||
# Send an invite and have the other user join the room.
|
||||
self.helper.invite(
|
||||
room=self.room_id,
|
||||
src=self.user_id,
|
||||
targ=self.other_user_id,
|
||||
tok=self.access_token,
|
||||
)
|
||||
self.helper.join(self.room_id, self.other_user_id, tok=self.other_access_token)
|
||||
|
||||
# Send 5 messages in the room and note down their event IDs.
|
||||
sent_event_ids = []
|
||||
for _ in range(5):
|
||||
resp = self.helper.send_event(
|
||||
self.room_id,
|
||||
"m.room.message",
|
||||
{"body": "honk", "msgtype": "m.text"},
|
||||
tok=self.access_token,
|
||||
)
|
||||
sent_event_ids.append(resp["event_id"])
|
||||
|
||||
# We expect to get notifications for messages in reverse order.
|
||||
# So reverse this list of event IDs to make it easier to compare
|
||||
# against later.
|
||||
sent_event_ids.reverse()
|
||||
|
||||
# We should have a few notifications now. Let's try and fetch the first 2.
|
||||
notification_event_ids, _ = self._request_notifications(
|
||||
from_token=None, limit=2, expected_count=2
|
||||
)
|
||||
|
||||
# Check we got the expected event IDs back.
|
||||
self.assertEqual(notification_event_ids, sent_event_ids[:2])
|
||||
|
||||
# Try requesting again without a 'from' query parameter. We should get the
|
||||
# same two notifications back.
|
||||
notification_event_ids, next_token = self._request_notifications(
|
||||
from_token=None, limit=2, expected_count=2
|
||||
)
|
||||
self.assertEqual(notification_event_ids, sent_event_ids[:2])
|
||||
|
||||
# Ask for the next 5 notifications, though there should only be
|
||||
# 4 remaining; the next 3 messages and the invite.
|
||||
#
|
||||
# We need to use the "next_token" from the response as the "from"
|
||||
# query parameter in the next request in order to paginate.
|
||||
notification_event_ids, next_token = self._request_notifications(
|
||||
from_token=next_token, limit=5, expected_count=4
|
||||
)
|
||||
# Ensure we chop off the invite on the end.
|
||||
notification_event_ids = notification_event_ids[:-1]
|
||||
self.assertEqual(notification_event_ids, sent_event_ids[2:])
|
||||
|
||||
def _request_notifications(
|
||||
self, from_token: Optional[str], limit: int, expected_count: int
|
||||
) -> Tuple[List[str], str]:
|
||||
"""
|
||||
Make a request to /notifications to get the latest events to be notified about.
|
||||
|
||||
Only the event IDs are returned. The request is made by the "other user".
|
||||
|
||||
Args:
|
||||
from_token: An optional starting parameter.
|
||||
limit: The maximum number of results to return.
|
||||
expected_count: The number of events to expect in the response.
|
||||
|
||||
Returns:
|
||||
A list of event IDs that the client should be notified about.
|
||||
Events are returned newest-first.
|
||||
"""
|
||||
# Construct the request path.
|
||||
path = f"/notifications?limit={limit}"
|
||||
if from_token is not None:
|
||||
path += f"&from={from_token}"
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
path,
|
||||
access_token=self.other_access_token,
|
||||
)
|
||||
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(
|
||||
len(channel.json_body["notifications"]), expected_count, channel.json_body
|
||||
)
|
||||
|
||||
# Extract the necessary data from the response.
|
||||
next_token = channel.json_body["next_token"]
|
||||
event_ids = [
|
||||
event["event"]["event_id"] for event in channel.json_body["notifications"]
|
||||
]
|
||||
|
||||
return event_ids, next_token
|
||||
|
||||
def test_parameters(self) -> None:
|
||||
"""
|
||||
Test that appropriate errors are returned when query parameters are malformed.
|
||||
"""
|
||||
# Test that no parameters are required.
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications",
|
||||
access_token=self.other_access_token,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
|
||||
# Test that limit cannot be negative
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications?limit=-1",
|
||||
access_token=self.other_access_token,
|
||||
)
|
||||
self.assertEqual(channel.code, 400)
|
||||
|
||||
# Test that the 'limit' parameter must be an integer.
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications?limit=foobar",
|
||||
access_token=self.other_access_token,
|
||||
)
|
||||
self.assertEqual(channel.code, 400)
|
||||
|
||||
# Test that the 'from' parameter must be an integer.
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
"/notifications?from=osborne",
|
||||
access_token=self.other_access_token,
|
||||
)
|
||||
self.assertEqual(channel.code, 400)
|
||||
|
|
|
@ -1228,7 +1228,9 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
|||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.store = hs.get_datastores().main
|
||||
self.sync_endpoint = "/_matrix/client/unstable/org.matrix.msc3575/sync"
|
||||
self.sync_endpoint = (
|
||||
"/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"
|
||||
)
|
||||
self.store = hs.get_datastores().main
|
||||
self.event_sources = hs.get_event_sources()
|
||||
|
||||
|
|
Loading…
Reference in a new issue