diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 0000000000..a746ae6de3
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,31 @@
+name: Deploy the documentation
+
+on:
+ push:
+ branches:
+ - develop
+
+ workflow_dispatch:
+
+jobs:
+ pages:
+ name: GitHub Pages
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Setup mdbook
+ uses: peaceiris/actions-mdbook@4b5ef36b314c2599664ca107bb8c02412548d79d # v1.1.14
+ with:
+ mdbook-version: '0.4.9'
+
+ - name: Build the documentation
+ run: mdbook build
+
+ - name: Deploy latest documentation
+ uses: peaceiris/actions-gh-pages@068dc23d9710f1ba62e86896f84735d869951305 # v3.8.0
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ keep_files: true
+ publish_dir: ./book
+ destination_dir: ./develop
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 2ae81b5fcf..955beb4aa0 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -232,9 +232,9 @@ jobs:
- name: Run SyTest
run: /bootstrap.sh synapse
working-directory: /src
- - name: Dump results.tap
+ - name: Summarise results.tap
if: ${{ always() }}
- run: cat /logs/results.tap
+ run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
- name: Upload SyTest logs
uses: actions/upload-artifact@v2
if: ${{ always() }}
diff --git a/.gitignore b/.gitignore
index 295a18b539..6b9257b5c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,3 +46,6 @@ __pycache__/
/docs/build/
/htmlcov
/pip-wheel-metadata/
+
+# docs
+book/
diff --git a/CHANGES.md b/CHANGES.md
index f03a53affc..04d260f8e5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,12 @@
+Synapse 1.35.1 (2021-06-03)
+===========================
+
+Bugfixes
+--------
+
+- Fix a bug introduced in v1.35.0 where invite-only rooms would be shown to all users in a space, regardless of if the user had access to it. ([\#10109](https://github.com/matrix-org/synapse/issues/10109))
+
+
Synapse 1.35.0 (2021-06-01)
===========================
diff --git a/MANIFEST.in b/MANIFEST.in
index 25d1cb758e..0522319c40 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -40,6 +40,7 @@ exclude mypy.ini
exclude sytest-blacklist
exclude test_postgresql.sh
+include book.toml
include pyproject.toml
recursive-include changelog.d *
diff --git a/book.toml b/book.toml
new file mode 100644
index 0000000000..fa83d86ffc
--- /dev/null
+++ b/book.toml
@@ -0,0 +1,39 @@
+# Documentation for possible options in this file is at
+# https://rust-lang.github.io/mdBook/format/config.html
+[book]
+title = "Synapse"
+authors = ["The Matrix.org Foundation C.I.C."]
+language = "en"
+multilingual = false
+
+# The directory that documentation files are stored in
+src = "docs"
+
+[build]
+# Prevent markdown pages from being automatically generated when they're
+# linked to in SUMMARY.md
+create-missing = false
+
+[output.html]
+# The URL visitors will be directed to when they try to edit a page
+edit-url-template = "https://github.com/matrix-org/synapse/edit/develop/{path}"
+
+# Remove the numbers that appear before each item in the sidebar, as they can
+# get quite messy as we nest deeper
+no-section-label = true
+
+# The source code URL of the repository
+git-repository-url = "https://github.com/matrix-org/synapse"
+
+# The path that the docs are hosted on
+site-url = "/synapse/"
+
+# Additional HTML, JS, CSS that's injected into each page of the book.
+# More information available in docs/website_files/README.md
+additional-css = [
+ "docs/website_files/table-of-contents.css",
+ "docs/website_files/remove-nav-buttons.css",
+ "docs/website_files/indent-section-headers.css",
+]
+additional-js = ["docs/website_files/table-of-contents.js"]
+theme = "docs/website_files/theme"
\ No newline at end of file
diff --git a/changelog.d/10044.feature b/changelog.d/10044.feature
new file mode 100644
index 0000000000..70c0a3851e
--- /dev/null
+++ b/changelog.d/10044.feature
@@ -0,0 +1 @@
+Add new admin APIs to remove media by media ID from quarantine. Contributed by @dkimpel.
diff --git a/changelog.d/10086.doc b/changelog.d/10086.doc
new file mode 100644
index 0000000000..2200579012
--- /dev/null
+++ b/changelog.d/10086.doc
@@ -0,0 +1 @@
+Add initial infrastructure for rendering Synapse documentation with mdbook.
diff --git a/changelog.d/10089.doc b/changelog.d/10089.doc
new file mode 100644
index 0000000000..d9e93773ab
--- /dev/null
+++ b/changelog.d/10089.doc
@@ -0,0 +1 @@
+Convert the remaining Admin API documentation files to markdown.
diff --git a/changelog.d/10094.misc b/changelog.d/10094.misc
new file mode 100644
index 0000000000..01efe14f74
--- /dev/null
+++ b/changelog.d/10094.misc
@@ -0,0 +1 @@
+In Github Actions workflows, summarize the Sytest results in an easy-to-read format.
diff --git a/changelog.d/10109.bugfix b/changelog.d/10109.bugfix
deleted file mode 100644
index bc41bf9e5e..0000000000
--- a/changelog.d/10109.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix a bug introduced in v1.35.0 where invite-only rooms would be shown to users in a space who were not invited.
diff --git a/changelog.d/10111.misc b/changelog.d/10111.misc
new file mode 100644
index 0000000000..42e42b69ab
--- /dev/null
+++ b/changelog.d/10111.misc
@@ -0,0 +1 @@
+Improve opentracing annotations for `Notifier`.
diff --git a/changelog.d/10112.misc b/changelog.d/10112.misc
new file mode 100644
index 0000000000..40af09760c
--- /dev/null
+++ b/changelog.d/10112.misc
@@ -0,0 +1 @@
+Enable Prometheus metrics for the jaeger client library.
diff --git a/changelog.d/10113.feature b/changelog.d/10113.feature
new file mode 100644
index 0000000000..2658ab8918
--- /dev/null
+++ b/changelog.d/10113.feature
@@ -0,0 +1 @@
+Report OpenTracing spans for database activity.
diff --git a/changelog.d/10118.bugfix b/changelog.d/10118.bugfix
new file mode 100644
index 0000000000..db62b50e0b
--- /dev/null
+++ b/changelog.d/10118.bugfix
@@ -0,0 +1 @@
+Fix a bug introduced in Synapse 1.33.0 which caused replication requests to fail when receiving a lot of very large events via federation.
diff --git a/changelog.d/9224.feature b/changelog.d/9224.feature
new file mode 100644
index 0000000000..76519c23e2
--- /dev/null
+++ b/changelog.d/9224.feature
@@ -0,0 +1 @@
+Add new endpoint `/_matrix/client/r0/rooms/{roomId}/aliases` from Client-Server API r0.6.1 (previously [MSC2432](https://github.com/matrix-org/matrix-doc/pull/2432)).
diff --git a/debian/changelog b/debian/changelog
index d5efb8ccba..084e878def 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+matrix-synapse-py3 (1.35.1) stable; urgency=medium
+
+ * New synapse release 1.35.1.
+
+ -- Synapse Packaging team <packages@matrix.org> Thu, 03 Jun 2021 08:11:29 -0400
+
matrix-synapse-py3 (1.35.0) stable; urgency=medium
* New synapse release 1.35.0.
diff --git a/docs/README.md b/docs/README.md
index 3c6ea48c66..e113f55d2a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,7 +1,72 @@
# Synapse Documentation
-This directory contains documentation specific to the `synapse` homeserver.
+**The documentation is currently hosted [here](https://matrix-org.github.io/synapse).**
+Please update any links to point to the new website instead.
-All matrix-generic documentation now lives in its own project, located at [matrix-org/matrix-doc](https://github.com/matrix-org/matrix-doc)
+## About
-(Note: some items here may be moved to [matrix-org/matrix-doc](https://github.com/matrix-org/matrix-doc) at some point in the future.)
+This directory currently holds a series of markdown files documenting how to install, use
+and develop Synapse, the reference Matrix homeserver. The documentation is readable directly
+from this repository, but it is recommended to instead browse through the
+[website](https://matrix-org.github.io/synapse) for easier discoverability.
+
+## Adding to the documentation
+
+Most of the documentation currently exists as top-level files, as when organising them into
+a structured website, these files were kept in place so that existing links would not break.
+The rest of the documentation is stored in folders, such as `setup`, `usage`, and `development`
+etc. **All new documentation files should be placed in structured folders.** For example:
+
+To create a new user-facing documentation page about a new Single Sign-On protocol named
+"MyCoolProtocol", one should create a new file with a relevant name, such as "my_cool_protocol.md".
+This file might fit into the documentation structure at:
+
+- Usage
+ - Configuration
+ - User Authentication
+ - Single Sign-On
+ - **My Cool Protocol**
+
+Given that, one would place the new file under
+`usage/configuration/user_authentication/single_sign_on/my_cool_protocol.md`.
+
+Note that the structure of the documentation (and thus the left sidebar on the website) is determined
+by the list in [SUMMARY.md](SUMMARY.md). The final thing to do when adding a new page is to add a new
+line linking to the new documentation file:
+
+```markdown
+- [My Cool Protocol](usage/configuration/user_authentication/single_sign_on/my_cool_protocol.md)
+```
+
+## Building the documentation
+
+The documentation is built with [mdbook](https://rust-lang.github.io/mdBook/), and the outline of the
+documentation is determined by the structure of [SUMMARY.md](SUMMARY.md).
+
+First, [get mdbook](https://github.com/rust-lang/mdBook#installation). Then, **from the root of the repository**,
+build the documentation with:
+
+```sh
+mdbook build
+```
+
+The rendered contents will be outputted to a new `book/` directory at the root of the repository. You can
+browse the book by opening `book/index.html` in a web browser.
+
+You can also have mdbook host the docs on a local webserver with hot-reload functionality via:
+
+```sh
+mdbook serve
+```
+
+The URL at which the docs can be viewed at will be logged.
+
+## Configuration and theming
+
+The look and behaviour of the website is configured by the [book.toml](../book.toml) file
+at the root of the repository. See
+[mdbook's documentation on configuration](https://rust-lang.github.io/mdBook/format/config.html)
+for available options.
+
+The site can be themed and additionally extended with extra UI and features. See
+[website_files/README.md](website_files/README.md) for details.
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
new file mode 100644
index 0000000000..8f39ae0270
--- /dev/null
+++ b/docs/SUMMARY.md
@@ -0,0 +1,87 @@
+# Summary
+
+# Introduction
+- [Welcome and Overview](welcome_and_overview.md)
+
+# Setup
+ - [Installation](setup/installation.md)
+ - [Using Postgres](postgres.md)
+ - [Configuring a Reverse Proxy](reverse_proxy.md)
+ - [Configuring a Turn Server](turn-howto.md)
+ - [Delegation](delegate.md)
+
+# Upgrading
+ - [Upgrading between Synapse Versions](upgrading/README.md)
+ - [Upgrading from pre-Synapse 1.0](MSC1711_certificates_FAQ.md)
+
+# Usage
+ - [Federation](federate.md)
+ - [Configuration](usage/configuration/README.md)
+ - [Homeserver Sample Config File](usage/configuration/homeserver_sample_config.md)
+ - [Logging Sample Config File](usage/configuration/logging_sample_config.md)
+ - [Structured Logging](structured_logging.md)
+ - [User Authentication](usage/configuration/user_authentication/README.md)
+ - [Single-Sign On]()
+ - [OpenID Connect](openid.md)
+ - [SAML]()
+ - [CAS]()
+ - [SSO Mapping Providers](sso_mapping_providers.md)
+ - [Password Auth Providers](password_auth_providers.md)
+ - [JSON Web Tokens](jwt.md)
+ - [Registration Captcha](CAPTCHA_SETUP.md)
+ - [Application Services](application_services.md)
+ - [Server Notices](server_notices.md)
+ - [Consent Tracking](consent_tracking.md)
+ - [URL Previews](url_previews.md)
+ - [User Directory](user_directory.md)
+ - [Message Retention Policies](message_retention_policies.md)
+ - [Pluggable Modules]()
+ - [Third Party Rules]()
+ - [Spam Checker](spam_checker.md)
+ - [Presence Router](presence_router_module.md)
+ - [Media Storage Providers]()
+ - [Workers](workers.md)
+ - [Using `synctl` with Workers](synctl_workers.md)
+ - [Systemd](systemd-with-workers/README.md)
+ - [Administration](usage/administration/README.md)
+ - [Admin API](usage/administration/admin_api/README.md)
+ - [Account Validity](admin_api/account_validity.md)
+ - [Delete Group](admin_api/delete_group.md)
+ - [Event Reports](admin_api/event_reports.md)
+ - [Media](admin_api/media_admin_api.md)
+ - [Purge History](admin_api/purge_history_api.md)
+ - [Purge Rooms](admin_api/purge_room.md)
+ - [Register Users](admin_api/register_api.md)
+ - [Manipulate Room Membership](admin_api/room_membership.md)
+ - [Rooms](admin_api/rooms.md)
+ - [Server Notices](admin_api/server_notices.md)
+ - [Shutdown Room](admin_api/shutdown_room.md)
+ - [Statistics](admin_api/statistics.md)
+ - [Users](admin_api/user_admin_api.md)
+ - [Server Version](admin_api/version_api.md)
+ - [Manhole](manhole.md)
+ - [Monitoring](metrics-howto.md)
+ - [Scripts]()
+
+# Development
+ - [Contributing Guide](development/contributing_guide.md)
+ - [Code Style](code_style.md)
+ - [Git Usage](dev/git.md)
+ - [Testing]()
+ - [OpenTracing](opentracing.md)
+ - [Synapse Architecture]()
+ - [Log Contexts](log_contexts.md)
+ - [Replication](replication.md)
+ - [TCP Replication](tcp_replication.md)
+ - [Internal Documentation](development/internal_documentation/README.md)
+ - [Single Sign-On]()
+ - [SAML](dev/saml.md)
+ - [CAS](dev/cas.md)
+ - [State Resolution]()
+ - [The Auth Chain Difference Algorithm](auth_chain_difference_algorithm.md)
+ - [Media Repository](media_repository.md)
+ - [Room and User Statistics](room_and_user_statistics.md)
+ - [Scripts]()
+
+# Other
+ - [Dependency Deprecation Policy](deprecation_policy.md)
\ No newline at end of file
diff --git a/docs/admin_api/README.rst b/docs/admin_api/README.rst
index 9587bee0ce..37cee87d32 100644
--- a/docs/admin_api/README.rst
+++ b/docs/admin_api/README.rst
@@ -1,28 +1,14 @@
Admin APIs
==========
-This directory includes documentation for the various synapse specific admin
-APIs available.
-
-Authenticating as a server admin
---------------------------------
-
-Many of the API calls in the admin api will require an `access_token` for a
-server admin. (Note that a server admin is distinct from a room admin.)
-
-A user can be marked as a server admin by updating the database directly, e.g.:
-
-.. code-block:: sql
+**Note**: The latest documentation can be viewed `here <https://matrix-org.github.io/synapse>`_.
+See `docs/README.md <../docs/README.md>`_ for more information.
- UPDATE users SET admin = 1 WHERE name = '@foo:bar.com';
+**Please update links to point to the website instead.** Existing files in this directory
+are preserved to maintain historical links, but may be moved in the future.
-A new server admin user can also be created using the
-``register_new_matrix_user`` script.
-
-Finding your user's `access_token` is client-dependent, but will usually be shown in the client's settings.
-
-Once you have your `access_token`, to include it in a request, the best option is to add the token to a request header:
-
-``curl --header "Authorization: Bearer <access_token>" <the_rest_of_your_API_request>``
+This directory includes documentation for the various synapse specific admin
+APIs available. Updates to the existing Admin API documentation should still
+be made to these files, but any new documentation files should instead be placed under
+`docs/usage/administration/admin_api <../docs/usage/administration/admin_api>`_.
-Fore more details, please refer to the complete `matrix spec documentation <https://matrix.org/docs/spec/client_server/r0.5.0#using-access-tokens>`_.
diff --git a/docs/admin_api/account_validity.md b/docs/admin_api/account_validity.md
new file mode 100644
index 0000000000..b74b5d0c1a
--- /dev/null
+++ b/docs/admin_api/account_validity.md
@@ -0,0 +1,42 @@
+# Account validity API
+
+This API allows a server administrator to manage the validity of an account. To
+use it, you must enable the account validity feature (under
+`account_validity`) in Synapse's configuration.
+
+## Renew account
+
+This API extends the validity of an account by as much time as configured in the
+`period` parameter from the `account_validity` configuration.
+
+The API is:
+
+```
+POST /_synapse/admin/v1/account_validity/validity
+```
+
+with the following body:
+
+```json
+{
+ "user_id": "<user ID for the account to renew>",
+ "expiration_ts": 0,
+ "enable_renewal_emails": true
+}
+```
+
+
+`expiration_ts` is an optional parameter and overrides the expiration date,
+which otherwise defaults to now + validity period.
+
+`enable_renewal_emails` is also an optional parameter and enables/disables
+sending renewal emails to the user. Defaults to true.
+
+The API returns with the new expiration date for this account, as a timestamp in
+milliseconds since epoch:
+
+```json
+{
+ "expiration_ts": 0
+}
+```
diff --git a/docs/admin_api/account_validity.rst b/docs/admin_api/account_validity.rst
deleted file mode 100644
index 7559de4c57..0000000000
--- a/docs/admin_api/account_validity.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-Account validity API
-====================
-
-This API allows a server administrator to manage the validity of an account. To
-use it, you must enable the account validity feature (under
-``account_validity``) in Synapse's configuration.
-
-Renew account
--------------
-
-This API extends the validity of an account by as much time as configured in the
-``period`` parameter from the ``account_validity`` configuration.
-
-The API is::
-
- POST /_synapse/admin/v1/account_validity/validity
-
-with the following body:
-
-.. code:: json
-
- {
- "user_id": "<user ID for the account to renew>",
- "expiration_ts": 0,
- "enable_renewal_emails": true
- }
-
-
-``expiration_ts`` is an optional parameter and overrides the expiration date,
-which otherwise defaults to now + validity period.
-
-``enable_renewal_emails`` is also an optional parameter and enables/disables
-sending renewal emails to the user. Defaults to true.
-
-The API returns with the new expiration date for this account, as a timestamp in
-milliseconds since epoch:
-
-.. code:: json
-
- {
- "expiration_ts": 0
- }
diff --git a/docs/admin_api/delete_group.md b/docs/admin_api/delete_group.md
index c061678e75..9c335ff759 100644
--- a/docs/admin_api/delete_group.md
+++ b/docs/admin_api/delete_group.md
@@ -11,4 +11,4 @@ POST /_synapse/admin/v1/delete_group/<group_id>
```
To use it, you will need to authenticate by providing an `access_token` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
diff --git a/docs/admin_api/event_reports.md b/docs/admin_api/event_reports.md
index bfec06f755..186139185e 100644
--- a/docs/admin_api/event_reports.md
+++ b/docs/admin_api/event_reports.md
@@ -7,7 +7,7 @@ The api is:
GET /_synapse/admin/v1/event_reports?from=0&limit=10
```
To use it, you will need to authenticate by providing an `access_token` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
It returns a JSON body like the following:
@@ -95,7 +95,7 @@ The api is:
GET /_synapse/admin/v1/event_reports/<report_id>
```
To use it, you will need to authenticate by providing an `access_token` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
It returns a JSON body like the following:
diff --git a/docs/admin_api/media_admin_api.md b/docs/admin_api/media_admin_api.md
index d1b7e390d5..9ab5269881 100644
--- a/docs/admin_api/media_admin_api.md
+++ b/docs/admin_api/media_admin_api.md
@@ -4,6 +4,7 @@
* [List all media uploaded by a user](#list-all-media-uploaded-by-a-user)
- [Quarantine media](#quarantine-media)
* [Quarantining media by ID](#quarantining-media-by-id)
+ * [Remove media from quarantine by ID](#remove-media-from-quarantine-by-id)
* [Quarantining media in a room](#quarantining-media-in-a-room)
* [Quarantining all media of a user](#quarantining-all-media-of-a-user)
* [Protecting media from being quarantined](#protecting-media-from-being-quarantined)
@@ -27,7 +28,7 @@ The API is:
GET /_synapse/admin/v1/room/<room_id>/media
```
To use it, you will need to authenticate by providing an `access_token` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
The API returns a JSON body like the following:
```json
@@ -77,6 +78,27 @@ Response:
{}
```
+## Remove media from quarantine by ID
+
+This API removes a single piece of local or remote media from quarantine.
+
+Request:
+
+```
+POST /_synapse/admin/v1/media/unquarantine/<server_name>/<media_id>
+
+{}
+```
+
+Where `server_name` is in the form of `example.org`, and `media_id` is in the
+form of `abcdefg12345...`.
+
+Response:
+
+```json
+{}
+```
+
## Quarantining media in a room
This API quarantines all local and remote media in a room.
@@ -289,7 +311,7 @@ The following fields are returned in the JSON response body:
* `deleted`: integer - The number of media items successfully deleted
To use it, you will need to authenticate by providing an `access_token` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
If the user re-requests purged remote media, synapse will re-request the media
from the originating server.
diff --git a/docs/admin_api/purge_history_api.rst b/docs/admin_api/purge_history_api.md
index 92cd05f2a0..25decc3e61 100644
--- a/docs/admin_api/purge_history_api.rst
+++ b/docs/admin_api/purge_history_api.md
@@ -1,5 +1,4 @@
-Purge History API
-=================
+# Purge History API
The purge history API allows server admins to purge historic events from their
database, reclaiming disk space.
@@ -13,10 +12,12 @@ delete the last message in a room.
The API is:
-``POST /_synapse/admin/v1/purge_history/<room_id>[/<event_id>]``
+```
+POST /_synapse/admin/v1/purge_history/<room_id>[/<event_id>]
+```
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
By default, events sent by local users are not deleted, as they may represent
the only copies of this content in existence. (Events sent by remote users are
@@ -24,54 +25,54 @@ deleted.)
Room state data (such as joins, leaves, topic) is always preserved.
-To delete local message events as well, set ``delete_local_events`` in the body:
+To delete local message events as well, set `delete_local_events` in the body:
-.. code:: json
-
- {
- "delete_local_events": true
- }
+```
+{
+ "delete_local_events": true
+}
+```
The caller must specify the point in the room to purge up to. This can be
specified by including an event_id in the URI, or by setting a
-``purge_up_to_event_id`` or ``purge_up_to_ts`` in the request body. If an event
+`purge_up_to_event_id` or `purge_up_to_ts` in the request body. If an event
id is given, that event (and others at the same graph depth) will be retained.
-If ``purge_up_to_ts`` is given, it should be a timestamp since the unix epoch,
+If `purge_up_to_ts` is given, it should be a timestamp since the unix epoch,
in milliseconds.
The API starts the purge running, and returns immediately with a JSON body with
a purge id:
-.. code:: json
-
- {
- "purge_id": "<opaque id>"
- }
+```json
+{
+ "purge_id": "<opaque id>"
+}
+```
-Purge status query
-------------------
+## Purge status query
It is possible to poll for updates on recent purges with a second API;
-``GET /_synapse/admin/v1/purge_history_status/<purge_id>``
+```
+GET /_synapse/admin/v1/purge_history_status/<purge_id>
+```
-Again, you will need to authenticate by providing an ``access_token`` for a
+Again, you will need to authenticate by providing an `access_token` for a
server admin.
This API returns a JSON body like the following:
-.. code:: json
-
- {
- "status": "active"
- }
+```json
+{
+ "status": "active"
+}
+```
-The status will be one of ``active``, ``complete``, or ``failed``.
+The status will be one of `active`, `complete`, or `failed`.
-Reclaim disk space (Postgres)
------------------------------
+## Reclaim disk space (Postgres)
To reclaim the disk space and return it to the operating system, you need to run
`VACUUM FULL;` on the database.
-https://www.postgresql.org/docs/current/sql-vacuum.html
+<https://www.postgresql.org/docs/current/sql-vacuum.html>
diff --git a/docs/admin_api/register_api.md b/docs/admin_api/register_api.md
new file mode 100644
index 0000000000..c346090bb1
--- /dev/null
+++ b/docs/admin_api/register_api.md
@@ -0,0 +1,73 @@
+# Shared-Secret Registration
+
+This API allows for the creation of users in an administrative and
+non-interactive way. This is generally used for bootstrapping a Synapse
+instance with administrator accounts.
+
+To authenticate yourself to the server, you will need both the shared secret
+(`registration_shared_secret` in the homeserver configuration), and a
+one-time nonce. If the registration shared secret is not configured, this API
+is not enabled.
+
+To fetch the nonce, you need to request one from the API:
+
+```
+> GET /_synapse/admin/v1/register
+
+< {"nonce": "thisisanonce"}
+```
+
+Once you have the nonce, you can make a `POST` to the same URL with a JSON
+body containing the nonce, username, password, whether they are an admin
+(optional, False by default), and a HMAC digest of the content. Also you can
+set the displayname (optional, `username` by default).
+
+As an example:
+
+```
+> POST /_synapse/admin/v1/register
+> {
+ "nonce": "thisisanonce",
+ "username": "pepper_roni",
+ "displayname": "Pepper Roni",
+ "password": "pizza",
+ "admin": true,
+ "mac": "mac_digest_here"
+ }
+
+< {
+ "access_token": "token_here",
+ "user_id": "@pepper_roni:localhost",
+ "home_server": "test",
+ "device_id": "device_id_here"
+ }
+```
+
+The MAC is the hex digest output of the HMAC-SHA1 algorithm, with the key being
+the shared secret and the content being the nonce, user, password, either the
+string "admin" or "notadmin", and optionally the user_type
+each separated by NULs. For an example of generation in Python:
+
+```python
+import hmac, hashlib
+
+def generate_mac(nonce, user, password, admin=False, user_type=None):
+
+ mac = hmac.new(
+ key=shared_secret,
+ digestmod=hashlib.sha1,
+ )
+
+ mac.update(nonce.encode('utf8'))
+ mac.update(b"\x00")
+ mac.update(user.encode('utf8'))
+ mac.update(b"\x00")
+ mac.update(password.encode('utf8'))
+ mac.update(b"\x00")
+ mac.update(b"admin" if admin else b"notadmin")
+ if user_type:
+ mac.update(b"\x00")
+ mac.update(user_type.encode('utf8'))
+
+ return mac.hexdigest()
+```
\ No newline at end of file
diff --git a/docs/admin_api/register_api.rst b/docs/admin_api/register_api.rst
deleted file mode 100644
index c3057b204b..0000000000
--- a/docs/admin_api/register_api.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-Shared-Secret Registration
-==========================
-
-This API allows for the creation of users in an administrative and
-non-interactive way. This is generally used for bootstrapping a Synapse
-instance with administrator accounts.
-
-To authenticate yourself to the server, you will need both the shared secret
-(``registration_shared_secret`` in the homeserver configuration), and a
-one-time nonce. If the registration shared secret is not configured, this API
-is not enabled.
-
-To fetch the nonce, you need to request one from the API::
-
- > GET /_synapse/admin/v1/register
-
- < {"nonce": "thisisanonce"}
-
-Once you have the nonce, you can make a ``POST`` to the same URL with a JSON
-body containing the nonce, username, password, whether they are an admin
-(optional, False by default), and a HMAC digest of the content. Also you can
-set the displayname (optional, ``username`` by default).
-
-As an example::
-
- > POST /_synapse/admin/v1/register
- > {
- "nonce": "thisisanonce",
- "username": "pepper_roni",
- "displayname": "Pepper Roni",
- "password": "pizza",
- "admin": true,
- "mac": "mac_digest_here"
- }
-
- < {
- "access_token": "token_here",
- "user_id": "@pepper_roni:localhost",
- "home_server": "test",
- "device_id": "device_id_here"
- }
-
-The MAC is the hex digest output of the HMAC-SHA1 algorithm, with the key being
-the shared secret and the content being the nonce, user, password, either the
-string "admin" or "notadmin", and optionally the user_type
-each separated by NULs. For an example of generation in Python::
-
- import hmac, hashlib
-
- def generate_mac(nonce, user, password, admin=False, user_type=None):
-
- mac = hmac.new(
- key=shared_secret,
- digestmod=hashlib.sha1,
- )
-
- mac.update(nonce.encode('utf8'))
- mac.update(b"\x00")
- mac.update(user.encode('utf8'))
- mac.update(b"\x00")
- mac.update(password.encode('utf8'))
- mac.update(b"\x00")
- mac.update(b"admin" if admin else b"notadmin")
- if user_type:
- mac.update(b"\x00")
- mac.update(user_type.encode('utf8'))
-
- return mac.hexdigest()
diff --git a/docs/admin_api/room_membership.md b/docs/admin_api/room_membership.md
index b6746ff5e4..ed40366099 100644
--- a/docs/admin_api/room_membership.md
+++ b/docs/admin_api/room_membership.md
@@ -24,7 +24,7 @@ POST /_synapse/admin/v1/join/<room_id_or_alias>
```
To use it, you will need to authenticate by providing an `access_token` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
Response:
diff --git a/docs/admin_api/rooms.md b/docs/admin_api/rooms.md
index 5721210fee..dc007fa00e 100644
--- a/docs/admin_api/rooms.md
+++ b/docs/admin_api/rooms.md
@@ -443,7 +443,7 @@ with a body of:
```
To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see [README.rst](README.rst).
+server admin: see [Admin API](../../usage/administration/admin_api).
A response body like the following is returned:
diff --git a/docs/admin_api/statistics.md b/docs/admin_api/statistics.md
index d398a120fb..d93d52a3ac 100644
--- a/docs/admin_api/statistics.md
+++ b/docs/admin_api/statistics.md
@@ -10,7 +10,7 @@ GET /_synapse/admin/v1/statistics/users/media
```
To use it, you will need to authenticate by providing an `access_token`
-for a server admin: see [README.rst](README.rst).
+for a server admin: see [Admin API](../../usage/administration/admin_api).
A response body like the following is returned:
diff --git a/docs/admin_api/user_admin_api.md b/docs/admin_api/user_admin_api.md
new file mode 100644
index 0000000000..c835e4a0cd
--- /dev/null
+++ b/docs/admin_api/user_admin_api.md
@@ -0,0 +1,1001 @@
+# User Admin API
+
+## Query User Account
+
+This API returns information about a specific user account.
+
+The api is:
+
+```
+GET /_synapse/admin/v2/users/<user_id>
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+It returns a JSON body like the following:
+
+```json
+{
+ "displayname": "User",
+ "threepids": [
+ {
+ "medium": "email",
+ "address": "<user_mail_1>"
+ },
+ {
+ "medium": "email",
+ "address": "<user_mail_2>"
+ }
+ ],
+ "avatar_url": "<avatar_url>",
+ "admin": 0,
+ "deactivated": 0,
+ "shadow_banned": 0,
+ "password_hash": "$2b$12$p9B4GkqYdRTPGD",
+ "creation_ts": 1560432506,
+ "appservice_id": null,
+ "consent_server_notice_sent": null,
+ "consent_version": null
+}
+```
+
+URL parameters:
+
+- `user_id`: fully-qualified user id: for example, `@user:server.com`.
+
+## Create or modify Account
+
+This API allows an administrator to create or modify a user account with a
+specific `user_id`.
+
+This api is:
+
+```
+PUT /_synapse/admin/v2/users/<user_id>
+```
+
+with a body of:
+
+```json
+{
+ "password": "user_password",
+ "displayname": "User",
+ "threepids": [
+ {
+ "medium": "email",
+ "address": "<user_mail_1>"
+ },
+ {
+ "medium": "email",
+ "address": "<user_mail_2>"
+ }
+ ],
+ "avatar_url": "<avatar_url>",
+ "admin": false,
+ "deactivated": false
+}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+URL parameters:
+
+- `user_id`: fully-qualified user id: for example, `@user:server.com`.
+
+Body parameters:
+
+- `password`, optional. If provided, the user's password is updated and all
+ devices are logged out.
+
+- `displayname`, optional, defaults to the value of `user_id`.
+
+- `threepids`, optional, allows setting the third-party IDs (email, msisdn)
+ belonging to a user.
+
+- `avatar_url`, optional, must be a
+ [MXC URI](https://matrix.org/docs/spec/client_server/r0.6.0#matrix-content-mxc-uris).
+
+- `admin`, optional, defaults to `false`.
+
+- `deactivated`, optional. If unspecified, deactivation state will be left
+ unchanged on existing accounts and set to `false` for new accounts.
+ A user cannot be erased by deactivating with this API. For details on
+ deactivating users see [Deactivate Account](#deactivate-account).
+
+If the user already exists then optional parameters default to the current value.
+
+In order to re-activate an account `deactivated` must be set to `false`. If
+users do not login via single-sign-on, a new `password` must be provided.
+
+## List Accounts
+
+This API returns all local user accounts.
+By default, the response is ordered by ascending user ID.
+
+```
+GET /_synapse/admin/v2/users?from=0&limit=10&guests=false
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "users": [
+ {
+ "name": "<user_id1>",
+ "is_guest": 0,
+ "admin": 0,
+ "user_type": null,
+ "deactivated": 0,
+ "shadow_banned": 0,
+ "displayname": "<User One>",
+ "avatar_url": null
+ }, {
+ "name": "<user_id2>",
+ "is_guest": 0,
+ "admin": 1,
+ "user_type": null,
+ "deactivated": 0,
+ "shadow_banned": 0,
+ "displayname": "<User Two>",
+ "avatar_url": "<avatar_url>"
+ }
+ ],
+ "next_token": "100",
+ "total": 200
+}
+```
+
+To paginate, check for `next_token` and if present, call the endpoint again
+with `from` set to the value of `next_token`. This will return a new page.
+
+If the endpoint does not return a `next_token` then there are no more users
+to paginate through.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - Is optional and filters to only return users with user IDs
+ that contain this value. This parameter is ignored when using the `name` parameter.
+- `name` - Is optional and filters to only return users with user ID localparts
+ **or** displaynames that contain this value.
+- `guests` - string representing a bool - Is optional and if `false` will **exclude** guest users.
+ Defaults to `true` to include guest users.
+- `deactivated` - string representing a bool - Is optional and if `true` will **include** deactivated users.
+ Defaults to `false` to exclude deactivated users.
+- `limit` - string representing a positive integer - Is optional but is used for pagination,
+ denoting the maximum number of items to return in this call. Defaults to `100`.
+- `from` - string representing a positive integer - Is optional but used for pagination,
+ denoting the offset in the returned results. This should be treated as an opaque value and
+ not explicitly set to anything other than the return value of `next_token` from a previous call.
+ Defaults to `0`.
+- `order_by` - The method by which to sort the returned list of users.
+ If the ordered field has duplicates, the second order is always by ascending `name`,
+ which guarantees a stable ordering. Valid values are:
+
+ - `name` - Users are ordered alphabetically by `name`. This is the default.
+ - `is_guest` - Users are ordered by `is_guest` status.
+ - `admin` - Users are ordered by `admin` status.
+ - `user_type` - Users are ordered alphabetically by `user_type`.
+ - `deactivated` - Users are ordered by `deactivated` status.
+ - `shadow_banned` - Users are ordered by `shadow_banned` status.
+ - `displayname` - Users are ordered alphabetically by `displayname`.
+ - `avatar_url` - Users are ordered alphabetically by avatar URL.
+
+- `dir` - Direction of media order. Either `f` for forwards or `b` for backwards.
+ Setting this value to `b` will reverse the above sort order. Defaults to `f`.
+
+Caution. The database only has indexes on the columns `name` and `created_ts`.
+This means that if a different sort order is used (`is_guest`, `admin`,
+`user_type`, `deactivated`, `shadow_banned`, `avatar_url` or `displayname`),
+this can cause a large load on the database, especially for large environments.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `users` - An array of objects, each containing information about an user.
+ User objects contain the following fields:
+
+ - `name` - string - Fully-qualified user ID (ex. `@user:server.com`).
+ - `is_guest` - bool - Status if that user is a guest account.
+ - `admin` - bool - Status if that user is a server administrator.
+ - `user_type` - string - Type of the user. Normal users are type `None`.
+ This allows user type specific behaviour. There are also types `support` and `bot`.
+ - `deactivated` - bool - Status if that user has been marked as deactivated.
+ - `shadow_banned` - bool - Status if that user has been marked as shadow banned.
+ - `displayname` - string - The user's display name if they have set one.
+ - `avatar_url` - string - The user's avatar URL if they have set one.
+
+- `next_token`: string representing a positive integer - Indication for pagination. See above.
+- `total` - integer - Total number of media.
+
+
+## Query current sessions for a user
+
+This API returns information about the active sessions for a specific user.
+
+The endpoints are:
+
+```
+GET /_synapse/admin/v1/whois/<user_id>
+```
+
+and:
+
+```
+GET /_matrix/client/r0/admin/whois/<userId>
+```
+
+See also: [Client Server
+API Whois](https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid).
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+It returns a JSON body like the following:
+
+```json
+{
+ "user_id": "<user_id>",
+ "devices": {
+ "": {
+ "sessions": [
+ {
+ "connections": [
+ {
+ "ip": "1.2.3.4",
+ "last_seen": 1417222374433,
+ "user_agent": "Mozilla/5.0 ..."
+ },
+ {
+ "ip": "1.2.3.10",
+ "last_seen": 1417222374500,
+ "user_agent": "Dalvik/2.1.0 ..."
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+```
+
+`last_seen` is measured in milliseconds since the Unix epoch.
+
+## Deactivate Account
+
+This API deactivates an account. It removes active access tokens, resets the
+password, and deletes third-party IDs (to prevent the user requesting a
+password reset).
+
+It can also mark the user as GDPR-erased. This means messages sent by the
+user will still be visible by anyone that was in the room when these messages
+were sent, but hidden from users joining the room afterwards.
+
+The api is:
+
+```
+POST /_synapse/admin/v1/deactivate/<user_id>
+```
+
+with a body of:
+
+```json
+{
+ "erase": true
+}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+The erase parameter is optional and defaults to `false`.
+An empty body may be passed for backwards compatibility.
+
+The following actions are performed when deactivating an user:
+
+- Try to unpind 3PIDs from the identity server
+- Remove all 3PIDs from the homeserver
+- Delete all devices and E2EE keys
+- Delete all access tokens
+- Delete the password hash
+- Removal from all rooms the user is a member of
+- Remove the user from the user directory
+- Reject all pending invites
+- Remove all account validity information related to the user
+
+The following additional actions are performed during deactivation if `erase`
+is set to `true`:
+
+- Remove the user's display name
+- Remove the user's avatar URL
+- Mark the user as erased
+
+
+## Reset password
+
+Changes the password of another user. This will automatically log the user out of all their devices.
+
+The api is:
+
+```
+POST /_synapse/admin/v1/reset_password/<user_id>
+```
+
+with a body of:
+
+```json
+{
+ "new_password": "<secret>",
+ "logout_devices": true
+}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+The parameter `new_password` is required.
+The parameter `logout_devices` is optional and defaults to `true`.
+
+
+## Get whether a user is a server administrator or not
+
+The api is:
+
+```
+GET /_synapse/admin/v1/users/<user_id>/admin
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "admin": true
+}
+```
+
+
+## Change whether a user is a server administrator or not
+
+Note that you cannot demote yourself.
+
+The api is:
+
+```
+PUT /_synapse/admin/v1/users/<user_id>/admin
+```
+
+with a body of:
+
+```json
+{
+ "admin": true
+}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+
+## List room memberships of a user
+
+Gets a list of all `room_id` that a specific `user_id` is member.
+
+The API is:
+
+```
+GET /_synapse/admin/v1/users/<user_id>/joined_rooms
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+ {
+ "joined_rooms": [
+ "!DuGcnbhHGaSZQoNQR:matrix.org",
+ "!ZtSaPCawyWtxfWiIy:matrix.org"
+ ],
+ "total": 2
+ }
+```
+
+The server returns the list of rooms of which the user and the server
+are member. If the user is local, all the rooms of which the user is
+member are returned.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `joined_rooms` - An array of `room_id`.
+- `total` - Number of rooms.
+
+
+## List media of a user
+Gets a list of all local media that a specific `user_id` has created.
+By default, the response is ordered by descending creation date and ascending media ID.
+The newest media is on top. You can change the order with parameters
+`order_by` and `dir`.
+
+The API is:
+
+```
+GET /_synapse/admin/v1/users/<user_id>/media
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "media": [
+ {
+ "created_ts": 100400,
+ "last_access_ts": null,
+ "media_id": "qXhyRzulkwLsNHTbpHreuEgo",
+ "media_length": 67,
+ "media_type": "image/png",
+ "quarantined_by": null,
+ "safe_from_quarantine": false,
+ "upload_name": "test1.png"
+ },
+ {
+ "created_ts": 200400,
+ "last_access_ts": null,
+ "media_id": "FHfiSnzoINDatrXHQIXBtahw",
+ "media_length": 67,
+ "media_type": "image/png",
+ "quarantined_by": null,
+ "safe_from_quarantine": false,
+ "upload_name": "test2.png"
+ }
+ ],
+ "next_token": 3,
+ "total": 2
+}
+```
+
+To paginate, check for `next_token` and if present, call the endpoint again
+with `from` set to the value of `next_token`. This will return a new page.
+
+If the endpoint does not return a `next_token` then there are no more
+reports to paginate through.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - string - fully qualified: for example, `@user:server.com`.
+- `limit`: string representing a positive integer - Is optional but is used for pagination,
+ denoting the maximum number of items to return in this call. Defaults to `100`.
+- `from`: string representing a positive integer - Is optional but used for pagination,
+ denoting the offset in the returned results. This should be treated as an opaque value and
+ not explicitly set to anything other than the return value of `next_token` from a previous call.
+ Defaults to `0`.
+- `order_by` - The method by which to sort the returned list of media.
+ If the ordered field has duplicates, the second order is always by ascending `media_id`,
+ which guarantees a stable ordering. Valid values are:
+
+ - `media_id` - Media are ordered alphabetically by `media_id`.
+ - `upload_name` - Media are ordered alphabetically by name the media was uploaded with.
+ - `created_ts` - Media are ordered by when the content was uploaded in ms.
+ Smallest to largest. This is the default.
+ - `last_access_ts` - Media are ordered by when the content was last accessed in ms.
+ Smallest to largest.
+ - `media_length` - Media are ordered by length of the media in bytes.
+ Smallest to largest.
+ - `media_type` - Media are ordered alphabetically by MIME-type.
+ - `quarantined_by` - Media are ordered alphabetically by the user ID that
+ initiated the quarantine request for this media.
+ - `safe_from_quarantine` - Media are ordered by the status if this media is safe
+ from quarantining.
+
+- `dir` - Direction of media order. Either `f` for forwards or `b` for backwards.
+ Setting this value to `b` will reverse the above sort order. Defaults to `f`.
+
+If neither `order_by` nor `dir` is set, the default order is newest media on top
+(corresponds to `order_by` = `created_ts` and `dir` = `b`).
+
+Caution. The database only has indexes on the columns `media_id`,
+`user_id` and `created_ts`. This means that if a different sort order is used
+(`upload_name`, `last_access_ts`, `media_length`, `media_type`,
+`quarantined_by` or `safe_from_quarantine`), this can cause a large load on the
+database, especially for large environments.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `media` - An array of objects, each containing information about a media.
+ Media objects contain the following fields:
+
+ - `created_ts` - integer - Timestamp when the content was uploaded in ms.
+ - `last_access_ts` - integer - Timestamp when the content was last accessed in ms.
+ - `media_id` - string - The id used to refer to the media.
+ - `media_length` - integer - Length of the media in bytes.
+ - `media_type` - string - The MIME-type of the media.
+ - `quarantined_by` - string - The user ID that initiated the quarantine request
+ for this media.
+
+ - `safe_from_quarantine` - bool - Status if this media is safe from quarantining.
+ - `upload_name` - string - The name the media was uploaded with.
+
+- `next_token`: integer - Indication for pagination. See above.
+- `total` - integer - Total number of media.
+
+## Login as a user
+
+Get an access token that can be used to authenticate as that user. Useful for
+when admins wish to do actions on behalf of a user.
+
+The API is:
+
+```
+POST /_synapse/admin/v1/users/<user_id>/login
+{}
+```
+
+An optional `valid_until_ms` field can be specified in the request body as an
+integer timestamp that specifies when the token should expire. By default tokens
+do not expire.
+
+A response body like the following is returned:
+
+```json
+{
+ "access_token": "<opaque_access_token_string>"
+}
+```
+
+This API does *not* generate a new device for the user, and so will not appear
+their `/devices` list, and in general the target user should not be able to
+tell they have been logged in as.
+
+To expire the token call the standard `/logout` API with the token.
+
+Note: The token will expire if the *admin* user calls `/logout/all` from any
+of their devices, but the token will *not* expire if the target user does the
+same.
+
+
+## User devices
+
+### List all devices
+Gets information about all devices for a specific `user_id`.
+
+The API is:
+
+```
+GET /_synapse/admin/v2/users/<user_id>/devices
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "devices": [
+ {
+ "device_id": "QBUAZIFURK",
+ "display_name": "android",
+ "last_seen_ip": "1.2.3.4",
+ "last_seen_ts": 1474491775024,
+ "user_id": "<user_id>"
+ },
+ {
+ "device_id": "AUIECTSRND",
+ "display_name": "ios",
+ "last_seen_ip": "1.2.3.5",
+ "last_seen_ts": 1474491775025,
+ "user_id": "<user_id>"
+ }
+ ],
+ "total": 2
+}
+```
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `devices` - An array of objects, each containing information about a device.
+ Device objects contain the following fields:
+
+ - `device_id` - Identifier of device.
+ - `display_name` - Display name set by the user for this device.
+ Absent if no name has been set.
+ - `last_seen_ip` - The IP address where this device was last seen.
+ (May be a few minutes out of date, for efficiency reasons).
+ - `last_seen_ts` - The timestamp (in milliseconds since the unix epoch) when this
+ devices was last seen. (May be a few minutes out of date, for efficiency reasons).
+ - `user_id` - Owner of device.
+
+- `total` - Total number of user's devices.
+
+### Delete multiple devices
+Deletes the given devices for a specific `user_id`, and invalidates
+any access token associated with them.
+
+The API is:
+
+```
+POST /_synapse/admin/v2/users/<user_id>/delete_devices
+
+{
+ "devices": [
+ "QBUAZIFURK",
+ "AUIECTSRND"
+ ],
+}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+An empty JSON dict is returned.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+
+The following fields are required in the JSON request body:
+
+- `devices` - The list of device IDs to delete.
+
+### Show a device
+Gets information on a single device, by `device_id` for a specific `user_id`.
+
+The API is:
+
+```
+GET /_synapse/admin/v2/users/<user_id>/devices/<device_id>
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "device_id": "<device_id>",
+ "display_name": "android",
+ "last_seen_ip": "1.2.3.4",
+ "last_seen_ts": 1474491775024,
+ "user_id": "<user_id>"
+}
+```
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+- `device_id` - The device to retrieve.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `device_id` - Identifier of device.
+- `display_name` - Display name set by the user for this device.
+ Absent if no name has been set.
+- `last_seen_ip` - The IP address where this device was last seen.
+ (May be a few minutes out of date, for efficiency reasons).
+- `last_seen_ts` - The timestamp (in milliseconds since the unix epoch) when this
+ devices was last seen. (May be a few minutes out of date, for efficiency reasons).
+- `user_id` - Owner of device.
+
+### Update a device
+Updates the metadata on the given `device_id` for a specific `user_id`.
+
+The API is:
+
+```
+PUT /_synapse/admin/v2/users/<user_id>/devices/<device_id>
+
+{
+ "display_name": "My other phone"
+}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+An empty JSON dict is returned.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+- `device_id` - The device to update.
+
+The following fields are required in the JSON request body:
+
+- `display_name` - The new display name for this device. If not given,
+ the display name is unchanged.
+
+### Delete a device
+Deletes the given `device_id` for a specific `user_id`,
+and invalidates any access token associated with it.
+
+The API is:
+
+```
+DELETE /_synapse/admin/v2/users/<user_id>/devices/<device_id>
+
+{}
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+An empty JSON dict is returned.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+- `device_id` - The device to delete.
+
+## List all pushers
+Gets information about all pushers for a specific `user_id`.
+
+The API is:
+
+```
+GET /_synapse/admin/v1/users/<user_id>/pushers
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "pushers": [
+ {
+ "app_display_name":"HTTP Push Notifications",
+ "app_id":"m.http",
+ "data": {
+ "url":"example.com"
+ },
+ "device_display_name":"pushy push",
+ "kind":"http",
+ "lang":"None",
+ "profile_tag":"",
+ "pushkey":"a@example.com"
+ }
+ ],
+ "total": 1
+}
+```
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - fully qualified: for example, `@user:server.com`.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `pushers` - An array containing the current pushers for the user
+
+ - `app_display_name` - string - A string that will allow the user to identify
+ what application owns this pusher.
+
+ - `app_id` - string - This is a reverse-DNS style identifier for the application.
+ Max length, 64 chars.
+
+ - `data` - A dictionary of information for the pusher implementation itself.
+
+ - `url` - string - Required if `kind` is `http`. The URL to use to send
+ notifications to.
+
+ - `format` - string - The format to use when sending notifications to the
+ Push Gateway.
+
+ - `device_display_name` - string - A string that will allow the user to identify
+ what device owns this pusher.
+
+ - `profile_tag` - string - This string determines which set of device specific rules
+ this pusher executes.
+
+ - `kind` - string - The kind of pusher. "http" is a pusher that sends HTTP pokes.
+ - `lang` - string - The preferred language for receiving notifications
+ (e.g. 'en' or 'en-US')
+
+ - `profile_tag` - string - This string determines which set of device specific rules
+ this pusher executes.
+
+ - `pushkey` - string - This is a unique identifier for this pusher.
+ Max length, 512 bytes.
+
+- `total` - integer - Number of pushers.
+
+See also the
+[Client-Server API Spec on pushers](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers).
+
+## Shadow-banning users
+
+Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
+A shadow-banned users receives successful responses to their client-server API requests,
+but the events are not propagated into rooms. This can be an effective tool as it
+(hopefully) takes longer for the user to realise they are being moderated before
+pivoting to another account.
+
+Shadow-banning a user should be used as a tool of last resort and may lead to confusing
+or broken behaviour for the client. A shadow-banned user will not receive any
+notification and it is generally more appropriate to ban or kick abusive users.
+A shadow-banned user will be unable to contact anyone on the server.
+
+The API is:
+
+```
+POST /_synapse/admin/v1/users/<user_id>/shadow_ban
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+An empty JSON dict is returned.
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - The fully qualified MXID: for example, `@user:server.com`. The user must
+ be local.
+
+## Override ratelimiting for users
+
+This API allows to override or disable ratelimiting for a specific user.
+There are specific APIs to set, get and delete a ratelimit.
+
+### Get status of ratelimit
+
+The API is:
+
+```
+GET /_synapse/admin/v1/users/<user_id>/override_ratelimit
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "messages_per_second": 0,
+ "burst_count": 0
+}
+```
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - The fully qualified MXID: for example, `@user:server.com`. The user must
+ be local.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `messages_per_second` - integer - The number of actions that can
+ be performed in a second. `0` mean that ratelimiting is disabled for this user.
+- `burst_count` - integer - How many actions that can be performed before
+ being limited.
+
+If **no** custom ratelimit is set, an empty JSON dict is returned.
+
+```json
+{}
+```
+
+### Set ratelimit
+
+The API is:
+
+```
+POST /_synapse/admin/v1/users/<user_id>/override_ratelimit
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+A response body like the following is returned:
+
+```json
+{
+ "messages_per_second": 0,
+ "burst_count": 0
+}
+```
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - The fully qualified MXID: for example, `@user:server.com`. The user must
+ be local.
+
+Body parameters:
+
+- `messages_per_second` - positive integer, optional. The number of actions that can
+ be performed in a second. Defaults to `0`.
+- `burst_count` - positive integer, optional. How many actions that can be performed
+ before being limited. Defaults to `0`.
+
+To disable users' ratelimit set both values to `0`.
+
+**Response**
+
+The following fields are returned in the JSON response body:
+
+- `messages_per_second` - integer - The number of actions that can
+ be performed in a second.
+- `burst_count` - integer - How many actions that can be performed before
+ being limited.
+
+### Delete ratelimit
+
+The API is:
+
+```
+DELETE /_synapse/admin/v1/users/<user_id>/override_ratelimit
+```
+
+To use it, you will need to authenticate by providing an `access_token` for a
+server admin: [Admin API](../../usage/administration/admin_api)
+
+An empty JSON dict is returned.
+
+```json
+{}
+```
+
+**Parameters**
+
+The following parameters should be set in the URL:
+
+- `user_id` - The fully qualified MXID: for example, `@user:server.com`. The user must
+ be local.
+
diff --git a/docs/admin_api/user_admin_api.rst b/docs/admin_api/user_admin_api.rst
deleted file mode 100644
index dbce9c90b6..0000000000
--- a/docs/admin_api/user_admin_api.rst
+++ /dev/null
@@ -1,981 +0,0 @@
-.. contents::
-
-Query User Account
-==================
-
-This API returns information about a specific user account.
-
-The api is::
-
- GET /_synapse/admin/v2/users/<user_id>
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-It returns a JSON body like the following:
-
-.. code:: json
-
- {
- "displayname": "User",
- "threepids": [
- {
- "medium": "email",
- "address": "<user_mail_1>"
- },
- {
- "medium": "email",
- "address": "<user_mail_2>"
- }
- ],
- "avatar_url": "<avatar_url>",
- "admin": 0,
- "deactivated": 0,
- "shadow_banned": 0,
- "password_hash": "$2b$12$p9B4GkqYdRTPGD",
- "creation_ts": 1560432506,
- "appservice_id": null,
- "consent_server_notice_sent": null,
- "consent_version": null
- }
-
-URL parameters:
-
-- ``user_id``: fully-qualified user id: for example, ``@user:server.com``.
-
-Create or modify Account
-========================
-
-This API allows an administrator to create or modify a user account with a
-specific ``user_id``.
-
-This api is::
-
- PUT /_synapse/admin/v2/users/<user_id>
-
-with a body of:
-
-.. code:: json
-
- {
- "password": "user_password",
- "displayname": "User",
- "threepids": [
- {
- "medium": "email",
- "address": "<user_mail_1>"
- },
- {
- "medium": "email",
- "address": "<user_mail_2>"
- }
- ],
- "avatar_url": "<avatar_url>",
- "admin": false,
- "deactivated": false
- }
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-URL parameters:
-
-- ``user_id``: fully-qualified user id: for example, ``@user:server.com``.
-
-Body parameters:
-
-- ``password``, optional. If provided, the user's password is updated and all
- devices are logged out.
-
-- ``displayname``, optional, defaults to the value of ``user_id``.
-
-- ``threepids``, optional, allows setting the third-party IDs (email, msisdn)
- belonging to a user.
-
-- ``avatar_url``, optional, must be a
- `MXC URI <https://matrix.org/docs/spec/client_server/r0.6.0#matrix-content-mxc-uris>`_.
-
-- ``admin``, optional, defaults to ``false``.
-
-- ``deactivated``, optional. If unspecified, deactivation state will be left
- unchanged on existing accounts and set to ``false`` for new accounts.
- A user cannot be erased by deactivating with this API. For details on deactivating users see
- `Deactivate Account <#deactivate-account>`_.
-
-If the user already exists then optional parameters default to the current value.
-
-In order to re-activate an account ``deactivated`` must be set to ``false``. If
-users do not login via single-sign-on, a new ``password`` must be provided.
-
-List Accounts
-=============
-
-This API returns all local user accounts.
-By default, the response is ordered by ascending user ID.
-
-The API is::
-
- GET /_synapse/admin/v2/users?from=0&limit=10&guests=false
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "users": [
- {
- "name": "<user_id1>",
- "is_guest": 0,
- "admin": 0,
- "user_type": null,
- "deactivated": 0,
- "shadow_banned": 0,
- "displayname": "<User One>",
- "avatar_url": null
- }, {
- "name": "<user_id2>",
- "is_guest": 0,
- "admin": 1,
- "user_type": null,
- "deactivated": 0,
- "shadow_banned": 0,
- "displayname": "<User Two>",
- "avatar_url": "<avatar_url>"
- }
- ],
- "next_token": "100",
- "total": 200
- }
-
-To paginate, check for ``next_token`` and if present, call the endpoint again
-with ``from`` set to the value of ``next_token``. This will return a new page.
-
-If the endpoint does not return a ``next_token`` then there are no more users
-to paginate through.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - Is optional and filters to only return users with user IDs
- that contain this value. This parameter is ignored when using the ``name`` parameter.
-- ``name`` - Is optional and filters to only return users with user ID localparts
- **or** displaynames that contain this value.
-- ``guests`` - string representing a bool - Is optional and if ``false`` will **exclude** guest users.
- Defaults to ``true`` to include guest users.
-- ``deactivated`` - string representing a bool - Is optional and if ``true`` will **include** deactivated users.
- Defaults to ``false`` to exclude deactivated users.
-- ``limit`` - string representing a positive integer - Is optional but is used for pagination,
- denoting the maximum number of items to return in this call. Defaults to ``100``.
-- ``from`` - string representing a positive integer - Is optional but used for pagination,
- denoting the offset in the returned results. This should be treated as an opaque value and
- not explicitly set to anything other than the return value of ``next_token`` from a previous call.
- Defaults to ``0``.
-- ``order_by`` - The method by which to sort the returned list of users.
- If the ordered field has duplicates, the second order is always by ascending ``name``,
- which guarantees a stable ordering. Valid values are:
-
- - ``name`` - Users are ordered alphabetically by ``name``. This is the default.
- - ``is_guest`` - Users are ordered by ``is_guest`` status.
- - ``admin`` - Users are ordered by ``admin`` status.
- - ``user_type`` - Users are ordered alphabetically by ``user_type``.
- - ``deactivated`` - Users are ordered by ``deactivated`` status.
- - ``shadow_banned`` - Users are ordered by ``shadow_banned`` status.
- - ``displayname`` - Users are ordered alphabetically by ``displayname``.
- - ``avatar_url`` - Users are ordered alphabetically by avatar URL.
-
-- ``dir`` - Direction of media order. Either ``f`` for forwards or ``b`` for backwards.
- Setting this value to ``b`` will reverse the above sort order. Defaults to ``f``.
-
-Caution. The database only has indexes on the columns ``name`` and ``created_ts``.
-This means that if a different sort order is used (``is_guest``, ``admin``,
-``user_type``, ``deactivated``, ``shadow_banned``, ``avatar_url`` or ``displayname``),
-this can cause a large load on the database, especially for large environments.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``users`` - An array of objects, each containing information about an user.
- User objects contain the following fields:
-
- - ``name`` - string - Fully-qualified user ID (ex. ``@user:server.com``).
- - ``is_guest`` - bool - Status if that user is a guest account.
- - ``admin`` - bool - Status if that user is a server administrator.
- - ``user_type`` - string - Type of the user. Normal users are type ``None``.
- This allows user type specific behaviour. There are also types ``support`` and ``bot``.
- - ``deactivated`` - bool - Status if that user has been marked as deactivated.
- - ``shadow_banned`` - bool - Status if that user has been marked as shadow banned.
- - ``displayname`` - string - The user's display name if they have set one.
- - ``avatar_url`` - string - The user's avatar URL if they have set one.
-
-- ``next_token``: string representing a positive integer - Indication for pagination. See above.
-- ``total`` - integer - Total number of media.
-
-
-Query current sessions for a user
-=================================
-
-This API returns information about the active sessions for a specific user.
-
-The api is::
-
- GET /_synapse/admin/v1/whois/<user_id>
-
-and::
-
- GET /_matrix/client/r0/admin/whois/<userId>
-
-See also: `Client Server API Whois
-<https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid>`_
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-It returns a JSON body like the following:
-
-.. code:: json
-
- {
- "user_id": "<user_id>",
- "devices": {
- "": {
- "sessions": [
- {
- "connections": [
- {
- "ip": "1.2.3.4",
- "last_seen": 1417222374433,
- "user_agent": "Mozilla/5.0 ..."
- },
- {
- "ip": "1.2.3.10",
- "last_seen": 1417222374500,
- "user_agent": "Dalvik/2.1.0 ..."
- }
- ]
- }
- ]
- }
- }
- }
-
-``last_seen`` is measured in milliseconds since the Unix epoch.
-
-Deactivate Account
-==================
-
-This API deactivates an account. It removes active access tokens, resets the
-password, and deletes third-party IDs (to prevent the user requesting a
-password reset).
-
-It can also mark the user as GDPR-erased. This means messages sent by the
-user will still be visible by anyone that was in the room when these messages
-were sent, but hidden from users joining the room afterwards.
-
-The api is::
-
- POST /_synapse/admin/v1/deactivate/<user_id>
-
-with a body of:
-
-.. code:: json
-
- {
- "erase": true
- }
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-The erase parameter is optional and defaults to ``false``.
-An empty body may be passed for backwards compatibility.
-
-The following actions are performed when deactivating an user:
-
-- Try to unpind 3PIDs from the identity server
-- Remove all 3PIDs from the homeserver
-- Delete all devices and E2EE keys
-- Delete all access tokens
-- Delete the password hash
-- Removal from all rooms the user is a member of
-- Remove the user from the user directory
-- Reject all pending invites
-- Remove all account validity information related to the user
-
-The following additional actions are performed during deactivation if ``erase``
-is set to ``true``:
-
-- Remove the user's display name
-- Remove the user's avatar URL
-- Mark the user as erased
-
-
-Reset password
-==============
-
-Changes the password of another user. This will automatically log the user out of all their devices.
-
-The api is::
-
- POST /_synapse/admin/v1/reset_password/<user_id>
-
-with a body of:
-
-.. code:: json
-
- {
- "new_password": "<secret>",
- "logout_devices": true
- }
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-The parameter ``new_password`` is required.
-The parameter ``logout_devices`` is optional and defaults to ``true``.
-
-Get whether a user is a server administrator or not
-===================================================
-
-
-The api is::
-
- GET /_synapse/admin/v1/users/<user_id>/admin
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "admin": true
- }
-
-
-Change whether a user is a server administrator or not
-======================================================
-
-Note that you cannot demote yourself.
-
-The api is::
-
- PUT /_synapse/admin/v1/users/<user_id>/admin
-
-with a body of:
-
-.. code:: json
-
- {
- "admin": true
- }
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-
-List room memberships of an user
-================================
-Gets a list of all ``room_id`` that a specific ``user_id`` is member.
-
-The API is::
-
- GET /_synapse/admin/v1/users/<user_id>/joined_rooms
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "joined_rooms": [
- "!DuGcnbhHGaSZQoNQR:matrix.org",
- "!ZtSaPCawyWtxfWiIy:matrix.org"
- ],
- "total": 2
- }
-
-The server returns the list of rooms of which the user and the server
-are member. If the user is local, all the rooms of which the user is
-member are returned.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``joined_rooms`` - An array of ``room_id``.
-- ``total`` - Number of rooms.
-
-
-List media of a user
-====================
-Gets a list of all local media that a specific ``user_id`` has created.
-By default, the response is ordered by descending creation date and ascending media ID.
-The newest media is on top. You can change the order with parameters
-``order_by`` and ``dir``.
-
-The API is::
-
- GET /_synapse/admin/v1/users/<user_id>/media
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "media": [
- {
- "created_ts": 100400,
- "last_access_ts": null,
- "media_id": "qXhyRzulkwLsNHTbpHreuEgo",
- "media_length": 67,
- "media_type": "image/png",
- "quarantined_by": null,
- "safe_from_quarantine": false,
- "upload_name": "test1.png"
- },
- {
- "created_ts": 200400,
- "last_access_ts": null,
- "media_id": "FHfiSnzoINDatrXHQIXBtahw",
- "media_length": 67,
- "media_type": "image/png",
- "quarantined_by": null,
- "safe_from_quarantine": false,
- "upload_name": "test2.png"
- }
- ],
- "next_token": 3,
- "total": 2
- }
-
-To paginate, check for ``next_token`` and if present, call the endpoint again
-with ``from`` set to the value of ``next_token``. This will return a new page.
-
-If the endpoint does not return a ``next_token`` then there are no more
-reports to paginate through.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - string - fully qualified: for example, ``@user:server.com``.
-- ``limit``: string representing a positive integer - Is optional but is used for pagination,
- denoting the maximum number of items to return in this call. Defaults to ``100``.
-- ``from``: string representing a positive integer - Is optional but used for pagination,
- denoting the offset in the returned results. This should be treated as an opaque value and
- not explicitly set to anything other than the return value of ``next_token`` from a previous call.
- Defaults to ``0``.
-- ``order_by`` - The method by which to sort the returned list of media.
- If the ordered field has duplicates, the second order is always by ascending ``media_id``,
- which guarantees a stable ordering. Valid values are:
-
- - ``media_id`` - Media are ordered alphabetically by ``media_id``.
- - ``upload_name`` - Media are ordered alphabetically by name the media was uploaded with.
- - ``created_ts`` - Media are ordered by when the content was uploaded in ms.
- Smallest to largest. This is the default.
- - ``last_access_ts`` - Media are ordered by when the content was last accessed in ms.
- Smallest to largest.
- - ``media_length`` - Media are ordered by length of the media in bytes.
- Smallest to largest.
- - ``media_type`` - Media are ordered alphabetically by MIME-type.
- - ``quarantined_by`` - Media are ordered alphabetically by the user ID that
- initiated the quarantine request for this media.
- - ``safe_from_quarantine`` - Media are ordered by the status if this media is safe
- from quarantining.
-
-- ``dir`` - Direction of media order. Either ``f`` for forwards or ``b`` for backwards.
- Setting this value to ``b`` will reverse the above sort order. Defaults to ``f``.
-
-If neither ``order_by`` nor ``dir`` is set, the default order is newest media on top
-(corresponds to ``order_by`` = ``created_ts`` and ``dir`` = ``b``).
-
-Caution. The database only has indexes on the columns ``media_id``,
-``user_id`` and ``created_ts``. This means that if a different sort order is used
-(``upload_name``, ``last_access_ts``, ``media_length``, ``media_type``,
-``quarantined_by`` or ``safe_from_quarantine``), this can cause a large load on the
-database, especially for large environments.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``media`` - An array of objects, each containing information about a media.
- Media objects contain the following fields:
-
- - ``created_ts`` - integer - Timestamp when the content was uploaded in ms.
- - ``last_access_ts`` - integer - Timestamp when the content was last accessed in ms.
- - ``media_id`` - string - The id used to refer to the media.
- - ``media_length`` - integer - Length of the media in bytes.
- - ``media_type`` - string - The MIME-type of the media.
- - ``quarantined_by`` - string - The user ID that initiated the quarantine request
- for this media.
-
- - ``safe_from_quarantine`` - bool - Status if this media is safe from quarantining.
- - ``upload_name`` - string - The name the media was uploaded with.
-
-- ``next_token``: integer - Indication for pagination. See above.
-- ``total`` - integer - Total number of media.
-
-Login as a user
-===============
-
-Get an access token that can be used to authenticate as that user. Useful for
-when admins wish to do actions on behalf of a user.
-
-The API is::
-
- POST /_synapse/admin/v1/users/<user_id>/login
- {}
-
-An optional ``valid_until_ms`` field can be specified in the request body as an
-integer timestamp that specifies when the token should expire. By default tokens
-do not expire.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "access_token": "<opaque_access_token_string>"
- }
-
-
-This API does *not* generate a new device for the user, and so will not appear
-their ``/devices`` list, and in general the target user should not be able to
-tell they have been logged in as.
-
-To expire the token call the standard ``/logout`` API with the token.
-
-Note: The token will expire if the *admin* user calls ``/logout/all`` from any
-of their devices, but the token will *not* expire if the target user does the
-same.
-
-
-User devices
-============
-
-List all devices
-----------------
-Gets information about all devices for a specific ``user_id``.
-
-The API is::
-
- GET /_synapse/admin/v2/users/<user_id>/devices
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "devices": [
- {
- "device_id": "QBUAZIFURK",
- "display_name": "android",
- "last_seen_ip": "1.2.3.4",
- "last_seen_ts": 1474491775024,
- "user_id": "<user_id>"
- },
- {
- "device_id": "AUIECTSRND",
- "display_name": "ios",
- "last_seen_ip": "1.2.3.5",
- "last_seen_ts": 1474491775025,
- "user_id": "<user_id>"
- }
- ],
- "total": 2
- }
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``devices`` - An array of objects, each containing information about a device.
- Device objects contain the following fields:
-
- - ``device_id`` - Identifier of device.
- - ``display_name`` - Display name set by the user for this device.
- Absent if no name has been set.
- - ``last_seen_ip`` - The IP address where this device was last seen.
- (May be a few minutes out of date, for efficiency reasons).
- - ``last_seen_ts`` - The timestamp (in milliseconds since the unix epoch) when this
- devices was last seen. (May be a few minutes out of date, for efficiency reasons).
- - ``user_id`` - Owner of device.
-
-- ``total`` - Total number of user's devices.
-
-Delete multiple devices
-------------------
-Deletes the given devices for a specific ``user_id``, and invalidates
-any access token associated with them.
-
-The API is::
-
- POST /_synapse/admin/v2/users/<user_id>/delete_devices
-
- {
- "devices": [
- "QBUAZIFURK",
- "AUIECTSRND"
- ],
- }
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-An empty JSON dict is returned.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-
-The following fields are required in the JSON request body:
-
-- ``devices`` - The list of device IDs to delete.
-
-Show a device
----------------
-Gets information on a single device, by ``device_id`` for a specific ``user_id``.
-
-The API is::
-
- GET /_synapse/admin/v2/users/<user_id>/devices/<device_id>
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "device_id": "<device_id>",
- "display_name": "android",
- "last_seen_ip": "1.2.3.4",
- "last_seen_ts": 1474491775024,
- "user_id": "<user_id>"
- }
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-- ``device_id`` - The device to retrieve.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``device_id`` - Identifier of device.
-- ``display_name`` - Display name set by the user for this device.
- Absent if no name has been set.
-- ``last_seen_ip`` - The IP address where this device was last seen.
- (May be a few minutes out of date, for efficiency reasons).
-- ``last_seen_ts`` - The timestamp (in milliseconds since the unix epoch) when this
- devices was last seen. (May be a few minutes out of date, for efficiency reasons).
-- ``user_id`` - Owner of device.
-
-Update a device
----------------
-Updates the metadata on the given ``device_id`` for a specific ``user_id``.
-
-The API is::
-
- PUT /_synapse/admin/v2/users/<user_id>/devices/<device_id>
-
- {
- "display_name": "My other phone"
- }
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-An empty JSON dict is returned.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-- ``device_id`` - The device to update.
-
-The following fields are required in the JSON request body:
-
-- ``display_name`` - The new display name for this device. If not given,
- the display name is unchanged.
-
-Delete a device
----------------
-Deletes the given ``device_id`` for a specific ``user_id``,
-and invalidates any access token associated with it.
-
-The API is::
-
- DELETE /_synapse/admin/v2/users/<user_id>/devices/<device_id>
-
- {}
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-An empty JSON dict is returned.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-- ``device_id`` - The device to delete.
-
-List all pushers
-================
-Gets information about all pushers for a specific ``user_id``.
-
-The API is::
-
- GET /_synapse/admin/v1/users/<user_id>/pushers
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "pushers": [
- {
- "app_display_name":"HTTP Push Notifications",
- "app_id":"m.http",
- "data": {
- "url":"example.com"
- },
- "device_display_name":"pushy push",
- "kind":"http",
- "lang":"None",
- "profile_tag":"",
- "pushkey":"a@example.com"
- }
- ],
- "total": 1
- }
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - fully qualified: for example, ``@user:server.com``.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``pushers`` - An array containing the current pushers for the user
-
- - ``app_display_name`` - string - A string that will allow the user to identify
- what application owns this pusher.
-
- - ``app_id`` - string - This is a reverse-DNS style identifier for the application.
- Max length, 64 chars.
-
- - ``data`` - A dictionary of information for the pusher implementation itself.
-
- - ``url`` - string - Required if ``kind`` is ``http``. The URL to use to send
- notifications to.
-
- - ``format`` - string - The format to use when sending notifications to the
- Push Gateway.
-
- - ``device_display_name`` - string - A string that will allow the user to identify
- what device owns this pusher.
-
- - ``profile_tag`` - string - This string determines which set of device specific rules
- this pusher executes.
-
- - ``kind`` - string - The kind of pusher. "http" is a pusher that sends HTTP pokes.
- - ``lang`` - string - The preferred language for receiving notifications
- (e.g. 'en' or 'en-US')
-
- - ``profile_tag`` - string - This string determines which set of device specific rules
- this pusher executes.
-
- - ``pushkey`` - string - This is a unique identifier for this pusher.
- Max length, 512 bytes.
-
-- ``total`` - integer - Number of pushers.
-
-See also `Client-Server API Spec <https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers>`_
-
-Shadow-banning users
-====================
-
-Shadow-banning is a useful tool for moderating malicious or egregiously abusive users.
-A shadow-banned users receives successful responses to their client-server API requests,
-but the events are not propagated into rooms. This can be an effective tool as it
-(hopefully) takes longer for the user to realise they are being moderated before
-pivoting to another account.
-
-Shadow-banning a user should be used as a tool of last resort and may lead to confusing
-or broken behaviour for the client. A shadow-banned user will not receive any
-notification and it is generally more appropriate to ban or kick abusive users.
-A shadow-banned user will be unable to contact anyone on the server.
-
-The API is::
-
- POST /_synapse/admin/v1/users/<user_id>/shadow_ban
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-An empty JSON dict is returned.
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - The fully qualified MXID: for example, ``@user:server.com``. The user must
- be local.
-
-Override ratelimiting for users
-===============================
-
-This API allows to override or disable ratelimiting for a specific user.
-There are specific APIs to set, get and delete a ratelimit.
-
-Get status of ratelimit
------------------------
-
-The API is::
-
- GET /_synapse/admin/v1/users/<user_id>/override_ratelimit
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "messages_per_second": 0,
- "burst_count": 0
- }
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - The fully qualified MXID: for example, ``@user:server.com``. The user must
- be local.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``messages_per_second`` - integer - The number of actions that can
- be performed in a second. `0` mean that ratelimiting is disabled for this user.
-- ``burst_count`` - integer - How many actions that can be performed before
- being limited.
-
-If **no** custom ratelimit is set, an empty JSON dict is returned.
-
-.. code:: json
-
- {}
-
-Set ratelimit
--------------
-
-The API is::
-
- POST /_synapse/admin/v1/users/<user_id>/override_ratelimit
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-A response body like the following is returned:
-
-.. code:: json
-
- {
- "messages_per_second": 0,
- "burst_count": 0
- }
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - The fully qualified MXID: for example, ``@user:server.com``. The user must
- be local.
-
-Body parameters:
-
-- ``messages_per_second`` - positive integer, optional. The number of actions that can
- be performed in a second. Defaults to ``0``.
-- ``burst_count`` - positive integer, optional. How many actions that can be performed
- before being limited. Defaults to ``0``.
-
-To disable users' ratelimit set both values to ``0``.
-
-**Response**
-
-The following fields are returned in the JSON response body:
-
-- ``messages_per_second`` - integer - The number of actions that can
- be performed in a second.
-- ``burst_count`` - integer - How many actions that can be performed before
- being limited.
-
-Delete ratelimit
-----------------
-
-The API is::
-
- DELETE /_synapse/admin/v1/users/<user_id>/override_ratelimit
-
-To use it, you will need to authenticate by providing an ``access_token`` for a
-server admin: see `README.rst <README.rst>`_.
-
-An empty JSON dict is returned.
-
-.. code:: json
-
- {}
-
-**Parameters**
-
-The following parameters should be set in the URL:
-
-- ``user_id`` - The fully qualified MXID: for example, ``@user:server.com``. The user must
- be local.
-
diff --git a/docs/admin_api/version_api.rst b/docs/admin_api/version_api.md
index 833d9028be..efb4a0c0f7 100644
--- a/docs/admin_api/version_api.rst
+++ b/docs/admin_api/version_api.md
@@ -1,20 +1,21 @@
-Version API
-===========
+# Version API
This API returns the running Synapse version and the Python version
on which Synapse is being run. This is useful when a Synapse instance
is behind a proxy that does not forward the 'Server' header (which also
contains Synapse version information).
-The api is::
+The api is:
- GET /_synapse/admin/v1/server_version
+```
+GET /_synapse/admin/v1/server_version
+```
It returns a JSON body like the following:
-.. code:: json
-
- {
- "server_version": "0.99.2rc1 (b=develop, abcdef123)",
- "python_version": "3.6.8"
- }
+```json
+{
+ "server_version": "0.99.2rc1 (b=develop, abcdef123)",
+ "python_version": "3.6.8"
+}
+```
diff --git a/docs/development/contributing_guide.md b/docs/development/contributing_guide.md
new file mode 100644
index 0000000000..ddf0887123
--- /dev/null
+++ b/docs/development/contributing_guide.md
@@ -0,0 +1,7 @@
+<!--
+ Include the contents of CONTRIBUTING.md from the project root (where GitHub likes it
+ to be)
+-->
+# Contributing
+
+{{#include ../../CONTRIBUTING.md}}
diff --git a/docs/development/internal_documentation/README.md b/docs/development/internal_documentation/README.md
new file mode 100644
index 0000000000..51c5fb94d5
--- /dev/null
+++ b/docs/development/internal_documentation/README.md
@@ -0,0 +1,12 @@
+# Internal Documentation
+
+This section covers implementation documentation for various parts of Synapse.
+
+If a developer is planning to make a change to a feature of Synapse, it can be useful for
+general documentation of how that feature is implemented to be available. This saves the
+developer time in place of needing to understand how the feature works by reading the
+code.
+
+Documentation that would be more useful for the perspective of a system administrator,
+rather than a developer who's intending to change to code, should instead be placed
+under the Usage section of the documentation.
\ No newline at end of file
diff --git a/docs/favicon.png b/docs/favicon.png
new file mode 100644
index 0000000000..5f18bf641f
--- /dev/null
+++ b/docs/favicon.png
Binary files differdiff --git a/docs/favicon.svg b/docs/favicon.svg
new file mode 100644
index 0000000000..e571aeb3ed
--- /dev/null
+++ b/docs/favicon.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 199.7 184.2"
+ version="1.1"
+ id="svg62"
+ sodipodi:docname="mdbook-favicon.svg"
+ inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
+ <metadata
+ id="metadata68">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs66" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1026"
+ id="namedview64"
+ showgrid="false"
+ inkscape:zoom="3.2245912"
+ inkscape:cx="84.790185"
+ inkscape:cy="117.96478"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg62" />
+ <style
+ id="style58">
+ @media (prefers-color-scheme: dark) {
+ svg { fill: white; }
+ }
+ </style>
+ <path
+ d="m 189.5,36.8 c 0.2,2.8 0,5.1 -0.6,6.8 L 153,162 c -0.6,2.1 -2,3.7 -4.2,5 -2.2,1.2 -4.4,1.9 -6.7,1.9 H 31.4 c -9.6,0 -15.3,-2.8 -17.3,-8.4 -0.8,-2.2 -0.8,-3.9 0.1,-5.2 0.9,-1.2 2.4,-1.8 4.6,-1.8 H 123 c 7.4,0 12.6,-1.4 15.4,-4.1 2.8,-2.7 5.7,-8.9 8.6,-18.4 L 179.9,22.4 c 1.8,-5.9 1,-11.1 -2.2,-15.6 C 174.5,2.3 169.9,0 164,0 H 72.7 c -1,0 -3.1,0.4 -6.1,1.1 L 66.7,0.7 C 64.5,0.2 62.6,0 61,0.1 c -1.6,0.1 -3,0.5 -4.3,1.4 -1.3,0.9 -2.4,1.8 -3.2,2.8 -0.8,1 -1.5,2.2 -2.3,3.8 -0.8,1.6 -1.4,3 -1.9,4.3 -0.5,1.3 -1.1,2.7 -1.8,4.2 -0.7,1.5 -1.3,2.7 -2,3.7 -0.5,0.6 -1.2,1.5 -2,2.5 -0.8,1 -1.6,2 -2.2,2.8 -0.6,0.8 -0.9,1.5 -1.1,2.2 -0.2,0.7 -0.1,1.8 0.2,3.2 0.3,1.4 0.4,2.4 0.4,3.1 -0.3,3 -1.4,6.9 -3.3,11.6 -1.9,4.7 -3.6,8.1 -5.1,10.1 -0.3,0.4 -1.2,1.3 -2.6,2.7 -1.4,1.4 -2.3,2.6 -2.6,3.7 -0.3,0.4 -0.3,1.5 -0.1,3.4 0.3,1.8 0.4,3.1 0.3,3.8 -0.3,2.7 -1.3,6.3 -3,10.8 -2.406801,6.370944 -3.4,8.2 -5,11 -0.2,0.5 -0.9,1.4 -2,2.8 -1.1,1.4 -1.8,2.5 -2,3.4 -0.2,0.6 -0.1,1.8 0.1,3.4 0.2,1.6 0.2,2.8 -0.1,3.6 -0.6,3 -1.8,6.7 -3.6,11 -1.8,4.3 -3.6,7.9 -5.4,11 -0.5,0.8 -1.1,1.7 -2,2.8 -0.8,1.1 -1.5,2 -2,2.8 -0.5,0.8 -0.8,1.6 -1,2.5 -0.1,0.5 0,1.3 0.4,2.3 0.3,1.1 0.4,1.9 0.4,2.6 -0.1,1.1 -0.2,2.6 -0.5,4.4 -0.2,1.8 -0.4,2.9 -0.4,3.2 -1.8,4.8 -1.7,9.9 0.2,15.2 2.2,6.2 6.2,11.5 11.9,15.8 5.7,4.3 11.7,6.4 17.8,6.4 h 110.7 c 5.2,0 10.1,-1.7 14.7,-5.2 4.6,-3.5 7.7,-7.8 9.2,-12.9 l 33,-108.6 c 1.8,-5.8 1,-10.9 -2.2,-15.5 -1.7,-2.5 -4,-4.2 -7.1,-5.4 z M 38.14858,105.59813 60.882735,41.992545 h 10.8 c 6.340631,0 33.351895,0.778957 70.804135,0.970479 -18.18245,63.254766 0,0 -18.18245,63.254766 -23.00947,-0.10382 -63.362955,-0.6218 -72.55584,-0.51966 -18,0.2 -13.6,-0.1 -13.6,-0.1 z m 80.621,-5.891206 c 15.19043,-50.034423 0,1e-5 15.19043,-50.034423 l -11.90624,-0.13228 2.73304,-9.302941 -44.32863,0.07339 -2.532953,8.036036 -11.321128,-0.18864 -17.955519,51.440073 c 0.02698,0.027 4.954586,0.0514 12.187488,0.0717 l -2.997994,9.804886 c 11.36463,0.0271 1.219679,-0.0736 46.117666,-0.31499 l 2.65246,-9.571696 c 7.08021,0.14819 11.59705,0.13117 12.16138,0.1189 z m -56.149615,-3.855606 13.7,-42.5 h 9.8 l 1.194896,32.99936 23.205109,-32.99936 h 9.9 l -13.6,42.5 h -7.099996 l 12.499996,-35.4 -24.50001,35.4 h -6.799995 l -0.8,-35 -10.8,35 z"
+ id="path60"
+ sodipodi:nodetypes="ccccssccsssccsssccsssssscsssscssscccscscscsccsccccccssssccccccsccsccccccccccccccccccccccccccccc" />
+</svg>
diff --git a/docs/setup/installation.md b/docs/setup/installation.md
new file mode 100644
index 0000000000..8bb1cffd3d
--- /dev/null
+++ b/docs/setup/installation.md
@@ -0,0 +1,7 @@
+<!--
+ Include the contents of INSTALL.md from the project root without moving it, which may
+ break links around the internet. Additionally, note that SUMMARY.md is unable to
+ directly link to content outside of the docs/ directory. So we use this file as a
+ redirection.
+-->
+{{#include ../../INSTALL.md}}
\ No newline at end of file
diff --git a/docs/upgrading/README.md b/docs/upgrading/README.md
new file mode 100644
index 0000000000..258e58cf15
--- /dev/null
+++ b/docs/upgrading/README.md
@@ -0,0 +1,7 @@
+<!--
+ Include the contents of UPGRADE.rst from the project root without moving it, which may
+ break links around the internet. Additionally, note that SUMMARY.md is unable to
+ directly link to content outside of the docs/ directory. So we use this file as a
+ redirection.
+-->
+{{#include ../../UPGRADE.rst}}
\ No newline at end of file
diff --git a/docs/usage/administration/README.md b/docs/usage/administration/README.md
new file mode 100644
index 0000000000..e1e57546ab
--- /dev/null
+++ b/docs/usage/administration/README.md
@@ -0,0 +1,7 @@
+# Administration
+
+This section contains information on managing your Synapse homeserver. This includes:
+
+* Managing users, rooms and media via the Admin API.
+* Setting up metrics and monitoring to give you insight into your homeserver's health.
+* Configuring structured logging.
\ No newline at end of file
diff --git a/docs/usage/administration/admin_api/README.md b/docs/usage/administration/admin_api/README.md
new file mode 100644
index 0000000000..2fca96f8be
--- /dev/null
+++ b/docs/usage/administration/admin_api/README.md
@@ -0,0 +1,29 @@
+# The Admin API
+
+## Authenticate as a server admin
+
+Many of the API calls in the admin api will require an `access_token` for a
+server admin. (Note that a server admin is distinct from a room admin.)
+
+A user can be marked as a server admin by updating the database directly, e.g.:
+
+```sql
+UPDATE users SET admin = 1 WHERE name = '@foo:bar.com';
+```
+
+A new server admin user can also be created using the `register_new_matrix_user`
+command. This is a script that is located in the `scripts/` directory, or possibly
+already on your `$PATH` depending on how Synapse was installed.
+
+Finding your user's `access_token` is client-dependent, but will usually be shown in the client's settings.
+
+## Making an Admin API request
+Once you have your `access_token`, you will need to authenticate each request to an Admin API endpoint by
+providing the token as either a query parameter or a request header. To add it as a request header in cURL:
+
+```sh
+curl --header "Authorization: Bearer <access_token>" <the_rest_of_your_API_request>
+```
+
+For more details on access tokens in Matrix, please refer to the complete
+[matrix spec documentation](https://matrix.org/docs/spec/client_server/r0.6.1#using-access-tokens).
diff --git a/docs/usage/configuration/README.md b/docs/usage/configuration/README.md
new file mode 100644
index 0000000000..41d41167c6
--- /dev/null
+++ b/docs/usage/configuration/README.md
@@ -0,0 +1,4 @@
+# Configuration
+
+This section contains information on tweaking Synapse via the various options in the configuration file. A configuration
+file should have been generated when you [installed Synapse](../../setup/installation.html).
diff --git a/docs/usage/configuration/homeserver_sample_config.md b/docs/usage/configuration/homeserver_sample_config.md
new file mode 100644
index 0000000000..11e806998d
--- /dev/null
+++ b/docs/usage/configuration/homeserver_sample_config.md
@@ -0,0 +1,14 @@
+# Homeserver Sample Configuration File
+
+Below is a sample homeserver configuration file. The homeserver configuration file
+can be tweaked to change the behaviour of your homeserver. A restart of the server is
+generally required to apply any changes made to this file.
+
+Note that the contents below are *not* intended to be copied and used as the basis for
+a real homeserver.yaml. Instead, if you are starting from scratch, please generate
+a fresh config using Synapse by following the instructions in
+[Installation](../../setup/installation.md).
+
+```yaml
+{{#include ../../sample_config.yaml}}
+```
diff --git a/docs/usage/configuration/logging_sample_config.md b/docs/usage/configuration/logging_sample_config.md
new file mode 100644
index 0000000000..4c4bc6fc16
--- /dev/null
+++ b/docs/usage/configuration/logging_sample_config.md
@@ -0,0 +1,14 @@
+# Logging Sample Configuration File
+
+Below is a sample logging configuration file. This file can be tweaked to control how your
+homeserver will output logs. A restart of the server is generally required to apply any
+changes made to this file.
+
+Note that the contents below are *not* intended to be copied and used as the basis for
+a real homeserver.yaml. Instead, if you are starting from scratch, please generate
+a fresh config using Synapse by following the instructions in
+[Installation](../../setup/installation.md).
+
+```yaml
+{{#include ../../sample_log_config.yaml}}
+``__`
\ No newline at end of file
diff --git a/docs/usage/configuration/user_authentication/README.md b/docs/usage/configuration/user_authentication/README.md
new file mode 100644
index 0000000000..087ae053cf
--- /dev/null
+++ b/docs/usage/configuration/user_authentication/README.md
@@ -0,0 +1,15 @@
+# User Authentication
+
+Synapse supports multiple methods of authenticating users, either out-of-the-box or through custom pluggable
+authentication modules.
+
+Included in Synapse is support for authenticating users via:
+
+* A username and password.
+* An email address and password.
+* Single Sign-On through the SAML, Open ID Connect or CAS protocols.
+* JSON Web Tokens.
+* An administrator's shared secret.
+
+Synapse can additionally be extended to support custom authentication schemes through optional "password auth provider"
+modules.
\ No newline at end of file
diff --git a/docs/website_files/README.md b/docs/website_files/README.md
new file mode 100644
index 0000000000..04d191479b
--- /dev/null
+++ b/docs/website_files/README.md
@@ -0,0 +1,30 @@
+# Documentation Website Files and Assets
+
+This directory contains extra files for modifying the look and functionality of
+[mdbook](https://github.com/rust-lang/mdBook), the documentation software that's
+used to generate Synapse's documentation website.
+
+The configuration options in the `output.html` section of [book.toml](../../book.toml)
+point to additional JS/CSS in this directory that are added on each page load. In
+addition, the `theme` directory contains files that overwrite their counterparts in
+each of the default themes included with mdbook.
+
+Currently we use these files to generate a floating Table of Contents panel. The code for
+which was partially taken from
+[JorelAli/mdBook-pagetoc](https://github.com/JorelAli/mdBook-pagetoc/)
+before being modified such that it scrolls with the content of the page. This is handled
+by the `table-of-contents.js/css` files. The table of contents panel only appears on pages
+that have more than one header, as well as only appearing on desktop-sized monitors.
+
+We remove the navigation arrows which typically appear on the left and right side of the
+screen on desktop as they interfere with the table of contents. This is handled by
+the `remove-nav-buttons.css` file.
+
+Finally, we also stylise the chapter titles in the left sidebar by indenting them
+slightly so that they are more visually distinguishable from the section headers
+(the bold titles). This is done through the `indent-section-headers.css` file.
+
+More information can be found in mdbook's official documentation for
+[injecting page JS/CSS](https://rust-lang.github.io/mdBook/format/config.html)
+and
+[customising the default themes](https://rust-lang.github.io/mdBook/format/theme/index.html).
\ No newline at end of file
diff --git a/docs/website_files/indent-section-headers.css b/docs/website_files/indent-section-headers.css
new file mode 100644
index 0000000000..f9b3c82ca6
--- /dev/null
+++ b/docs/website_files/indent-section-headers.css
@@ -0,0 +1,7 @@
+/*
+ * Indents each chapter title in the left sidebar so that they aren't
+ * at the same level as the section headers.
+ */
+.chapter-item {
+ margin-left: 1em;
+}
\ No newline at end of file
diff --git a/docs/website_files/remove-nav-buttons.css b/docs/website_files/remove-nav-buttons.css
new file mode 100644
index 0000000000..4b280794ea
--- /dev/null
+++ b/docs/website_files/remove-nav-buttons.css
@@ -0,0 +1,8 @@
+/* Remove the prev, next chapter buttons as they interfere with the
+ * table of contents.
+ * Note that the table of contents only appears on desktop, thus we
+ * only remove the desktop (wide) chapter buttons.
+ */
+.nav-wide-wrapper {
+ display: none
+}
\ No newline at end of file
diff --git a/docs/website_files/table-of-contents.css b/docs/website_files/table-of-contents.css
new file mode 100644
index 0000000000..d16bb3b988
--- /dev/null
+++ b/docs/website_files/table-of-contents.css
@@ -0,0 +1,42 @@
+@media only screen and (max-width:1439px) {
+ .sidetoc {
+ display: none;
+ }
+}
+
+@media only screen and (min-width:1440px) {
+ main {
+ position: relative;
+ margin-left: 100px !important;
+ }
+ .sidetoc {
+ margin-left: auto;
+ margin-right: auto;
+ left: calc(100% + (var(--content-max-width))/4 - 140px);
+ position: absolute;
+ text-align: right;
+ }
+ .pagetoc {
+ position: fixed;
+ width: 250px;
+ overflow: auto;
+ right: 20px;
+ height: calc(100% - var(--menu-bar-height));
+ }
+ .pagetoc a {
+ color: var(--fg) !important;
+ display: block;
+ padding: 5px 15px 5px 10px;
+ text-align: left;
+ text-decoration: none;
+ }
+ .pagetoc a:hover,
+ .pagetoc a.active {
+ background: var(--sidebar-bg) !important;
+ color: var(--sidebar-fg) !important;
+ }
+ .pagetoc .active {
+ background: var(--sidebar-bg);
+ color: var(--sidebar-fg);
+ }
+}
diff --git a/docs/website_files/table-of-contents.js b/docs/website_files/table-of-contents.js
new file mode 100644
index 0000000000..0de5960b22
--- /dev/null
+++ b/docs/website_files/table-of-contents.js
@@ -0,0 +1,134 @@
+const getPageToc = () => document.getElementsByClassName('pagetoc')[0];
+
+const pageToc = getPageToc();
+const pageTocChildren = [...pageToc.children];
+const headers = [...document.getElementsByClassName('header')];
+
+
+// Select highlighted item in ToC when clicking an item
+pageTocChildren.forEach(child => {
+ child.addEventHandler('click', () => {
+ pageTocChildren.forEach(child => {
+ child.classList.remove('active');
+ });
+ child.classList.add('active');
+ });
+});
+
+
+/**
+ * Test whether a node is in the viewport
+ */
+function isInViewport(node) {
+ const rect = node.getBoundingClientRect();
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
+}
+
+
+/**
+ * Set a new ToC entry.
+ * Clear any previously highlighted ToC items, set the new one,
+ * and adjust the ToC scroll position.
+ */
+function setTocEntry() {
+ let activeEntry;
+ const pageTocChildren = [...getPageToc().children];
+
+ // Calculate which header is the current one at the top of screen
+ headers.forEach(header => {
+ if (window.pageYOffset >= header.offsetTop) {
+ activeEntry = header;
+ }
+ });
+
+ // Update selected item in ToC when scrolling
+ pageTocChildren.forEach(child => {
+ if (activeEntry.href.localeCompare(child.href) === 0) {
+ child.classList.add('active');
+ } else {
+ child.classList.remove('active');
+ }
+ });
+
+ let tocEntryForLocation = document.querySelector(`nav a[href="${activeEntry.href}"]`);
+ if (tocEntryForLocation) {
+ const headingForLocation = document.querySelector(activeEntry.hash);
+ if (headingForLocation && isInViewport(headingForLocation)) {
+ // Update ToC scroll
+ const nav = getPageToc();
+ const content = document.querySelector('html');
+ if (content.scrollTop !== 0) {
+ nav.scrollTo({
+ top: tocEntryForLocation.offsetTop - 100,
+ left: 0,
+ behavior: 'smooth',
+ });
+ } else {
+ nav.scrollTop = 0;
+ }
+ }
+ }
+}
+
+
+/**
+ * Populate sidebar on load
+ */
+window.addEventListener('load', () => {
+ // Only create table of contents if there is more than one header on the page
+ if (headers.length <= 1) {
+ return;
+ }
+
+ // Create an entry in the page table of contents for each header in the document
+ headers.forEach((header, index) => {
+ const link = document.createElement('a');
+
+ // Indent shows hierarchy
+ let indent = '0px';
+ switch (header.parentElement.tagName) {
+ case 'H1':
+ indent = '5px';
+ break;
+ case 'H2':
+ indent = '20px';
+ break;
+ case 'H3':
+ indent = '30px';
+ break;
+ case 'H4':
+ indent = '40px';
+ break;
+ case 'H5':
+ indent = '50px';
+ break;
+ case 'H6':
+ indent = '60px';
+ break;
+ default:
+ break;
+ }
+
+ let tocEntry;
+ if (index == 0) {
+ // Create a bolded title for the first element
+ tocEntry = document.createElement("strong");
+ tocEntry.innerHTML = header.text;
+ } else {
+ // All other elements are non-bold
+ tocEntry = document.createTextNode(header.text);
+ }
+ link.appendChild(tocEntry);
+
+ link.style.paddingLeft = indent;
+ link.href = header.href;
+ pageToc.appendChild(link);
+ });
+ setTocEntry.call();
+});
+
+
+// Handle active headers on scroll, if there is more than one header on the page
+if (headers.length > 1) {
+ window.addEventListener('scroll', setTocEntry);
+}
diff --git a/docs/website_files/theme/index.hbs b/docs/website_files/theme/index.hbs
new file mode 100644
index 0000000000..3b7a5b6163
--- /dev/null
+++ b/docs/website_files/theme/index.hbs
@@ -0,0 +1,312 @@
+<!DOCTYPE HTML>
+<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
+ <head>
+ <!-- Book generated using mdBook -->
+ <meta charset="UTF-8">
+ <title>{{ title }}</title>
+ {{#if is_print }}
+ <meta name="robots" content="noindex" />
+ {{/if}}
+ {{#if base_url}}
+ <base href="{{ base_url }}">
+ {{/if}}
+
+
+ <!-- Custom HTML head -->
+ {{> head}}
+
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
+ <meta name="description" content="{{ description }}">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="theme-color" content="#ffffff" />
+
+ {{#if favicon_svg}}
+ <link rel="icon" href="{{ path_to_root }}favicon.svg">
+ {{/if}}
+ {{#if favicon_png}}
+ <link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
+ {{/if}}
+ <link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
+ <link rel="stylesheet" href="{{ path_to_root }}css/general.css">
+ <link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
+ {{#if print_enable}}
+ <link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
+ {{/if}}
+
+ <!-- Fonts -->
+ <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
+ {{#if copy_fonts}}
+ <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
+ {{/if}}
+
+ <!-- Highlight.js Stylesheets -->
+ <link rel="stylesheet" href="{{ path_to_root }}highlight.css">
+ <link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
+ <link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
+
+ <!-- Custom theme stylesheets -->
+ {{#each additional_css}}
+ <link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
+ {{/each}}
+
+ {{#if mathjax_support}}
+ <!-- MathJax -->
+ <script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+ {{/if}}
+ </head>
+ <body>
+ <!-- Provide site root to javascript -->
+ <script type="text/javascript">
+ var path_to_root = "{{ path_to_root }}";
+ var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
+ </script>
+
+ <!-- Work around some values being stored in localStorage wrapped in quotes -->
+ <script type="text/javascript">
+ try {
+ var theme = localStorage.getItem('mdbook-theme');
+ var sidebar = localStorage.getItem('mdbook-sidebar');
+ if (theme.startsWith('"') && theme.endsWith('"')) {
+ localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
+ }
+ if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
+ localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
+ }
+ } catch (e) { }
+ </script>
+
+ <!-- Set the theme before any content is loaded, prevents flash -->
+ <script type="text/javascript">
+ var theme;
+ try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
+ if (theme === null || theme === undefined) { theme = default_theme; }
+ var html = document.querySelector('html');
+ html.classList.remove('no-js')
+ html.classList.remove('{{ default_theme }}')
+ html.classList.add(theme);
+ html.classList.add('js');
+ </script>
+
+ <!-- Hide / unhide sidebar before it is displayed -->
+ <script type="text/javascript">
+ var html = document.querySelector('html');
+ var sidebar = 'hidden';
+ if (document.body.clientWidth >= 1080) {
+ try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
+ sidebar = sidebar || 'visible';
+ }
+ html.classList.remove('sidebar-visible');
+ html.classList.add("sidebar-" + sidebar);
+ </script>
+
+ <nav id="sidebar" class="sidebar" aria-label="Table of contents">
+ <div class="sidebar-scrollbox">
+ {{#toc}}{{/toc}}
+ </div>
+ <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
+ </nav>
+
+ <div id="page-wrapper" class="page-wrapper">
+
+ <div class="page">
+ {{> header}}
+ <div id="menu-bar-hover-placeholder"></div>
+ <div id="menu-bar" class="menu-bar sticky bordered">
+ <div class="left-buttons">
+ <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
+ <i class="fa fa-bars"></i>
+ </button>
+ <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
+ <i class="fa fa-paint-brush"></i>
+ </button>
+ <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
+ <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
+ <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
+ </ul>
+ {{#if search_enabled}}
+ <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
+ <i class="fa fa-search"></i>
+ </button>
+ {{/if}}
+ </div>
+
+ <h1 class="menu-title">{{ book_title }}</h1>
+
+ <div class="right-buttons">
+ {{#if print_enable}}
+ <a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
+ <i id="print-button" class="fa fa-print"></i>
+ </a>
+ {{/if}}
+ {{#if git_repository_url}}
+ <a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
+ <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
+ </a>
+ {{/if}}
+ {{#if git_repository_edit_url}}
+ <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
+ <i id="git-edit-button" class="fa fa-edit"></i>
+ </a>
+ {{/if}}
+
+ </div>
+ </div>
+
+ {{#if search_enabled}}
+ <div id="search-wrapper" class="hidden">
+ <form id="searchbar-outer" class="searchbar-outer">
+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
+ </form>
+ <div id="searchresults-outer" class="searchresults-outer hidden">
+ <div id="searchresults-header" class="searchresults-header"></div>
+ <ul id="searchresults">
+ </ul>
+ </div>
+ </div>
+ {{/if}}
+
+ <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
+ <script type="text/javascript">
+ document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
+ document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
+ Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
+ link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
+ });
+ </script>
+
+ <div id="content" class="content">
+ <main>
+ <!-- Page table of contents -->
+ <div class="sidetoc">
+ <nav class="pagetoc"></nav>
+ </div>
+
+ {{{ content }}}
+ </main>
+
+ <nav class="nav-wrapper" aria-label="Page navigation">
+ <!-- Mobile navigation buttons -->
+ {{#previous}}
+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
+ <i class="fa fa-angle-left"></i>
+ </a>
+ {{/previous}}
+
+ {{#next}}
+ <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
+ <i class="fa fa-angle-right"></i>
+ </a>
+ {{/next}}
+
+ <div style="clear: both"></div>
+ </nav>
+ </div>
+ </div>
+
+ <nav class="nav-wide-wrapper" aria-label="Page navigation">
+ {{#previous}}
+ <a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
+ <i class="fa fa-angle-left"></i>
+ </a>
+ {{/previous}}
+
+ {{#next}}
+ <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
+ <i class="fa fa-angle-right"></i>
+ </a>
+ {{/next}}
+ </nav>
+
+ </div>
+
+ {{#if livereload}}
+ <!-- Livereload script (if served using the cli tool) -->
+ <script type="text/javascript">
+ var socket = new WebSocket("{{{livereload}}}");
+ socket.onmessage = function (event) {
+ if (event.data === "reload") {
+ socket.close();
+ location.reload();
+ }
+ };
+ window.onbeforeunload = function() {
+ socket.close();
+ }
+ </script>
+ {{/if}}
+
+ {{#if google_analytics}}
+ <!-- Google Analytics Tag -->
+ <script type="text/javascript">
+ var localAddrs = ["localhost", "127.0.0.1", ""];
+ // make sure we don't activate google analytics if the developer is
+ // inspecting the book locally...
+ if (localAddrs.indexOf(document.location.hostname) === -1) {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+ ga('create', '{{google_analytics}}', 'auto');
+ ga('send', 'pageview');
+ }
+ </script>
+ {{/if}}
+
+ {{#if playground_line_numbers}}
+ <script type="text/javascript">
+ window.playground_line_numbers = true;
+ </script>
+ {{/if}}
+
+ {{#if playground_copyable}}
+ <script type="text/javascript">
+ window.playground_copyable = true;
+ </script>
+ {{/if}}
+
+ {{#if playground_js}}
+ <script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
+ {{/if}}
+
+ {{#if search_js}}
+ <script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
+ {{/if}}
+
+ <script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
+ <script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
+
+ <!-- Custom JS scripts -->
+ {{#each additional_js}}
+ <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
+ {{/each}}
+
+ {{#if is_print}}
+ {{#if mathjax_support}}
+ <script type="text/javascript">
+ window.addEventListener('load', function() {
+ MathJax.Hub.Register.StartupHook('End', function() {
+ window.setTimeout(window.print, 100);
+ });
+ });
+ </script>
+ {{else}}
+ <script type="text/javascript">
+ window.addEventListener('load', function() {
+ window.setTimeout(window.print, 100);
+ });
+ </script>
+ {{/if}}
+ {{/if}}
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/docs/welcome_and_overview.md b/docs/welcome_and_overview.md
new file mode 100644
index 0000000000..30e75984d1
--- /dev/null
+++ b/docs/welcome_and_overview.md
@@ -0,0 +1,4 @@
+# Introduction
+
+Welcome to the documentation repository for Synapse, the reference
+[Matrix](https://matrix.org) homeserver implementation.
\ No newline at end of file
diff --git a/mypy.ini b/mypy.ini
index 062872020e..8ba1b96311 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -130,7 +130,7 @@ ignore_missing_imports = True
[mypy-canonicaljson]
ignore_missing_imports = True
-[mypy-jaeger_client]
+[mypy-jaeger_client.*]
ignore_missing_imports = True
[mypy-jsonschema]
diff --git a/synapse/__init__.py b/synapse/__init__.py
index d9843a1708..445e8a5cad 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -47,7 +47,7 @@ try:
except ImportError:
pass
-__version__ = "1.35.0"
+__version__ = "1.35.1"
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
# We import here so that we don't have to install a bunch of deps when
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index 49ed7cabcc..f3f97db2fa 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -3056,8 +3056,9 @@ class FederationHandler(BaseHandler):
"""
instance = self.config.worker.events_shard_config.get_instance(room_id)
if instance != self._instance_name:
- # Limit the number of events sent over federation.
- for batch in batch_iter(event_and_contexts, 1000):
+ # Limit the number of events sent over replication. We choose 200
+ # here as that is what we default to in `max_request_body_size(..)`
+ for batch in batch_iter(event_and_contexts, 200):
result = await self._send_events(
instance_name=instance,
store=self.store,
diff --git a/synapse/logging/opentracing.py b/synapse/logging/opentracing.py
index f64845b80c..26c8ffe780 100644
--- a/synapse/logging/opentracing.py
+++ b/synapse/logging/opentracing.py
@@ -271,6 +271,12 @@ class SynapseTags:
# HTTP request tag (used to distinguish full vs incremental syncs, etc)
REQUEST_TAG = "request_tag"
+ # Text description of a database transaction
+ DB_TXN_DESC = "db.txn_desc"
+
+ # Uniqueish ID of a database transaction
+ DB_TXN_ID = "db.txn_id"
+
# Block everything by default
# A regex which matches the server_names to expose traces for.
@@ -356,10 +362,13 @@ def init_tracer(hs: "HomeServer"):
set_homeserver_whitelist(hs.config.opentracer_whitelist)
+ from jaeger_client.metrics.prometheus import PrometheusMetricsFactory
+
config = JaegerConfig(
config=hs.config.jaeger_config,
service_name="{} {}".format(hs.config.server_name, hs.get_instance_name()),
scope_manager=LogContextScopeManager(hs.config),
+ metrics_factory=PrometheusMetricsFactory(),
)
# If we have the rust jaeger reporter available let's use that.
diff --git a/synapse/notifier.py b/synapse/notifier.py
index 24b4e6649f..3c3cc47631 100644
--- a/synapse/notifier.py
+++ b/synapse/notifier.py
@@ -485,21 +485,21 @@ class Notifier:
end_time = self.clock.time_msec() + timeout
while not result:
- try:
- now = self.clock.time_msec()
- if end_time <= now:
- break
-
- # Now we wait for the _NotifierUserStream to be told there
- # is a new token.
- listener = user_stream.new_listener(prev_token)
- listener.deferred = timeout_deferred(
- listener.deferred,
- (end_time - now) / 1000.0,
- self.hs.get_reactor(),
- )
+ with start_active_span("wait_for_events"):
+ try:
+ now = self.clock.time_msec()
+ if end_time <= now:
+ break
+
+ # Now we wait for the _NotifierUserStream to be told there
+ # is a new token.
+ listener = user_stream.new_listener(prev_token)
+ listener.deferred = timeout_deferred(
+ listener.deferred,
+ (end_time - now) / 1000.0,
+ self.hs.get_reactor(),
+ )
- with start_active_span("wait_for_events.deferred"):
log_kv(
{
"wait_for_events": "sleep",
@@ -517,27 +517,27 @@ class Notifier:
}
)
- current_token = user_stream.current_token
+ current_token = user_stream.current_token
- result = await callback(prev_token, current_token)
- log_kv(
- {
- "wait_for_events": "result",
- "result": bool(result),
- }
- )
- if result:
+ result = await callback(prev_token, current_token)
+ log_kv(
+ {
+ "wait_for_events": "result",
+ "result": bool(result),
+ }
+ )
+ if result:
+ break
+
+ # Update the prev_token to the current_token since nothing
+ # has happened between the old prev_token and the current_token
+ prev_token = current_token
+ except defer.TimeoutError:
+ log_kv({"wait_for_events": "timeout"})
+ break
+ except defer.CancelledError:
+ log_kv({"wait_for_events": "cancelled"})
break
-
- # Update the prev_token to the current_token since nothing
- # has happened between the old prev_token and the current_token
- prev_token = current_token
- except defer.TimeoutError:
- log_kv({"wait_for_events": "timeout"})
- break
- except defer.CancelledError:
- log_kv({"wait_for_events": "cancelled"})
- break
if result is None:
# This happened if there was no timeout or if the timeout had
diff --git a/synapse/rest/admin/media.py b/synapse/rest/admin/media.py
index 2c71af4279..b68db2c57c 100644
--- a/synapse/rest/admin/media.py
+++ b/synapse/rest/admin/media.py
@@ -120,6 +120,35 @@ class QuarantineMediaByID(RestServlet):
return 200, {}
+class UnquarantineMediaByID(RestServlet):
+ """Quarantines local or remote media by a given ID so that no one can download
+ it via this server.
+ """
+
+ PATTERNS = admin_patterns(
+ "/media/unquarantine/(?P<server_name>[^/]+)/(?P<media_id>[^/]+)"
+ )
+
+ def __init__(self, hs: "HomeServer"):
+ self.store = hs.get_datastore()
+ self.auth = hs.get_auth()
+
+ async def on_POST(
+ self, request: SynapseRequest, server_name: str, media_id: str
+ ) -> Tuple[int, JsonDict]:
+ requester = await self.auth.get_user_by_req(request)
+ await assert_user_is_admin(self.auth, requester.user)
+
+ logging.info(
+ "Remove from quarantine local media by ID: %s/%s", server_name, media_id
+ )
+
+ # Remove from quarantine this media id
+ await self.store.quarantine_media_by_id(server_name, media_id, None)
+
+ return 200, {}
+
+
class ProtectMediaByID(RestServlet):
"""Protect local media from being quarantined."""
@@ -290,6 +319,7 @@ def register_servlets_for_media_repo(hs: "HomeServer", http_server):
PurgeMediaCacheRestServlet(hs).register(http_server)
QuarantineMediaInRoom(hs).register(http_server)
QuarantineMediaByID(hs).register(http_server)
+ UnquarantineMediaByID(hs).register(http_server)
QuarantineMediaByUser(hs).register(http_server)
ProtectMediaByID(hs).register(http_server)
UnprotectMediaByID(hs).register(http_server)
diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py
index 70286b0ff7..5a9c27f75f 100644
--- a/synapse/rest/client/v1/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -910,7 +910,7 @@ class RoomAliasListServlet(RestServlet):
r"^/_matrix/client/unstable/org\.matrix\.msc2432"
r"/rooms/(?P<room_id>[^/]*)/aliases"
),
- ]
+ ] + list(client_patterns("/rooms/(?P<room_id>[^/]*)/aliases$", unstable=False))
def __init__(self, hs: "HomeServer"):
super().__init__()
diff --git a/synapse/storage/database.py b/synapse/storage/database.py
index a761ad603b..974703d13a 100644
--- a/synapse/storage/database.py
+++ b/synapse/storage/database.py
@@ -40,6 +40,7 @@ from twisted.enterprise import adbapi
from synapse.api.errors import StoreError
from synapse.config.database import DatabaseConnectionConfig
+from synapse.logging import opentracing
from synapse.logging.context import (
LoggingContext,
current_context,
@@ -313,7 +314,14 @@ class LoggingTransaction:
start = time.time()
try:
- return func(sql, *args)
+ with opentracing.start_active_span(
+ "db.query",
+ tags={
+ opentracing.tags.DATABASE_TYPE: "sql",
+ opentracing.tags.DATABASE_STATEMENT: sql,
+ },
+ ):
+ return func(sql, *args)
except Exception as e:
sql_logger.debug("[SQL FAIL] {%s} %s", self.name, e)
raise
@@ -525,9 +533,16 @@ class DatabasePool:
exception_callbacks=exception_callbacks,
)
try:
- r = func(cursor, *args, **kwargs)
- conn.commit()
- return r
+ with opentracing.start_active_span(
+ "db.txn",
+ tags={
+ opentracing.SynapseTags.DB_TXN_DESC: desc,
+ opentracing.SynapseTags.DB_TXN_ID: name,
+ },
+ ):
+ r = func(cursor, *args, **kwargs)
+ conn.commit()
+ return r
except self.engine.module.OperationalError as e:
# This can happen if the database disappears mid
# transaction.
@@ -653,16 +668,17 @@ class DatabasePool:
logger.warning("Starting db txn '%s' from sentinel context", desc)
try:
- result = await self.runWithConnection(
- self.new_transaction,
- desc,
- after_callbacks,
- exception_callbacks,
- func,
- *args,
- db_autocommit=db_autocommit,
- **kwargs,
- )
+ with opentracing.start_active_span(f"db.{desc}"):
+ result = await self.runWithConnection(
+ self.new_transaction,
+ desc,
+ after_callbacks,
+ exception_callbacks,
+ func,
+ *args,
+ db_autocommit=db_autocommit,
+ **kwargs,
+ )
for after_callback, after_args, after_kwargs in after_callbacks:
after_callback(*after_args, **after_kwargs)
@@ -718,25 +734,29 @@ class DatabasePool:
with LoggingContext(
str(curr_context), parent_context=parent_context
) as context:
- sched_duration_sec = monotonic_time() - start_time
- sql_scheduling_timer.observe(sched_duration_sec)
- context.add_database_scheduled(sched_duration_sec)
-
- if self.engine.is_connection_closed(conn):
- logger.debug("Reconnecting closed database connection")
- conn.reconnect()
-
- try:
- if db_autocommit:
- self.engine.attempt_to_set_autocommit(conn, True)
-
- db_conn = LoggingDatabaseConnection(
- conn, self.engine, "runWithConnection"
- )
- return func(db_conn, *args, **kwargs)
- finally:
- if db_autocommit:
- self.engine.attempt_to_set_autocommit(conn, False)
+ with opentracing.start_active_span(
+ operation_name="db.connection",
+ ):
+ sched_duration_sec = monotonic_time() - start_time
+ sql_scheduling_timer.observe(sched_duration_sec)
+ context.add_database_scheduled(sched_duration_sec)
+
+ if self.engine.is_connection_closed(conn):
+ logger.debug("Reconnecting closed database connection")
+ conn.reconnect()
+ opentracing.log_kv({"message": "reconnected"})
+
+ try:
+ if db_autocommit:
+ self.engine.attempt_to_set_autocommit(conn, True)
+
+ db_conn = LoggingDatabaseConnection(
+ conn, self.engine, "runWithConnection"
+ )
+ return func(db_conn, *args, **kwargs)
+ finally:
+ if db_autocommit:
+ self.engine.attempt_to_set_autocommit(conn, False)
return await make_deferred_yieldable(
self._db_pool.runWithConnection(inner_func, *args, **kwargs)
diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py
index 0cf450f81d..2a96bcd314 100644
--- a/synapse/storage/databases/main/room.py
+++ b/synapse/storage/databases/main/room.py
@@ -764,14 +764,15 @@ class RoomWorkerStore(SQLBaseStore):
self,
server_name: str,
media_id: str,
- quarantined_by: str,
+ quarantined_by: Optional[str],
) -> int:
- """quarantines a single local or remote media id
+ """quarantines or unquarantines a single local or remote media id
Args:
server_name: The name of the server that holds this media
media_id: The ID of the media to be quarantined
quarantined_by: The user ID that initiated the quarantine request
+ If it is `None` media will be removed from quarantine
"""
logger.info("Quarantining media: %s/%s", server_name, media_id)
is_local = server_name == self.config.server_name
@@ -838,9 +839,9 @@ class RoomWorkerStore(SQLBaseStore):
txn,
local_mxcs: List[str],
remote_mxcs: List[Tuple[str, str]],
- quarantined_by: str,
+ quarantined_by: Optional[str],
) -> int:
- """Quarantine local and remote media items
+ """Quarantine and unquarantine local and remote media items
Args:
txn (cursor)
@@ -848,18 +849,27 @@ class RoomWorkerStore(SQLBaseStore):
remote_mxcs: A list of (remote server, media id) tuples representing
remote mxc URLs
quarantined_by: The ID of the user who initiated the quarantine request
+ If it is `None` media will be removed from quarantine
Returns:
The total number of media items quarantined
"""
+
# Update all the tables to set the quarantined_by flag
- txn.executemany(
- """
+ sql = """
UPDATE local_media_repository
SET quarantined_by = ?
- WHERE media_id = ? AND safe_from_quarantine = ?
- """,
- ((quarantined_by, media_id, False) for media_id in local_mxcs),
- )
+ WHERE media_id = ?
+ """
+
+ # set quarantine
+ if quarantined_by is not None:
+ sql += "AND safe_from_quarantine = ?"
+ rows = [(quarantined_by, media_id, False) for media_id in local_mxcs]
+ # remove from quarantine
+ else:
+ rows = [(quarantined_by, media_id) for media_id in local_mxcs]
+
+ txn.executemany(sql, rows)
# Note that a rowcount of -1 can be used to indicate no rows were affected.
total_media_quarantined = txn.rowcount if txn.rowcount > 0 else 0
diff --git a/tests/rest/admin/test_media.py b/tests/rest/admin/test_media.py
index f741121ea2..6fee0f95b6 100644
--- a/tests/rest/admin/test_media.py
+++ b/tests/rest/admin/test_media.py
@@ -566,6 +566,134 @@ class DeleteMediaByDateSizeTestCase(unittest.HomeserverTestCase):
self.assertFalse(os.path.exists(local_path))
+class QuarantineMediaByIDTestCase(unittest.HomeserverTestCase):
+
+ servlets = [
+ synapse.rest.admin.register_servlets,
+ synapse.rest.admin.register_servlets_for_media_repo,
+ login.register_servlets,
+ ]
+
+ def prepare(self, reactor, clock, hs):
+ media_repo = hs.get_media_repository_resource()
+ self.store = hs.get_datastore()
+ self.server_name = hs.hostname
+
+ self.admin_user = self.register_user("admin", "pass", admin=True)
+ self.admin_user_tok = self.login("admin", "pass")
+
+ # Create media
+ upload_resource = media_repo.children[b"upload"]
+ # file size is 67 Byte
+ image_data = unhexlify(
+ b"89504e470d0a1a0a0000000d4948445200000001000000010806"
+ b"0000001f15c4890000000a49444154789c63000100000500010d"
+ b"0a2db40000000049454e44ae426082"
+ )
+
+ # Upload some media into the room
+ response = self.helper.upload_media(
+ upload_resource, image_data, tok=self.admin_user_tok, expect_code=200
+ )
+ # Extract media ID from the response
+ server_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
+ self.media_id = server_and_media_id.split("/")[1]
+
+ self.url = "/_synapse/admin/v1/media/%s/%s/%s"
+
+ @parameterized.expand(["quarantine", "unquarantine"])
+ def test_no_auth(self, action: str):
+ """
+ Try to protect media without authentication.
+ """
+
+ channel = self.make_request(
+ "POST",
+ self.url % (action, self.server_name, self.media_id),
+ b"{}",
+ )
+
+ self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
+ self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
+
+ @parameterized.expand(["quarantine", "unquarantine"])
+ def test_requester_is_no_admin(self, action: str):
+ """
+ If the user is not a server admin, an error is returned.
+ """
+ self.other_user = self.register_user("user", "pass")
+ self.other_user_token = self.login("user", "pass")
+
+ channel = self.make_request(
+ "POST",
+ self.url % (action, self.server_name, self.media_id),
+ access_token=self.other_user_token,
+ )
+
+ self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
+ self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
+
+ def test_quarantine_media(self):
+ """
+ Tests that quarantining and remove from quarantine a media is successfully
+ """
+
+ media_info = self.get_success(self.store.get_local_media(self.media_id))
+ self.assertFalse(media_info["quarantined_by"])
+
+ # quarantining
+ channel = self.make_request(
+ "POST",
+ self.url % ("quarantine", self.server_name, self.media_id),
+ access_token=self.admin_user_tok,
+ )
+
+ self.assertEqual(200, channel.code, msg=channel.json_body)
+ self.assertFalse(channel.json_body)
+
+ media_info = self.get_success(self.store.get_local_media(self.media_id))
+ self.assertTrue(media_info["quarantined_by"])
+
+ # remove from quarantine
+ channel = self.make_request(
+ "POST",
+ self.url % ("unquarantine", self.server_name, self.media_id),
+ access_token=self.admin_user_tok,
+ )
+
+ self.assertEqual(200, channel.code, msg=channel.json_body)
+ self.assertFalse(channel.json_body)
+
+ media_info = self.get_success(self.store.get_local_media(self.media_id))
+ self.assertFalse(media_info["quarantined_by"])
+
+ def test_quarantine_protected_media(self):
+ """
+ Tests that quarantining from protected media fails
+ """
+
+ # protect
+ self.get_success(self.store.mark_local_media_as_safe(self.media_id, safe=True))
+
+ # verify protection
+ media_info = self.get_success(self.store.get_local_media(self.media_id))
+ self.assertTrue(media_info["safe_from_quarantine"])
+
+ # quarantining
+ channel = self.make_request(
+ "POST",
+ self.url % ("quarantine", self.server_name, self.media_id),
+ access_token=self.admin_user_tok,
+ )
+
+ self.assertEqual(200, channel.code, msg=channel.json_body)
+ self.assertFalse(channel.json_body)
+
+ # verify that is not in quarantine
+ media_info = self.get_success(self.store.get_local_media(self.media_id))
+ self.assertFalse(media_info["quarantined_by"])
+
+
class ProtectMediaByIDTestCase(unittest.HomeserverTestCase):
servlets = [
diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py
index 7c4bdcdfdd..5b1096d091 100644
--- a/tests/rest/client/v1/test_rooms.py
+++ b/tests/rest/client/v1/test_rooms.py
@@ -1880,8 +1880,7 @@ class RoomAliasListTestCase(unittest.HomeserverTestCase):
"""Calls the endpoint under test. returns the json response object."""
channel = self.make_request(
"GET",
- "/_matrix/client/unstable/org.matrix.msc2432/rooms/%s/aliases"
- % (self.room_id,),
+ "/_matrix/client/r0/rooms/%s/aliases" % (self.room_id,),
access_token=access_token,
)
self.assertEqual(channel.code, expected_code, channel.result)
|