diff --git a/.gitignore b/.gitignore index 74b1c7d2aa0..daef51d72d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -scripts/gen -scripts/continuserv/continuserv -scripts/speculator/speculator -templating/out +/api/node_modules +/assets +/assets.tar.gz +/scripts/gen +/scripts/continuserv/continuserv +/scripts/speculator/speculator +/scripts/swagger +/templating/out *.pyc -supporting-docs/_site -supporting-docs/.sass-cache -api/node_modules +*.swp diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index 6e3198ef996..00000000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,41 +0,0 @@ -.. This file is automatically processed by the templating system. To make it -.. happy, you MUST use '=' as the title underline and you MUST stick the version -.. in the title. The version MUST follow the numbering format -.. "v.." - You cannot use a-z. If the templating system fails to -.. find the right info, it will be treated as a test failure and so will show up -.. in Jenkins. Comments like this are ignored by both RST and the templating -.. system. Add the newest release notes beneath this comment. - -Specification changes in v0.2.0 (2015-10-02) -============================================ - -This update fundamentally restructures the specification. The specification has -been split into more digestible "modules" which each describe a particular -function (e.g. typing). This was done in order make the specification easier to -maintain and help define which modules are mandatory for certain types -of clients. Types of clients along with the mandatory modules can be found in a -new "Feature Profiles" section. This update also begins to aggressively -standardise on using Swagger and JSON Schema to document HTTP endpoints and -Events respectively. It also introduces a number of new concepts to Matrix. - -Additions: - - New section: Feature Profiles. - - New section: Receipts. - - New section: Room history visibility. - - New event: ``m.receipt``. - - New event: ``m.room.canonical_alias`` - - New event: ``m.room.history_visibility`` - - New keys: ``/createRoom`` - allows room "presets" using ``preset`` and - ``initial_state`` keys. - - New endpoint: ``/tokenrefresh`` - Related to refreshing access tokens. - -Modifications: - - Convert most of the older HTTP APIs to Swagger documentation. - - Convert most of the older event formats to JSON Schema. - - Move selected client-server sections to be "Modules". - -Specification changes in v0.1.0 (2015-06-01) -============================================ -- First numbered release. -- Restructure the format of Event information. Add more information. -- Restructure the format of the Client-Server HTTP APIs. \ No newline at end of file diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000000..f18263d0e95 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,153 @@ +Contributing to matrix-doc +========================== + +Everyone is welcome to contribute to the ``matrix-doc`` project, provided that they +are willing to license their contributions under the same license as the +project itself. We follow a simple 'inbound=outbound' model for contributions: +the act of submitting an 'inbound' contribution means that the contributor +agrees to license the code under the same terms as the project's overall +'outbound' license - in our case, this is Apache Software License +v2 (see LICENSE). + +Specification changes +~~~~~~~~~~~~~~~~~~~~~ + +The Matrix specification documents the APIs which Matrix clients can use. For +this to be effective, the APIs need to be present and working correctly in a +server before they can be documented in the specification. This process can +take some time to complete. + +For this reason, we have not found the github pull-request model effective for +discussing changes to the specification. Instead, we have adopted the following +workflow: + +1. Create a discussion document outlining the proposed change. The document + should include details such as the HTTP endpoint being changed (or the + suggested URL for a new endpoint), any new or changed parameters and response + fields, and generally as much detail about edge-cases and error handling as + is practical at this stage. + + The Matrix Core Team's preferred tool for such discussion documents is + `Google Docs `_ thanks to its support for comment + threads. Works in progress are kept in a folder at + https://drive.google.com/drive/folders/0B4wHq8qP86r2ck15MHEwMmlNVUk. + +2. Seek feedback on the proposal. `#matrix-dev:matrix.org + `_ is a good place to reach the + core team and others who may be interested in your proposal. + +3. Implement the changes in servers and clients. Refer to the CONTRIBUTING files + of the relevant projects for details of how best to do this. + + In general we will be unable to publish specification updates until the + reference server implements them, and they have been proven by a working + client implementation. + +4. Iterate steps 1-3 as necessary. + +5. Write the specification for the change, and create a `pull request`_ for + it. It may be that much of the text of the change can be taken directly from + the discussion document, though typically some effort will be needed to + change to the ReST syntax and to ensure that the text is as clear as + possible. + + +Other changes +~~~~~~~~~~~~~ + +The above process is unnecessary for smaller changes, and those which do not +put new requirements on servers. This category of changes includes the +following: + +* changes to supporting documentation + +* changes to the scripts used to generate the specification + +* clarifications to the specification which do not change the behaviour of + Matrix servers or clients in a way which might introduce compatibility + problems for existing deployments. For example, recommendations for UI + behaviour do not require a proposal document. On the other hand, changes to + event contents would be best discussed in a proposal document even though no + changes would be necessary to server implementations. + +For such changes, please do just open a `pull request`_. + + +Pull requests +~~~~~~~~~~~~~ +.. _pull request: `Pull requests`_ + +The preferred and easiest way to contribute changes to the ``matrix-doc`` project +is to fork it on github, and then create a pull request to ask us to pull your +changes into our repo (https://help.github.com/articles/using-pull-requests/). + +(Note that, unlike most of the other matrix.org projects, pull requests for +matrix-doc should be based on the ``master`` branch.) + +Code style +~~~~~~~~~~ + +The documentation style is described at +https://github.com/matrix-org/matrix-doc/blob/master/meta/documentation_style.rst. + +Python code within the ``matrix-doc`` project should follow the same style as +synapse, which is documented at +https://github.com/matrix-org/synapse/tree/master/docs/code_style.rst. + +Sign off +~~~~~~~~ + +In order to have a concrete record that your contribution is intentional +and you agree to license it under the same terms as the project's license, we've adopted the +same lightweight approach that the Linux Kernel +(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker +(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other +projects use: the DCO (Developer Certificate of Origin: +http://developercertificate.org/). This is a simple declaration that you wrote +the contribution or otherwise have the right to contribute it to Matrix:: + + Developer Certificate of Origin + Version 1.1 + + Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + 660 York Street, Suite 102, + San Francisco, CA 94110 USA + + Everyone is permitted to copy and distribute verbatim copies of this + license document, but changing it is not allowed. + + Developer's Certificate of Origin 1.1 + + By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +If you agree to this for your contribution, then all that's needed is to +include the line in your commit or pull request comment:: + + Signed-off-by: Your Name + +...using your real name; unfortunately pseudonyms and anonymous contributions +can't be accepted. Git makes this trivial - just use the -s flag when you do +``git commit``, having first set ``user.name`` and ``user.email`` git configs +(which you should have done anyway :) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..f433b1a53f5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.rst b/README.rst index f0da0267d18..76e3c1afa2e 100644 --- a/README.rst +++ b/README.rst @@ -4,27 +4,36 @@ Structure ========= - ``api`` : Contains the HTTP API specification. -- ``drafts`` : Contains documents which will make it into the specification - and/or supporting documentation at some point in the future. -- ``event-schemas`` : Contains the `JSON Schema`_ for all Matrix events +- ``attic``: Contains historical sections of specification for reference + purposes. +- ``changelogs``: Contains change logs for the various parts of the + specification. +- ``drafts``: Previously, contained documents which were under discussion for + future incusion into the specification and/or supporting documentation. This + is now historical, as we use separate discussion documents (see + ``_). +- ``event-schemas``: Contains the `JSON Schema`_ for all Matrix events contained in the specification, along with example JSON files. -- ``meta`` : Contains documents outlining the processes involved when writing +- ``meta``: Contains documents outlining the processes involved when writing documents, e.g. documentation style, guidelines. -- ``scripts`` : Contains scripts to generate formatted versions of the +- ``scripts``: Contains scripts to generate formatted versions of the documentation, typically HTML. -- ``specification`` : Contains the specification split up into sections. -- ``supporting-docs`` : Contains additional documents which explain design +- ``specification``: Contains the specification split up into sections. +- ``supporting-docs``: Contains additional documents which explain design decisions, examples, use cases, etc. -- ``templating`` : Contains the templates and templating system used to +- ``templating``: Contains the templates and templating system used to generate the spec. Contributing ============ Known issues with the specification are represented as JIRA issues at -https://matrix.org/jira/browse/SPEC +``_. -If you want to ask more about the specification, or have suggestions for -improvements, join us on ``#matrix-dev:matrix.org`` via https://matrix.org/beta. +If you want to ask more about the specification, join us on +`#matrix-dev:matrix.org `_. + +If you would like to contribute to the specification or supporting +documentation, see ``_. .. _JSON Schema: http://json-schema.org/ diff --git a/api/README b/api/README index ddb7775ef31..01b0958babe 100644 --- a/api/README +++ b/api/README @@ -1,4 +1,3 @@ -To get this running: - python -m SimpleHTTPServer +This directory contains swagger-compatible representations of our APIs. See +scripts/README.md for details on how to make use of them. -Go to http://localhost:8000/swagger.html diff --git a/api/application-service/application_service.yaml b/api/application-service/application_service.yaml new file mode 100644 index 00000000000..d7ad5b1954c --- /dev/null +++ b/api/application-service/application_service.yaml @@ -0,0 +1,213 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Application Service API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: "/" +consumes: + - application/json +produces: + - application/json +paths: + "/transactions/{txnId}": + put: + summary: Send some events to the application service. + description: |- + This API is called by the HS when the HS wants to push an event (or + batch of events) to the AS. + parameters: + - in: path + name: txnId + type: string + description: |- + The transaction ID for this set of events. Homeservers generate + these IDs and they are used to ensure idempotency of requests. + required: true + x-example: "35" + - in: body + name: body + description: A list of events + schema: + type: object + example: |- + { + "events": [ + { + "age": 32, + "content": { + "body": "incoming message", + "msgtype": "m.text" + }, + "event_id": "$14328055551tzaee:localhost", + "origin_server_ts": 1432804485886, + "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", + "type": "m.room.message", + "user_id": "@bob:localhost" + }, + { + "age": 1984, + "content": { + "body": "another incoming message", + "msgtype": "m.text" + }, + "event_id": "$1228055551ffsef:localhost", + "origin_server_ts": 1432804485886, + "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", + "type": "m.room.message", + "user_id": "@bob:localhost" + } + ] + } + description: "Transaction informations" + properties: + events: + type: array + description: A list of events + items: + type: object + title: Event + required: ["events"] + responses: + 200: + description: The transaction was processed successfully. + examples: + application/json: |- + {} + schema: + type: object + + "/rooms/{roomAlias}": + get: + summary: Query if a room alias should exist on the application service. + description: |- + This endpoint is invoked by the homeserver on an application service to query + the existence of a given room alias. The homeserver will only query room + aliases inside the application service's ``aliases`` namespace. The + homeserver will send this request when it receives a request to join a + room alias within the application service's namespace. + parameters: + - in: path + name: roomAlias + type: string + description: The room alias being queried. + required: true + x-example: "#magicforest:example.com" + responses: + 200: + description: |- + The application service indicates that this room alias exists. The + application service MUST have created a room and associated it with + the queried room alias using the client-server API. Additional + information about the room such as its name and topic can be set + before responding. + examples: + application/json: |- + {} + schema: + type: object + 401: + description: |- + The homeserver has not supplied credentials to the application service. + Optional error information can be included in the body of this response. + examples: + application/json: |- + { + "errcode": "COM.EXAMPLE.MYAPPSERVICE_UNAUTHORIZED" + } + schema: + type: object + 403: + description: |- + The credentials supplied by the homeserver were rejected. + examples: + application/json: |- + { + "errcode": "M_FORBIDDEN" + } + schema: + type: object + 404: + description: |- + The application service indicates that this room alias does not exist. + Optional error information can be included in the body of this response. + examples: + application/json: |- + { + "errcode": "COM.EXAMPLE.MYAPPSERVICE_NOT_FOUND" + } + schema: + type: object + "/users/{userId}": + get: + summary: Query if a user should exist on the application service. + description: |- + This endpoint is invoked by the homeserver on an application service to query + the existence of a given user ID. The homeserver will only query user IDs + inside the application service's ``users`` namespace. The homeserver will + send this request when it receives an event for an unknown user ID in + the application service's namespace. + parameters: + - in: path + name: userId + type: string + description: The user ID being queried. + required: true + x-example: "@alice:example.com" + responses: + 200: + description: |- + The application service indicates that this user exists. The application + service MUST create the user using the client-server API. + examples: + application/json: |- + {} + schema: + type: object + 401: + description: |- + The homeserver has not supplied credentials to the application service. + Optional error information can be included in the body of this response. + examples: + application/json: |- + { + "errcode": "COM.EXAMPLE.MYAPPSERVICE_UNAUTHORIZED" + } + schema: + type: object + 403: + description: |- + The credentials supplied by the homeserver were rejected. + examples: + application/json: |- + { + "errcode": "M_FORBIDDEN" + } + schema: + type: object + 404: + description: |- + The application service indicates that this user does not exist. + Optional error information can be included in the body of this response. + examples: + application/json: |- + { + "errcode": "COM.EXAMPLE.MYAPPSERVICE_NOT_FOUND" + } + schema: + type: object diff --git a/api/check_examples.py b/api/check_examples.py index ee3c773c12e..009055be944 100755 --- a/api/check_examples.py +++ b/api/check_examples.py @@ -1,4 +1,18 @@ #! /usr/bin/env python +# +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import sys import json @@ -49,7 +63,8 @@ def check_parameter(filepath, request, parameter): # Setting the 'id' tells jsonschema where the file is so that it # can correctly resolve relative $ref references in the schema schema['id'] = fileurl - jsonschema.validate(example, schema) + resolver = jsonschema.RefResolver(filepath, schema, handlers={"file": load_yaml}) + jsonschema.validate(example, schema, resolver=resolver) except Exception as e: raise ValueError("Error validating JSON schema for %r" % ( request @@ -76,7 +91,8 @@ def check_response(filepath, request, code, response): # Setting the 'id' tells jsonschema where the file is so that it # can correctly resolve relative $ref references in the schema schema['id'] = fileurl - jsonschema.validate(example, schema) + resolver = jsonschema.RefResolver(filepath, schema, handlers={"file": load_yaml}) + jsonschema.validate(example, schema, resolver=resolver) except Exception as e: raise ValueError("Error validating JSON schema for %r %r" % ( request, code @@ -103,6 +119,14 @@ def check_swagger_file(filepath): check_response(filepath, request, code, response) +def load_yaml(path): + if not path.startswith("file:///"): + raise Exception("Bad ref: %s" % (path,)) + path = path[len("file://"):] + with open(path, "r") as f: + return yaml.load(f) + + if __name__ == '__main__': paths = sys.argv[1:] if not paths: diff --git a/api/client-server/account-data.yaml b/api/client-server/account-data.yaml new file mode 100644 index 00000000000..934c59cf649 --- /dev/null +++ b/api/client-server/account-data.yaml @@ -0,0 +1,118 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Client Config API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/user/{userId}/account_data/{type}": + put: + summary: Set some account_data for the user. + description: |- + Set some account_data for the client. This config is only visible to the user + that set the account_data. The config will be synced to clients in the + top-level ``account_data``. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: userId + required: true + description: |- + The id of the user to set account_data for. The access token must be + authorized to make requests for this user id. + x-example: "@alice:example.com" + - in: path + type: string + name: type + required: true + description: |- + The event type of the account_data to set. Custom types should be + namespaced to avoid clashes. + x-example: "org.example.custom.config" + - in: body + name: content + required: true + description: |- + The content of the account_data + schema: + type: object + example: |- + {"custom_account_data_key": "custom_config_value"} + responses: + 200: + description: + The account_data was successfully added. + tags: + - User data + "/user/{userId}/rooms/{roomId}/account_data/{type}": + put: + summary: Set some account_data for the user. + description: |- + Set some account_data for the client on a given room. This config is only + visible to the user that set the account_data. The config will be synced to + clients in the per-room ``account_data``. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: userId + required: true + description: |- + The id of the user to set account_data for. The access token must be + authorized to make requests for this user id. + x-example: "@alice:example.com" + - in: path + type: string + name: roomId + required: true + description: |- + The id of the room to set account_data on. + x-example: "!726s6s6q:example.com" + - in: path + type: string + name: type + required: true + description: |- + The event type of the account_data to set. Custom types should be + namespaced to avoid clashes. + x-example: "org.example.custom.room.config" + - in: body + name: content + required: true + description: |- + The content of the account_data + schema: + type: object + example: |- + {"custom_account_data_key": "custom_account_data_value"} + responses: + 200: + description: + The account_data was successfully added. + tags: + - User data diff --git a/api/client-server/admin.yaml b/api/client-server/admin.yaml new file mode 100644 index 00000000000..a27596a88f9 --- /dev/null +++ b/api/client-server/admin.yaml @@ -0,0 +1,114 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Administration API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/admin/whois/{userId}": + get: + summary: Gets information about a particular user. + description: |- + Gets information about a particular user. + + This API may be restricted to only be called by the user being looked + up, or by a server admin. Server-local administrator privileges are not + specified in this document. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: userId + description: The user to look up. + required: true + x-example: "@peter:rabbit.rocks" + responses: + 200: + description: The lookup was successful. + examples: + application/json: |- + { + "user_id": "@peter:rabbit.rocks", + "devices": { + "teapot": { + "sessions": [ + { + "connections": [ + { + "ip": "127.0.0.1", + "last_seen": 1411996332123, + "user_agent": "curl/7.31.0-DEV" + }, + { + "ip": "10.0.0.2", + "last_seen": 1411996332123, + "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36" + } + ] + } + ] + } + } + } + schema: + type: object + properties: + user_id: + type: string + description: The Matrix user ID of the user. + devices: + type: object + description: |- + Each key is an identitfier for one of the user's devices. + additionalProperties: + type: object + title: DeviceInfo + properties: + sessions: + type: array + description: A user's sessions (i.e. what they did with an access token from one login). + items: + type: object + title: SessionInfo + properties: + connections: + type: array + description: Information particular connections in the session. + items: + type: object + title: ConnectionInfo + properties: + ip: + type: string + description: Most recently seen IP address of the session. + last_seen: + type: number + description: Unix timestamp that the session was last active. + user_agent: + type: string + description: User agent string last seen in the session. + tags: + - Server administration diff --git a/api/client-server/administrative_contact.yaml b/api/client-server/administrative_contact.yaml new file mode 100644 index 00000000000..73f2e05c956 --- /dev/null +++ b/api/client-server/administrative_contact.yaml @@ -0,0 +1,147 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Account Administrative Contact API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/account/3pid": + get: + summary: Gets a list of a user's third party identifiers. + description: |- + Gets a list of the third party identifiers that the homeserver has + associated with the user's account. + + This is *not* the same as the list of third party identifiers bound to + the user's Matrix ID in Identity Servers. + + Identifiers in this list may be used by the homeserver as, for example, + identifiers that it will accept to reset the user's account password. + security: + - accessToken: [] + responses: + 200: + description: The lookup was successful. + examples: + application/json: |- + { + "threepids": [ + { + "medium": "email", + "address": "monkey@banana.island" + } + ] + } + schema: + type: object + properties: + threepids: + type: array + items: + type: object + title: Third party identifier + properties: + medium: + type: string + description: The medium of the third party identifier. + enum: ["email"] + address: + type: string + description: The third party identifier address. + tags: + - User data + post: + summary: Adds contact information to the user's account. + description: Adds contact information to the user's account. + security: + - accessToken: [] + parameters: + - in: body + name: body + schema: + type: object + properties: + three_pid_creds: + title: "ThreePidCredentials" + type: object + description: The third party credentials to associate with the account. + properties: + client_secret: + type: string + description: The client secret used in the session with the Identity Server. + id_server: + type: string + description: The Identity Server to use. + sid: + type: string + description: The session identifier given by the Identity Server. + required: ["client_secret", "id_server", "sid"] + bind: + type: boolean + description: |- + Whether the homeserver should also bind this third party + identifier to the account's Matrix ID with the passed identity + server. Default: ``false``. + x-example: true + required: ["three_pid_creds"] + example: |- + { + "three_pid_creds": { + "id_server": "matrix.org", + "sid": "abc123987", + "client_secret": "d0n'tT3ll" + }, + "bind": false + } + responses: + 200: + description: The addition was successful. + examples: + application/json: "{}" + schema: + type: object + 403: + description: The credentials could not be verified with the identity server. + examples: + application/json: |- + { + "errcode": "M_THREEPID_AUTH_FAILED", + "error": "The third party credentials could not be verified by the identity server." + } + tags: + - User data + "/account/3pid/email/requestToken": + post: + summary: Requests a validation token be sent to the given email address for the purpose of adding an email address to an account + description: |- + Proxies the identity server API ``validate/email/requestToken``, but + first checks that the given email address is **not** already associated + with an account on this Home Server. This API should be used to request + validation tokens when adding an email address to an account. This API's + parameters and response is identical to that of the HS API + |/register/email/requestToken|_ endpoint. + responses: + 200: + description: An email was sent to the given address diff --git a/api/client-server/api-docs b/api/client-server/api-docs deleted file mode 100644 index 9c8c606654b..00000000000 --- a/api/client-server/api-docs +++ /dev/null @@ -1,50 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "apis": [ - { - "path": "-login", - "description": "Login operations" - }, - { - "path": "-registration", - "description": "Registration operations" - }, - { - "path": "-rooms", - "description": "Room operations" - }, - { - "path": "-profile", - "description": "Profile operations" - }, - { - "path": "-presence", - "description": "Presence operations" - }, - { - "path": "-events", - "description": "Event operations" - }, - { - "path": "-directory", - "description": "Directory operations" - }, - { - "path": "-content", - "description": "Content repository operations" - } - ], - "authorizations": { - "token": { - "scopes": [] - } - }, - "info": { - "title": "Matrix Client-Server API Reference", - "description": "This contains the client-server API for the reference implementation of the home server", - "termsOfServiceUrl": "http://matrix.org", - "license": "Apache 2.0", - "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html" - } -} diff --git a/api/client-server/api-docs-content b/api/client-server/api-docs-content deleted file mode 100644 index a0a31da077d..00000000000 --- a/api/client-server/api-docs-content +++ /dev/null @@ -1,119 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "basePath": "http://localhost:8008/_matrix", - "resourcePath": "/media/v1/", - "apis": [ - { - "path": "/media/v1/upload", - "operations": [ - { - "method": "POST", - "summary": "Upload some content to the content repository.", - "type": "ContentUploadResponse", - "nickname": "upload_content", - "parameters": [ - { - "name": "body", - "description": "The file to upload.", - "required": true, - "type": "file", - "paramType": "body" - } - ] - } - ] - }, - { - "path": "/media/v1/download/{serverName}/{mediaId}", - "operations": [ - { - "method": "GET", - "summary": "Get the content stored at this address.", - "type": "file", - "nickname": "download_content", - "parameters": [ - { - "name": "serverName", - "description": "The serverName from the mxc:/// URI (the authority component).", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "mediaId", - "description": "The mediaId from the mxc:/// URI (the path component).", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/media/v1/thumbnail/{serverName}/{mediaId}", - "operations": [ - { - "method": "GET", - "summary": "Get a thumbnail of the content stored at this address.", - "type": "file", - "nickname": "thumbnail_content", - "parameters": [ - { - "name": "serverName", - "description": "The serverName from the mxc:/// URI (the authority component).", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "mediaId", - "description": "The mediaId from the mxc:/// URI (the path component).", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "width", - "description": "The desired width of the thumbnail.", - "required": false, - "type": "integer", - "paramType": "query" - }, - { - "name": "height", - "description": "The desired height of the thumbnail.", - "required": false, - "type": "integer", - "paramType": "query" - }, - { - "name": "method", - "description": "The desired resizing method.", - "enum": [ - "crop", - "scale" - ], - "required": false, - "type": "string", - "paramType": "query" - } - ] - } - ] - } - ], - "models": { - "ContentUploadResponse": { - "id": "ContentUploadResponse", - "properties": { - "content_uri": { - "type": "string", - "description": "The mxc:// URI where this content is stored. This is of the form 'mxc://{serverName}/{mediaId}'", - "required": true - } - } - } - } -} diff --git a/api/client-server/api-docs-directory b/api/client-server/api-docs-directory deleted file mode 100644 index 5dda5806587..00000000000 --- a/api/client-server/api-docs-directory +++ /dev/null @@ -1,101 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "resourcePath": "/directory", - "produces": [ - "application/json" - ], - "apis": [ - { - "path": "/directory/room/{roomAlias}", - "operations": [ - { - "method": "GET", - "summary": "Get the room ID corresponding to this room alias.", - "notes": "Volatile: This API is likely to change.", - "type": "DirectoryResponse", - "nickname": "get_room_id_for_alias", - "parameters": [ - { - "name": "roomAlias", - "description": "The room alias.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "PUT", - "summary": "Create a new mapping from room alias to room ID.", - "notes": "Volatile: This API is likely to change.", - "type": "void", - "nickname": "add_room_alias", - "parameters": [ - { - "name": "roomAlias", - "description": "The room alias to set.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "description": "The room ID to set.", - "required": true, - "type": "RoomAliasRequest", - "paramType": "body" - } - ] - }, - { - "method": "DELETE", - "summary": "Removes a mapping of room alias to room ID.", - "notes": "Only privileged users can perform this action.", - "type": "void", - "nickname": "remove_room_alias", - "parameters": [ - { - "name": "roomAlias", - "description": "The room alias to remove.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - } - ], - "models": { - "DirectoryResponse": { - "id": "DirectoryResponse", - "properties": { - "room_id": { - "type": "string", - "description": "The fully-qualified room ID.", - "required": true - }, - "servers": { - "type": "array", - "items": { - "$ref": "string" - }, - "description": "A list of servers that know about this room.", - "required": true - } - } - }, - "RoomAliasRequest": { - "id": "RoomAliasRequest", - "properties": { - "room_id": { - "type": "string", - "description": "The room ID to map the alias to.", - "required": true - } - } - } - } -} diff --git a/api/client-server/api-docs-events b/api/client-server/api-docs-events deleted file mode 100644 index 1bdb9b034a4..00000000000 --- a/api/client-server/api-docs-events +++ /dev/null @@ -1,247 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "resourcePath": "/events", - "produces": [ - "application/json" - ], - "apis": [ - { - "path": "/events", - "operations": [ - { - "method": "GET", - "summary": "Listen on the event stream", - "notes": "This can only be done by the logged in user. This will block until an event is received, or until the timeout is reached.", - "type": "PaginationChunk", - "nickname": "get_event_stream", - "parameters": [ - { - "name": "from", - "description": "The token to stream from.", - "required": false, - "type": "string", - "paramType": "query" - }, - { - "name": "timeout", - "description": "The maximum time in milliseconds to wait for an event.", - "required": false, - "type": "integer", - "paramType": "query" - } - ] - } - ], - - "responseMessages": [ - { - "code": 400, - "message": "Bad pagination token." - } - ] - }, - { - "path": "/events/{eventId}", - "operations": [ - { - "method": "GET", - "summary": "Get information about a single event.", - "notes": "Get information about a single event.", - "type": "Event", - "nickname": "get_event", - "parameters": [ - { - "name": "eventId", - "description": "The event ID to get.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 404, - "message": "Event not found." - } - ] - } - ] - }, - { - "path": "/initialSync", - "operations": [ - { - "method": "GET", - "summary": "Get this user's current state.", - "notes": "Get this user's current state.", - "type": "InitialSyncResponse", - "nickname": "initial_sync", - "parameters": [ - { - "name": "limit", - "description": "The maximum number of messages to return for each room.", - "type": "integer", - "paramType": "query", - "required": false - } - ] - } - ] - }, - { - "path": "/publicRooms", - "operations": [ - { - "method": "GET", - "summary": "Get a list of publicly visible rooms.", - "type": "PublicRoomsPaginationChunk", - "nickname": "get_public_room_list" - } - ] - } - ], - "models": { - "PaginationChunk": { - "id": "PaginationChunk", - "properties": { - "start": { - "type": "string", - "description": "A token which correlates to the first value in \"chunk\" for paginating.", - "required": true - }, - "end": { - "type": "string", - "description": "A token which correlates to the last value in \"chunk\" for paginating.", - "required": true - }, - "chunk": { - "type": "array", - "description": "An array of events.", - "required": true, - "items": { - "$ref": "Event" - } - } - } - }, - "Event": { - "id": "Event", - "properties": { - "event_id": { - "type": "string", - "description": "An ID which uniquely identifies this event.", - "required": true - }, - "room_id": { - "type": "string", - "description": "The room in which this event occurred.", - "required": true - } - } - }, - "PublicRoomInfo": { - "id": "PublicRoomInfo", - "properties": { - "aliases": { - "type": "array", - "description": "A list of room aliases for this room.", - "items": { - "$ref": "string" - } - }, - "name": { - "type": "string", - "description": "The name of the room, as given by the m.room.name state event." - }, - "room_id": { - "type": "string", - "description": "The room ID for this public room.", - "required": true - }, - "topic": { - "type": "string", - "description": "The topic of this room, as given by the m.room.topic state event." - } - } - }, - "PublicRoomsPaginationChunk": { - "id": "PublicRoomsPaginationChunk", - "properties": { - "start": { - "type": "string", - "description": "A token which correlates to the first value in \"chunk\" for paginating.", - "required": true - }, - "end": { - "type": "string", - "description": "A token which correlates to the last value in \"chunk\" for paginating.", - "required": true - }, - "chunk": { - "type": "array", - "description": "A list of public room data.", - "required": true, - "items": { - "$ref": "PublicRoomInfo" - } - } - } - }, - "InitialSyncResponse": { - "id": "InitialSyncResponse", - "properties": { - "end": { - "type": "string", - "description": "A streaming token which can be used with /events to continue from this snapshot of data.", - "required": true - }, - "presence": { - "type": "array", - "description": "A list of presence events.", - "items": { - "$ref": "Event" - }, - "required": false - }, - "rooms": { - "type": "array", - "description": "A list of initial sync room data.", - "required": false, - "items": { - "$ref": "InitialSyncRoomData" - } - } - } - }, - "InitialSyncRoomData": { - "id": "InitialSyncRoomData", - "properties": { - "membership": { - "type": "string", - "description": "This user's membership state in this room.", - "required": true - }, - "room_id": { - "type": "string", - "description": "The ID of this room.", - "required": true - }, - "messages": { - "type": "PaginationChunk", - "description": "The most recent messages for this room, governed by the limit parameter.", - "required": false - }, - "state": { - "type": "array", - "description": "A list of state events representing the current state of the room.", - "required": false, - "items": { - "$ref": "Event" - } - } - } - } - } -} diff --git a/api/client-server/api-docs-login b/api/client-server/api-docs-login deleted file mode 100644 index d6f8d84f29e..00000000000 --- a/api/client-server/api-docs-login +++ /dev/null @@ -1,120 +0,0 @@ -{ - "apiVersion": "1.0.0", - "apis": [ - { - "operations": [ - { - "method": "GET", - "nickname": "get_login_info", - "notes": "All login stages MUST be mentioned if there is >1 login type.", - "summary": "Get the login mechanism to use when logging in.", - "type": "LoginFlows" - }, - { - "method": "POST", - "nickname": "submit_login", - "notes": "If this is part of a multi-stage login, there MUST be a 'session' key.", - "parameters": [ - { - "description": "A login submission", - "name": "body", - "paramType": "body", - "required": true, - "type": "LoginSubmission" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Bad login type" - }, - { - "code": 400, - "message": "Missing JSON keys" - } - ], - "summary": "Submit a login action.", - "type": "LoginResult" - } - ], - "path": "/login" - } - ], - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "consumes": [ - "application/json" - ], - "models": { - "LoginFlows": { - "id": "LoginFlows", - "properties": { - "flows": { - "description": "A list of valid login flows.", - "type": "array", - "items": { - "$ref": "LoginInfo" - } - } - } - }, - "LoginInfo": { - "id": "LoginInfo", - "properties": { - "stages": { - "description": "Multi-stage login only: An array of all the login types required to login.", - "items": { - "$ref": "string" - }, - "type": "array" - }, - "type": { - "description": "The login type that must be used when logging in.", - "type": "string" - } - } - }, - "LoginResult": { - "id": "LoginResult", - "properties": { - "access_token": { - "description": "The access token for this user's login if this is the final stage of the login process.", - "type": "string" - }, - "user_id": { - "description": "The user's fully-qualified user ID.", - "type": "string" - }, - "next": { - "description": "Multi-stage login only: The next login type to submit.", - "type": "string" - }, - "session": { - "description": "Multi-stage login only: The session token to send when submitting the next login type.", - "type": "string" - } - } - }, - "LoginSubmission": { - "id": "LoginSubmission", - "properties": { - "type": { - "description": "The type of login being submitted.", - "type": "string" - }, - "session": { - "description": "Multi-stage login only: The session token from an earlier login stage.", - "type": "string" - }, - "_login_type_defined_keys_": { - "description": "Keys as defined by the specified login type, e.g. \"user\", \"password\"" - } - } - } - }, - "produces": [ - "application/json" - ], - "resourcePath": "/login", - "swaggerVersion": "1.2" -} - diff --git a/api/client-server/api-docs-presence b/api/client-server/api-docs-presence deleted file mode 100644 index 6b22446024e..00000000000 --- a/api/client-server/api-docs-presence +++ /dev/null @@ -1,164 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "resourcePath": "/presence", - "produces": [ - "application/json" - ], - "consumes": [ - "application/json" - ], - "apis": [ - { - "path": "/presence/{userId}/status", - "operations": [ - { - "method": "PUT", - "summary": "Update this user's presence state.", - "notes": "This can only be done by the logged in user.", - "type": "void", - "nickname": "update_presence", - "parameters": [ - { - "name": "body", - "description": "The new presence state", - "required": true, - "type": "PresenceUpdate", - "paramType": "body" - }, - { - "name": "userId", - "description": "The user whose presence to set.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "GET", - "summary": "Get this user's presence state.", - "notes": "Get this user's presence state.", - "type": "PresenceUpdate", - "nickname": "get_presence", - "parameters": [ - { - "name": "userId", - "description": "The user whose presence to get.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/presence/list/{userId}", - "operations": [ - { - "method": "GET", - "summary": "Retrieve a list of presences for all of this user's friends.", - "notes": "", - "type": "array", - "items": { - "$ref": "Presence" - }, - "nickname": "get_presence_list", - "parameters": [ - { - "name": "userId", - "description": "The user whose presence list to get.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "POST", - "summary": "Add or remove users from this presence list.", - "notes": "Add or remove users from this presence list.", - "type": "void", - "nickname": "modify_presence_list", - "parameters": [ - { - "name": "userId", - "description": "The user whose presence list is being modified.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "description": "The modifications to make to this presence list.", - "required": true, - "type": "PresenceListModifications", - "paramType": "body" - } - ] - } - ] - } - ], - "models": { - "PresenceUpdate": { - "id": "PresenceUpdate", - "properties": { - "presence": { - "type": "string", - "description": "Enum: The presence state.", - "enum": [ - "offline", - "unavailable", - "online", - "free_for_chat" - ] - }, - "status_msg": { - "type": "string", - "description": "The user-defined message associated with this presence state." - } - }, - "subTypes": [ - "Presence" - ] - }, - "Presence": { - "id": "Presence", - "properties": { - "last_active_ago": { - "type": "integer", - "format": "int64", - "description": "The last time this user performed an action on their home server." - }, - "user_id": { - "type": "string", - "description": "The fully qualified user ID" - } - } - }, - "PresenceListModifications": { - "id": "PresenceListModifications", - "properties": { - "invite": { - "type": "array", - "description": "A list of user IDs to add to the list.", - "items": { - "type": "string", - "description": "A fully qualified user ID." - } - }, - "drop": { - "type": "array", - "description": "A list of user IDs to remove from the list.", - "items": { - "type": "string", - "description": "A fully qualified user ID." - } - } - } - } - } -} diff --git a/api/client-server/api-docs-profile b/api/client-server/api-docs-profile deleted file mode 100644 index d2fccaa67d3..00000000000 --- a/api/client-server/api-docs-profile +++ /dev/null @@ -1,122 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "resourcePath": "/profile", - "produces": [ - "application/json" - ], - "consumes": [ - "application/json" - ], - "apis": [ - { - "path": "/profile/{userId}/displayname", - "operations": [ - { - "method": "PUT", - "summary": "Set a display name.", - "notes": "This can only be done by the logged in user.", - "type": "void", - "nickname": "set_display_name", - "parameters": [ - { - "name": "body", - "description": "The new display name for this user.", - "required": true, - "type": "DisplayName", - "paramType": "body" - }, - { - "name": "userId", - "description": "The user whose display name to set.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "GET", - "summary": "Get a display name.", - "notes": "This can be done by anyone.", - "type": "DisplayName", - "nickname": "get_display_name", - "parameters": [ - { - "name": "userId", - "description": "The user whose display name to get.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/profile/{userId}/avatar_url", - "operations": [ - { - "method": "PUT", - "summary": "Set an avatar URL.", - "notes": "This can only be done by the logged in user.", - "type": "void", - "nickname": "set_avatar_url", - "parameters": [ - { - "name": "body", - "description": "The new avatar url for this user.", - "required": true, - "type": "AvatarUrl", - "paramType": "body" - }, - { - "name": "userId", - "description": "The user whose avatar url to set.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "GET", - "summary": "Get an avatar url.", - "notes": "This can be done by anyone.", - "type": "AvatarUrl", - "nickname": "get_avatar_url", - "parameters": [ - { - "name": "userId", - "description": "The user whose avatar url to get.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - } - ], - "models": { - "DisplayName": { - "id": "DisplayName", - "properties": { - "displayname": { - "type": "string", - "description": "The textual display name" - } - } - }, - "AvatarUrl": { - "id": "AvatarUrl", - "properties": { - "avatar_url": { - "type": "string", - "description": "A url to an image representing an avatar." - } - } - } - } -} diff --git a/api/client-server/api-docs-registration b/api/client-server/api-docs-registration deleted file mode 100644 index 11c170c3ecb..00000000000 --- a/api/client-server/api-docs-registration +++ /dev/null @@ -1,120 +0,0 @@ -{ - "apiVersion": "1.0.0", - "apis": [ - { - "operations": [ - { - "method": "GET", - "nickname": "get_registration_info", - "notes": "All login stages MUST be mentioned if there is >1 login type.", - "summary": "Get the login mechanism to use when registering.", - "type": "RegistrationFlows" - }, - { - "method": "POST", - "nickname": "submit_registration", - "notes": "If this is part of a multi-stage registration, there MUST be a 'session' key.", - "parameters": [ - { - "description": "A registration submission", - "name": "body", - "paramType": "body", - "required": true, - "type": "RegistrationSubmission" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Bad login type" - }, - { - "code": 400, - "message": "Missing JSON keys" - } - ], - "summary": "Submit a registration action.", - "type": "RegistrationResult" - } - ], - "path": "/register" - } - ], - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "consumes": [ - "application/json" - ], - "models": { - "RegistrationFlows": { - "id": "RegistrationFlows", - "properties": { - "flows": { - "description": "A list of valid registration flows.", - "type": "array", - "items": { - "$ref": "RegistrationInfo" - } - } - } - }, - "RegistrationInfo": { - "id": "RegistrationInfo", - "properties": { - "stages": { - "description": "Multi-stage registration only: An array of all the login types required to registration.", - "items": { - "$ref": "string" - }, - "type": "array" - }, - "type": { - "description": "The first login type that must be used when logging in.", - "type": "string" - } - } - }, - "RegistrationResult": { - "id": "RegistrationResult", - "properties": { - "access_token": { - "description": "The access token for this user's registration if this is the final stage of the registration process.", - "type": "string" - }, - "user_id": { - "description": "The user's fully-qualified user ID.", - "type": "string" - }, - "next": { - "description": "Multi-stage registration only: The next registration type to submit.", - "type": "string" - }, - "session": { - "description": "Multi-stage registration only: The session token to send when submitting the next registration type.", - "type": "string" - } - } - }, - "RegistrationSubmission": { - "id": "RegistrationSubmission", - "properties": { - "type": { - "description": "The type of registration being submitted.", - "type": "string" - }, - "session": { - "description": "Multi-stage registration only: The session token from an earlier registration stage.", - "type": "string" - }, - "_registration_type_defined_keys_": { - "description": "Keys as defined by the specified registration type, e.g. \"user\", \"password\"" - } - } - } - }, - "produces": [ - "application/json" - ], - "resourcePath": "/register", - "swaggerVersion": "1.2" -} - diff --git a/api/client-server/api-docs-rooms b/api/client-server/api-docs-rooms deleted file mode 100644 index 0d3d9fbcb91..00000000000 --- a/api/client-server/api-docs-rooms +++ /dev/null @@ -1,1128 +0,0 @@ -{ - "apiVersion": "1.0.0", - "swaggerVersion": "1.2", - "basePath": "http://localhost:8008/_matrix/client/api/v1", - "resourcePath": "/rooms", - "produces": [ - "application/json" - ], - "consumes": [ - "application/json" - ], - "authorizations": { - "token": [] - }, - "apis": [ - { - "path": "/rooms/{roomId}/send/{eventType}", - "operations": [ - { - "method": "POST", - "summary": "Send a generic non-state event to this room.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "EventId", - "nickname": "send_non_state_event", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The event contents", - "required": true, - "type": "EventContent", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to send the message in.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "eventType", - "description": "The type of event to send.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/state/{eventType}/{stateKey}", - "operations": [ - { - "method": "PUT", - "summary": "Send a generic state event to this room.", - "notes": "The state key can be omitted, such that you can PUT to /rooms/{roomId}/state/{eventType}. The state key defaults to a 0 length string in this case.", - "type": "void", - "nickname": "send_state_event", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The event contents", - "required": true, - "type": "EventContent", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to send the message in.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "eventType", - "description": "The type of event to send.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "stateKey", - "description": "An identifier used to specify clobbering semantics. State events with the same (roomId, eventType, stateKey) will be replaced.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/send/m.room.message", - "operations": [ - { - "method": "POST", - "summary": "Send a message in this room.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "EventId", - "nickname": "send_message", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The message contents", - "required": true, - "type": "Message", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to send the message in.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/state/m.room.topic", - "operations": [ - { - "method": "PUT", - "summary": "Set the topic for this room.", - "notes": "Set the topic for this room.", - "type": "void", - "nickname": "set_topic", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The topic contents", - "required": true, - "type": "Topic", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to set the topic in.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "GET", - "summary": "Get the topic for this room.", - "notes": "Get the topic for this room.", - "type": "Topic", - "nickname": "get_topic", - "parameters": [ - { - "name": "roomId", - "description": "The room to get topic in.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 404, - "message": "Topic not found." - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/state/m.room.name", - "operations": [ - { - "method": "PUT", - "summary": "Set the name of this room.", - "notes": "Set the name of this room.", - "type": "void", - "nickname": "set_room_name", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The name contents", - "required": true, - "type": "RoomName", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to set the name of.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - }, - { - "method": "GET", - "summary": "Get the room's name.", - "notes": "", - "type": "RoomName", - "nickname": "get_room_name", - "parameters": [ - { - "name": "roomId", - "description": "The room to get the name of.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 404, - "message": "Name not found." - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/state/m.room.power_levels", - "operations": [ - { - "method": "PUT", - "summary": "Set the power levels for this room.", - "notes": "This has to be set atomically. The levels set will clobber what was previously there.", - "type": "void", - "nickname": "set_room_power_levels", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The power levels", - "required": true, - "type": "RoomPowerLevels", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to set the power levels for.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/send/m.room.message.feedback", - "operations": [ - { - "method": "POST", - "summary": "Send feedback to a message.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "EventId", - "nickname": "send_feedback", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The feedback contents", - "required": true, - "type": "Feedback", - "paramType": "body" - }, - { - "name": "roomId", - "description": "The room to send the feedback in.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Bad feedback type." - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/invite", - "operations": [ - { - "method": "POST", - "summary": "Invite a user to this room.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "void", - "nickname": "invite", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "roomId", - "description": "The room which has this user.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "description": "The user to invite.", - "required": true, - "type": "InviteRequest", - "paramType": "body" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/join", - "operations": [ - { - "method": "POST", - "summary": "Join this room.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "void", - "nickname": "join_room", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "roomId", - "description": "The room to join.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "required": true, - "type": "JoinRequest", - "paramType": "body" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/leave", - "operations": [ - { - "method": "POST", - "summary": "Leave this room.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "void", - "nickname": "leave", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "roomId", - "description": "The room to leave.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "required": true, - "type": "LeaveRequest", - "paramType": "body" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/ban", - "operations": [ - { - "method": "POST", - "summary": "Ban a user in the room.", - "notes": "This operation can also be done as a PUT by suffixing /{txnId}. The caller must have the required power level to do this operation.", - "type": "void", - "nickname": "ban", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "roomId", - "description": "The room which has the user to ban.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "description": "The user to ban.", - "required": true, - "type": "BanRequest", - "paramType": "body" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/state/m.room.member/{userId}", - "operations": [ - { - "method": "PUT", - "summary": "Change the membership state for a user in a room.", - "notes": "Change the membership state for a user in a room.", - "type": "void", - "nickname": "set_membership", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The new membership state", - "required": true, - "type": "Member", - "paramType": "body" - }, - { - "name": "userId", - "description": "The user whose membership is being changed.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "roomId", - "description": "The room which has this user.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "No membership key." - }, - { - "code": 400, - "message": "Bad membership value." - }, - { - "code": 403, - "message": "When inviting: You are not in the room." - }, - { - "code": 403, - "message": "When inviting: is already in the room." - }, - { - "code": 403, - "message": "When joining: Cannot force another user to join." - }, - { - "code": 403, - "message": "When joining: You are not invited to this room." - } - ] - }, - { - "method": "GET", - "summary": "Get the membership state of a user in a room.", - "notes": "Get the membership state of a user in a room.", - "type": "Member", - "nickname": "get_membership", - "parameters": [ - { - "name": "userId", - "description": "The user whose membership state you want to get.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "roomId", - "description": "The room which has this user.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 404, - "message": "Member not found." - } - ] - } - ] - }, - { - "path": "/join/{roomAliasOrId}", - "operations": [ - { - "method": "POST", - "summary": "Join a room via a room alias or room ID.", - "notes": "This endpoint, unlike /rooms/{roomId}/join allows the client to join a room by it's alias. This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "JoinRoomInfo", - "nickname": "join", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "roomAliasOrId", - "description": "The room alias or room ID to join.", - "required": true, - "type": "string", - "paramType": "path" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Bad room alias." - } - ] - } - ] - }, - { - "path": "/createRoom", - "operations": [ - { - "method": "POST", - "summary": "Create a room.", - "notes": "Create a room. This operation can also be done as a PUT by suffixing /{txnId}.", - "type": "RoomInfo", - "nickname": "create_room", - "consumes": [ - "application/json" - ], - "parameters": [ - { - "name": "body", - "description": "The desired configuration for the room.", - "required": true, - "type": "RoomConfig", - "paramType": "body" - } - ], - "responseMessages": [ - { - "code": 400, - "message": "Body must be JSON." - }, - { - "code": 400, - "message": "Room alias already taken." - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/messages", - "operations": [ - { - "method": "GET", - "summary": "Get a list of messages for this room.", - "notes": "Get a list of messages for this room.", - "type": "MessagePaginationChunk", - "nickname": "get_messages", - "parameters": [ - { - "name": "roomId", - "description": "The room to get messages in.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "from", - "description": "The token to start getting results from.", - "required": false, - "type": "string", - "paramType": "query" - }, - { - "name": "to", - "description": "The token to stop getting results at.", - "required": false, - "type": "string", - "paramType": "query" - }, - { - "name": "limit", - "description": "The maximum number of messages to return.", - "required": false, - "type": "integer", - "paramType": "query" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/members", - "operations": [ - { - "method": "GET", - "summary": "Get a list of members for this room.", - "notes": "Get a list of members for this room.", - "type": "MemberPaginationChunk", - "nickname": "get_members", - "parameters": [ - { - "name": "roomId", - "description": "The room to get a list of members from.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "from", - "description": "The token to start getting results from.", - "required": false, - "type": "string", - "paramType": "query" - }, - { - "name": "to", - "description": "The token to stop getting results at.", - "required": false, - "type": "string", - "paramType": "query" - }, - { - "name": "limit", - "description": "The maximum number of members to return.", - "required": false, - "type": "integer", - "paramType": "query" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/state", - "operations": [ - { - "method": "GET", - "summary": "Get a list of all the current state events for this room.", - "notes": "This is equivalent to the events returned under the 'state' key for this room in /initialSync.", - "type": "array", - "items": { - "$ref": "Event" - }, - "nickname": "get_state_events", - "parameters": [ - { - "name": "roomId", - "description": "The room to get a list of current state events from.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/typing/{userId}", - "operations": [ - { - "method": "PUT", - "summary": "Inform the server that this user is typing.", - "notes": "Only the authorised user can send typing notifications.", - "type": "void", - "nickname": "put_typing", - "parameters": [ - { - "name": "roomId", - "description": "The room to send the m.typing event into.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "userId", - "description": "The user ID of the typer.", - "required": true, - "type": "string", - "paramType": "path" - }, - { - "name": "body", - "description": "The typing information.", - "required": true, - "type": "Typing", - "paramType": "body" - } - ] - } - ] - }, - { - "path": "/rooms/{roomId}/initialSync", - "operations": [ - { - "method": "GET", - "summary": "Get all the current information for this room, including messages and state events.", - "type": "InitialSyncRoomData", - "nickname": "get_room_sync_data", - "parameters": [ - { - "name": "roomId", - "description": "The room to get information for.", - "required": true, - "type": "string", - "paramType": "path" - } - ] - } - ] - } - ], - "models": { - "Topic": { - "id": "Topic", - "properties": { - "topic": { - "type": "string", - "description": "The topic text" - } - } - }, - "RoomName": { - "id": "RoomName", - "properties": { - "name": { - "type": "string", - "description": "The human-readable name for the room. Can contain spaces." - } - } - }, - "UserPowerLevels": { - "id": "UserPowerLevels", - "properties": { - "__user_id__": { - "type": "integer", - "description": "The power level for __user_id__" - } - } - }, - "EventPowerLevels": { - "id": "UserPowerLevels", - "properties": { - "__event_type__": { - "type": "integer", - "description": "The power level required in order to send __event_type__ events." - } - } - }, - "RoomPowerLevels": { - "id": "RoomPowerLevels", - "properties": { - "ban": { - "type": "integer", - "description": "The minimum level required in order to ban someone." - }, - "kick": { - "type": "integer", - "description": "The minimum level required in order to kick someone." - }, - "redact": { - "type": "integer", - "description": "The minimum level required in order to redact a message." - }, - "users_default": { - "type": "integer", - "description": "The default power level of a user who is not in the 'users' list." - }, - "state_default": { - "type": "integer", - "description": "The default power level required in order to send state events." - }, - "events_default": { - "type": "integer", - "description": "The default power level required in order to send non-state events." - }, - "users": { - "type": "UserPowerLevels", - "description": "Mappings of user ID to power level." - }, - "events": { - "type": "EventPowerLevels", - "description": "Mappings of event type to power level." - } - } - }, - "Message": { - "id": "Message", - "properties": { - "msgtype": { - "type": "string", - "description": "The type of message being sent, e.g. \"m.text\"", - "required": true - }, - "_msgtype_defined_keys_": { - "description": "Additional keys as defined by the msgtype, e.g. \"body\"" - } - } - }, - "Feedback": { - "id": "Feedback", - "properties": { - "target_event_id": { - "type": "string", - "description": "The event ID being acknowledged.", - "required": true - }, - "type": { - "type": "string", - "description": "The type of feedback. Either 'delivered' or 'read'.", - "required": true - } - } - }, - "Member": { - "id": "Member", - "properties": { - "membership": { - "type": "string", - "description": "Enum: The membership state of this member.", - "enum": [ - "invite", - "join", - "leave", - "ban" - ] - } - } - }, - "RoomInfo": { - "id": "RoomInfo", - "properties": { - "room_id": { - "type": "string", - "description": "The allocated room ID.", - "required": true - }, - "room_alias": { - "type": "string", - "description": "The alias for the room.", - "required": false - } - } - }, - "JoinRoomInfo": { - "id": "JoinRoomInfo", - "properties": { - "room_id": { - "type": "string", - "description": "The room ID joined, if joined via a room alias only.", - "required": true - } - } - }, - "Typing": { - "id": "Typing", - "properties": { - "typing": { - "type": "boolean", - "description": "True if the user is currently typing.", - "required": true - }, - "timeout": { - "type": "integer", - "description": "The length of time until the user should be treated as no longer typing, in milliseconds. Can be omitted if they are no longer typing.", - "required": true - } - } - }, - "RoomConfig": { - "id": "RoomConfig", - "properties": { - "visibility": { - "type": "string", - "description": "Enum: The room visibility. The room_alias_name is required if the visibility is public; without it the room will remain private.", - "required": false, - "enum": [ - "public", - "private" - ] - }, - "room_alias_name": { - "type": "string", - "description": "Localpart of the alias to give the new room. The home server will attach its domain name after this.", - "required": false - }, - "name": { - "type": "string", - "description": "Sets the name of the room. Send a m.room.name event after creating the room with the 'name' key specified.", - "required": false - }, - "topic": { - "type": "string", - "description": "Sets the topic for the room. Send a m.room.topic event after creating the room with the 'topic' key specified.", - "required": false - }, - "invite": { - "type": "array", - "description": "The list of user IDs to invite. Sends m.room.member events after creating the room.", - "items": { - "$ref": "string" - }, - "required": false - } - } - }, - "PaginationRequest": { - "id": "PaginationRequest", - "properties": { - "from": { - "type": "string", - "description": "The token to start getting results from." - }, - "to": { - "type": "string", - "description": "The token to stop getting results at." - }, - "limit": { - "type": "integer", - "description": "The maximum number of entries to return." - } - } - }, - "PaginationChunk": { - "id": "PaginationChunk", - "properties": { - "start": { - "type": "string", - "description": "A token which correlates to the first value in \"chunk\" for paginating.", - "required": true - }, - "end": { - "type": "string", - "description": "A token which correlates to the last value in \"chunk\" for paginating.", - "required": true - } - }, - "subTypes": [ - "MessagePaginationChunk" - ] - }, - "MessagePaginationChunk": { - "id": "MessagePaginationChunk", - "properties": { - "chunk": { - "type": "array", - "description": "A list of message events.", - "items": { - "$ref": "MessageEvent" - }, - "required": true - } - } - }, - "MemberPaginationChunk": { - "id": "MemberPaginationChunk", - "properties": { - "chunk": { - "type": "array", - "description": "A list of member events.", - "items": { - "$ref": "MemberEvent" - }, - "required": true - } - } - }, - "Event": { - "id": "Event", - "properties": { - "event_id": { - "type": "string", - "description": "An ID which uniquely identifies this event. This is automatically set by the server.", - "required": true - }, - "room_id": { - "type": "string", - "description": "The room in which this event occurred. This is automatically set by the server.", - "required": true - }, - "type": { - "type": "string", - "description": "The event type.", - "required": true - } - }, - "subTypes": [ - "MessageEvent" - ] - }, - "EventId": { - "id": "EventId", - "properties": { - "event_id": { - "type": "string", - "description": "The allocated event ID for this event.", - "required": true - } - } - }, - "EventContent": { - "id": "EventContent", - "properties": { - "__event_content_keys__": { - "type": "string", - "description": "Event-specific content keys and values.", - "required": false - } - } - }, - "MessageEvent": { - "id": "MessageEvent", - "properties": { - "content": { - "type": "Message" - } - } - }, - "MemberEvent": { - "id": "MemberEvent", - "properties": { - "content": { - "type": "Member" - } - } - }, - "InviteRequest": { - "id": "InviteRequest", - "properties": { - "user_id": { - "type": "string", - "description": "The fully-qualified user ID." - } - } - }, - "JoinRequest": { - "id": "JoinRequest", - "properties": {} - }, - "LeaveRequest": { - "id": "LeaveRequest", - "properties": {} - }, - "BanRequest": { - "id": "BanRequest", - "properties": { - "user_id": { - "type": "string", - "description": "The fully-qualified user ID." - }, - "reason": { - "type": "string", - "description": "The reason for the ban." - } - } - }, - "InitialSyncRoomData": { - "id": "InitialSyncRoomData", - "properties": { - "membership": { - "type": "string", - "description": "This user's membership state in this room.", - "required": true - }, - "room_id": { - "type": "string", - "description": "The ID of this room.", - "required": true - }, - "messages": { - "type": "MessagePaginationChunk", - "description": "The most recent messages for this room, governed by the limit parameter.", - "required": false - }, - "state": { - "type": "array", - "description": "A list of state events representing the current state of the room.", - "required": false, - "items": { - "$ref": "Event" - } - }, - "presence": { - "type": "array", - "description": "A list of m.presence events representing the current presence state of the room members.", - "required": false, - "items": { - "$ref": "Event" - } - } - } - } - } -} diff --git a/api/client-server/banning.yaml b/api/client-server/banning.yaml new file mode 100644 index 00000000000..cdeaaa5d5ea --- /dev/null +++ b/api/client-server/banning.yaml @@ -0,0 +1,139 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Banning API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/ban": + post: + summary: Ban a user in the room. + description: |- + Ban a user in the room. If the user is currently in the room, also kick them. + + When a user is banned from a room, they may not join it or be invited to it until they are unbanned. + + The caller must have the required power level in order to perform this operation. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier (not alias) from which the user should be banned. + required: true + x-example: "!e42d8c:matrix.org" + - in: body + name: body + required: true + schema: + type: object + example: |- + { + "reason": "Telling unfunny jokes", + "user_id": "@cheeky_monkey:matrix.org" + } + properties: + user_id: + type: string + description: The fully qualified user ID of the user being banned. + reason: + type: string + description: The reason the user has been banned. + required: ["user_id"] + responses: + 200: + description: The user has been kicked and banned from the room. + examples: + application/json: |- + {} + schema: + type: object + 403: + description: |- + You do not have permission to ban the user from the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: + + - The banner is not currently in the room. + - The banner's power level is insufficient to ban users from the room. + examples: + application/json: |- + { + "errcode": "M_FORBIDDEN", + "error": "You do not have a high enough power level to ban from this room." + } + tags: + - Room membership + "/rooms/{roomId}/unban": + post: + summary: Unban a user from the room. + description: |- + Unban a user from the room. This allows them to be invited to the room, + and join if they would otherwise be allowed to join according to its join rules. + + The caller must have the required power level in order to perform this operation. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier (not alias) from which the user should be unbanned. + required: true + x-example: "!e42d8c:matrix.org" + - in: body + name: body + required: true + schema: + type: object + example: |- + { + "user_id": "@cheeky_monkey:matrix.org" + } + properties: + user_id: + type: string + description: The fully qualified user ID of the user being unbanned. + required: ["user_id"] + responses: + 200: + description: The user has been unbanned from the room. + examples: + application/json: |- + {} + schema: + type: object + 403: + description: |- + You do not have permission to unban the user from the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: + + - The unbanner's power level is insufficient to unban users from the room. + examples: + application/json: |- + { + "errcode": "M_FORBIDDEN", + "error": "You do not have a high enough power level to unban from this room." + } + tags: + - Room membership diff --git a/api/client-server/cas_login_redirect.yaml b/api/client-server/cas_login_redirect.yaml new file mode 100644 index 00000000000..7a4cec5decb --- /dev/null +++ b/api/client-server/cas_login_redirect.yaml @@ -0,0 +1,53 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server CAS Login API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +paths: + "/login/cas/redirect": + get: + summary: Redirect the user's browser to the CAS interface. + description: |- + A web-based Matrix client should instruct the user's browser to + navigate to this endpoint in order to log in via CAS. + + The server MUST respond with an HTTP redirect to the CAS interface. The + URI MUST include a ``service`` parameter giving the path of the + |/login/cas/ticket|_ endpoint (including the ``redirectUrl`` query + parameter). + + For example, if the endpoint is called with + ``redirectUrl=https://client.example.com/?q=p``, it might redirect to + ``https://cas.example.com/?service=https%3A%2F%2Fserver.example.com%2F_matrix%2Fclient%2F%CLIENT_MAJOR_VERSION%%2Flogin%2Fcas%2Fticket%3FredirectUrl%3Dhttps%253A%252F%252Fclient.example.com%252F%253Fq%253Dp``. + + parameters: + - in: query + type: string + name: redirectUrl + description: |- + URI to which the user will be redirected after the homeserver has + authenticated the user with CAS. + required: true + responses: + 302: + description: A redirect to the CAS interface. + headers: + Location: + type: "string" diff --git a/api/client-server/cas_login_ticket.yaml b/api/client-server/cas_login_ticket.yaml new file mode 100644 index 00000000000..024694899da --- /dev/null +++ b/api/client-server/cas_login_ticket.yaml @@ -0,0 +1,65 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server CAS Login API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +paths: + "/login/cas/ticket": + get: + summary: Receive and validate a CAS login ticket. + description: |- + Once the CAS server has authenticated the user, it will redirect the + browser to this endpoint (assuming |/login/cas/redirect|_ gave it the + correct ``service`` parameter). + + The server MUST call ``/proxyValidate`` on the CAS server, to validate + the ticket supplied by the browser. + + If validation is successful, the server must generate a Matrix login + token. It must then respond with an HTTP redirect to the URI given in + the ``redirectUrl`` parameter, adding a ``loginToken`` query parameter + giving the generated token. + + If validation is unsuccessful, the server should respond with a ``401 + Unauthorized`` error, the body of which will be displayed to the user. + parameters: + - in: query + type: string + name: redirectUrl + description: |- + The ``redirectUrl`` originally provided by the client to + |/login/cas/redirect|_. + required: true + - in: query + type: string + name: ticket + description: |- + CAS authentication ticket. + required: true + responses: + 302: + description: A redirect to the Matrix client. + headers: + Location: + type: "string" + x-example: |- + https://client.example.com/?q=p&loginToken=secrettoken + 401: + description: The server was unable to validate the CAS ticket. diff --git a/api/client-server/v1/content-repo.yaml b/api/client-server/content-repo.yaml similarity index 63% rename from api/client-server/v1/content-repo.yaml rename to api/client-server/content-repo.yaml index 8e6e8d1a4c5..f9c92a0db70 100644 --- a/api/client-server/v1/content-repo.yaml +++ b/api/client-server/content-repo.yaml @@ -1,11 +1,24 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Content Repository API" + title: "Matrix Client-Server Content Repository API" version: "1.0.0" host: localhost:8008 schemes: - https -basePath: /_matrix/media/v1 +basePath: /_matrix/media/%CLIENT_MAJOR_VERSION% produces: - application/json - "*/*" @@ -20,6 +33,11 @@ paths: type: string description: The content type of the file being uploaded x-example: "Content-Type: audio/mpeg" + - in: query + type: string + x-example: "War and Peace.pdf" + name: filename + description: The name of the file being uploaded - in: body name: "" description: The content to be uploaded. @@ -43,6 +61,8 @@ paths: { "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" } + tags: + - Media "/download/{serverName}/{mediaId}": get: summary: "Download content from the content repository." @@ -74,6 +94,48 @@ paths: type: "string" schema: type: file + tags: + - Media + "/download/{serverName}/{mediaId}/{fileName}": + get: + summary: "Download content from the content repository as a given filename." + produces: ["*/*"] + parameters: + - in: path + type: string + name: serverName + x-example: matrix.org + required: true + description: | + The server name from the ``mxc://`` URI (the authoritory component) + - in: path + type: string + name: mediaId + x-example: ascERGshawAWawugaAcauga + required: true + description: | + The media ID from the ``mxc://`` URI (the path component) + - in: path + type: string + name: fileName + x-example: filename.jpg + required: true + description: | + The filename to give in the Content-Disposition + responses: + 200: + description: "The content that was previously uploaded." + headers: + Content-Type: + description: "The content type of the file that was previously uploaded." + type: "string" + Content-Disposition: + description: "The name of file given in the request" + type: "string" + schema: + type: file + tags: + - Media "/thumbnail/{serverName}/{mediaId}": get: summary: "Download a thumbnail of the content from the content repository." @@ -123,5 +185,5 @@ paths: enum: ["image/jpeg", "image/png"] schema: type: file - - + tags: + - Media diff --git a/api/client-server/create_room.yaml b/api/client-server/create_room.yaml new file mode 100644 index 00000000000..a321b0f7af6 --- /dev/null +++ b/api/client-server/create_room.yaml @@ -0,0 +1,213 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Creation API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/createRoom": + post: + summary: Create a new room + description: |- + Create a new room with various configuration options. + + The server MUST apply the normal state resolution rules when creating + the new room, including checking power levels for each event. It MUST + apply the events implied by the request in the following order: + + 1. Events set by ``presets``. + + 2. Events listed in ``initial_state``, in the order that they are + listed. + + 3. Events implied by ``name`` and ``topic``. + + 4. Invite events implied by ``invite`` and ``invite_3pid``. + + security: + - accessToken: [] + parameters: + - in: body + name: body + description: The desired room configuration. + schema: + type: object + example: |- + { + "preset": "public_chat", + "room_alias_name": "thepub", + "name": "The Grand Duke Pub", + "topic": "All about happy hour", + "creation_content": { + "m.federate": false + } + } + properties: + visibility: + type: string + enum: ["public", "private"] + description: |- + A ``public`` visibility indicates that the room will be shown + in the published room list. A ``private`` visibility will hide + the room from the published room list. Rooms default to + ``private`` visibility if this key is not included. NB: This + should not be confused with ``join_rules`` which also uses the + word ``public``. + room_alias_name: + type: string + description: |- + The desired room alias **local part**. If this is included, a + room alias will be created and mapped to the newly created + room. The alias will belong on the *same* homeserver which + created the room. For example, if this was set to "foo" and + sent to the homeserver "example.com" the complete room alias + would be ``#foo:example.com``. + name: + type: string + description: |- + If this is included, an ``m.room.name`` event will be sent + into the room to indicate the name of the room. See Room + Events for more information on ``m.room.name``. + topic: + type: string + description: |- + If this is included, an ``m.room.topic`` event will be sent + into the room to indicate the topic for the room. See Room + Events for more information on ``m.room.topic``. + invite: + type: array + description: |- + A list of user IDs to invite to the room. This will tell the + server to invite everyone in the list to the newly created room. + items: + type: string + invite_3pid: + type: array + description: |- + A list of objects representing third party IDs to invite into + the room. + items: + type: object + title: Invite3pid + properties: + id_server: + type: string + description: The hostname+port of the identity server which should be used for third party identifier lookups. + medium: + type: string + # TODO: Link to identity service spec when it eixsts + description: The kind of address being passed in the address field, for example ``email``. + address: + type: string + description: The invitee's third party identifier. + required: ["id_server", "medium", "address"] + creation_content: + title: CreationContent + type: object + description: |- + Extra keys to be added to the content of the ``m.room.create``. + The server will clobber the following keys: ``creator``. Future + versions of the specification may allow the server to clobber + other keys. + initial_state: + type: array + description: |- + A list of state events to set in the new room. This allows + the user to override the default state events set in the new + room. The expected format of the state events are an object + with type, state_key and content keys set. + + Takes precedence over events set by ``presets``, but gets + overriden by ``name`` and ``topic`` keys. + items: + type: object + title: StateEvent + properties: + type: + type: string + state_key: + type: string + content: + type: string + preset: + type: string + enum: ["private_chat", "public_chat", "trusted_private_chat"] + description: |- + Convenience parameter for setting various default state events + based on a preset. Must be either: + + ``private_chat`` => + ``join_rules`` is set to ``invite``. + ``history_visibility`` is set to ``shared``. + + ``trusted_private_chat`` => + ``join_rules`` is set to ``invite``. + ``history_visibility`` is set to ``shared``. + All invitees are given the same power level as the room creator. + + ``public_chat``: => + ``join_rules`` is set to ``public``. + ``history_visibility`` is set to ``shared``. + is_direct: + type: boolean + description: |- + This flag makes the server set the ``is_direct`` flag on the + ``m.room.member`` events sent to the users in ``invite`` and + ``invite_3pid``. See `Direct Messaging`_ for more information. + responses: + 200: + description: Information about the newly created room. + schema: + type: object + description: Information about the newly created room. + properties: + room_id: + type: string + description: |- + The created room's ID. + examples: + application/json: |- + { + "room_id": "!sefiuhWgwghwWgh:example.com" + } + 400: + description: |- + + The request is invalid. A meaningful ``errcode`` and description + error text will be returned. Example reasons for rejection include: + + - The request body is malformed (``errcode`` set to ``M_BAD_JSON`` + or ``M_NOT_JSON``). + + - The room alias specified is already taken (``errcode`` set to + ``M_ROOM_IN_USE``). + + - The initial state implied by the parameters to the request is + invalid: for example, the user's ``power_level`` is set below + that necessary to set the room name (``errcode`` set to + ``M_INVALID_ROOM_STATE``). + + tags: + - Room creation diff --git a/api/client-server/definitions/auth_data.yaml b/api/client-server/definitions/auth_data.yaml new file mode 100644 index 00000000000..c37c6a481da --- /dev/null +++ b/api/client-server/definitions/auth_data.yaml @@ -0,0 +1,33 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +title: Authentication Data +description: |- + Used by clients to submit authentication information to the interactive-authentication API +type: object +properties: + type: + description: The login type that the client is attempting to complete. + type: string + session: + description: The value of the session key given by the homeserver. + type: string +additionalProperties: + description: Keys dependent on the login type + type: object +required: + - type +example: + type: "example.type.foo" + session: "xxxxx" + example_credential: "verypoorsharedsecret" diff --git a/api/client-server/definitions/auth_response.yaml b/api/client-server/definitions/auth_response.yaml new file mode 100644 index 00000000000..5caf7a885ca --- /dev/null +++ b/api/client-server/definitions/auth_response.yaml @@ -0,0 +1,62 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +title: Authentication response +description: |- + Used by servers to indicate that additional authentication information is required, +type: object +properties: + flows: + description: A list of the login flows supported by the server for this API. + title: Flow information + type: array + items: + type: object + properties: + stages: + description: |- + The login type of each of the stages required to complete this + authentication flow + type: array + items: + type: string + example: "example.type.foo" + required: + - stages + params: + type: object + description: |- + Contains any information that the client will need to know in order to + use a given type of authentication. For each login type presented, + that type may be present as a key in this dictionary. For example, the + public part of an OAuth client ID could be given here. + additionalProperties: + type: object + example: + "example.type.baz": { "example_key": "foobar" } + session: + type: string + description: |- + This is a session identifier that the client must pass back to the home + server, if one is provided, in subsequent attempts to authenticate in the + same API call. + example: "xxxxxxyz" + completed: + type: array + description: |- + A list of the stages the client has completed successfully + items: + type: string + example: "example.type.foo" +required: + - flows diff --git a/api/client-server/definitions/client_device.yaml b/api/client-server/definitions/client_device.yaml new file mode 100644 index 00000000000..6dc3abf379a --- /dev/null +++ b/api/client-server/definitions/client_device.yaml @@ -0,0 +1,44 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +type: object +description: A client device +title: Device +properties: + device_id: + type: string + description: Identifier of this device. + example: QBUAZIFURK + display_name: + type: string + description: |- + Display name set by the user for this device. Absent if no name has been + set. + example: android + last_seen_ip: + type: string + description: |- + The IP address where this device was last seen. (May be a few minutes out + of date, for efficiency reasons). + example: 1.2.3.4 + last_seen_ts: + type: integer + format: int64 + description: |- + 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). + example: 1474491775024 +required: + - device_id diff --git a/api/client-server/definitions/device_keys.yaml b/api/client-server/definitions/device_keys.yaml new file mode 100644 index 00000000000..888c93a44d2 --- /dev/null +++ b/api/client-server/definitions/device_keys.yaml @@ -0,0 +1,68 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +type: object +title: DeviceKeys +description: Device identity keys +properties: + user_id: + type: string + description: |- + The ID of the user the device belongs to. Must match the user ID used + when logging in. + example: "@alice:example.com" + device_id: + type: string + description: |- + The ID of the device these keys belong to. Must match the device ID used + when logging in. + example: "JLAFKJWSCS" + algorithms: + type: array + items: + type: string + description: |- + The encryption algorithms supported by this device. + example: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"] + keys: + type: object + description: |- + Public identity keys. The names of the properties should be in the + format ``:``. The keys themselves should be + encoded as specified by the key algorithm. + additionalProperties: + type: string + example: + "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI" + "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" + signatures: + type: object + description: |- + Signatures for the device key object. A map from user ID, to a map from + ``:`` to the signature. + + The signature is calculated using the process described at `Signing + JSON`_. + additionalProperties: + type: object + additionalProperties: + type: string + example: + "@alice:example.com": + "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" +required: + - user_id + - device_id + - algorithms + - keys + - signatures diff --git a/api/client-server/definitions/error.yaml b/api/client-server/definitions/error.yaml new file mode 100644 index 00000000000..fa5cada7af7 --- /dev/null +++ b/api/client-server/definitions/error.yaml @@ -0,0 +1,23 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +type: object +description: A Matrix-level Error +properties: + errcode: + type: string + description: An error code. + error: + type: string + description: A human-readable error message. +required: ["errcode"] \ No newline at end of file diff --git a/api/client-server/definitions/event-schemas b/api/client-server/definitions/event-schemas new file mode 120000 index 00000000000..376cf90c03c --- /dev/null +++ b/api/client-server/definitions/event-schemas @@ -0,0 +1 @@ +../../../event-schemas \ No newline at end of file diff --git a/api/client-server/definitions/event.yaml b/api/client-server/definitions/event.yaml new file mode 100644 index 00000000000..aa893fd4ff1 --- /dev/null +++ b/api/client-server/definitions/event.yaml @@ -0,0 +1,62 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +properties: + content: + description: The content of this event. The fields in this object will vary depending + on the type of event. + title: EventContent + type: object + origin_server_ts: + description: Timestamp in milliseconds on originating homeserver when this event + was sent. + format: int64 + type: integer + sender: + description: The MXID of the user who sent this event. + type: string + state_key: + description: Optional. This key will only be present for state events. A unique + key which defines the overwriting semantics for this piece of room state. + type: string + type: + description: The type of event. + type: string + unsigned: + description: Information about this event which was not sent by the originating + homeserver + properties: + age: + description: Time in milliseconds since the event was sent. + format: int64 + type: integer + prev_content: + description: Optional. The previous ``content`` for this state. This will + be present only for state events appearing in the ``timeline``. If this + is not a state event, or there is no previous content, this key will be + missing. + title: EventContent + type: object + transaction_id: + description: Optional. The transaction ID set when this message was sent. + This key will only be present for message events sent by the device calling + this API. + type: string + redacted_because: + description: Optional. The event that redacted this event, if any. + title: Event + type: object + title: Unsigned + type: object +title: Event +type: object diff --git a/api/client-server/definitions/event_batch.yaml b/api/client-server/definitions/event_batch.yaml new file mode 100644 index 00000000000..21377a41926 --- /dev/null +++ b/api/client-server/definitions/event_batch.yaml @@ -0,0 +1,22 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +properties: + events: + description: List of events + items: + allOf: + - $ref: event.yaml + type: object + type: array +type: object diff --git a/api/client-server/definitions/event_filter.yaml b/api/client-server/definitions/event_filter.yaml new file mode 100644 index 00000000000..1cae3ea90e6 --- /dev/null +++ b/api/client-server/definitions/event_filter.yaml @@ -0,0 +1,47 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +title: Filter +properties: + limit: + description: The maximum number of events to return. + type: integer + not_senders: + description: A list of sender IDs to exclude. If this list is absent then no senders + are excluded. A matching sender will be excluded even if it is listed in the + ``'senders'`` filter. + items: + type: string + type: array + not_types: + description: A list of event types to exclude. If this list is absent then no + event types are excluded. A matching type will be excluded even if it is listed + in the ``'types'`` filter. A '*' can be used as a wildcard to match any sequence + of characters. + items: + type: string + type: array + senders: + description: A list of senders IDs to include. If this list is absent then all + senders are included. + items: + type: string + type: array + types: + description: A list of event types to include. If this list is absent then all + event types are included. A ``'*'`` can be used as a wildcard to match any sequence + of characters. + items: + type: string + type: array +type: object diff --git a/api/client-server/definitions/push_condition.yaml b/api/client-server/definitions/push_condition.yaml new file mode 100644 index 00000000000..796a51f4748 --- /dev/null +++ b/api/client-server/definitions/push_condition.yaml @@ -0,0 +1,45 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: PushCondition +type: object +properties: + kind: + enum: + - event_match + - contains_display_name + - room_member_count + type: string + key: + type: string + description: |- + Required for ``event_match`` conditions. The dot-separated field of the + event to match. + x-example: content.body + pattern: + type: string + description: |- + Required for ``event_match`` conditions. The glob-style pattern to + match against. Patterns with no special glob characters should be + treated as having asterisks prepended and appended when testing the + condition. + is: + type: string + description: |- + Required for ``room_member_count`` conditions. A decimal integer + optionally prefixed by one of, ==, <, >, >= or <=. A prefix of < matches + rooms where the member count is strictly less than the given number and + so forth. If no prefix is present, this parameter defaults to ==. +required: + - kind diff --git a/api/client-server/definitions/push_rule.yaml b/api/client-server/definitions/push_rule.yaml new file mode 100644 index 00000000000..14a9b7d41b3 --- /dev/null +++ b/api/client-server/definitions/push_rule.yaml @@ -0,0 +1,56 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +title: PushRule +type: object +properties: + actions: + items: + type: + - object + - string + type: array + description: |- + The actions to perform when this rule is matched. + default: + type: boolean + description: |- + Whether this is a default rule, or has been set explicitly. + enabled: + type: boolean + description: |- + Whether the push rule is enabled or not. + rule_id: + type: string + description: |- + The ID of this rule. + conditions: + type: array + items: + allOf: + - $ref: push_condition.yaml + description: |- + The conditions that must hold true for an event in order for a rule to be + applied to an event. A rule with no conditions always matches. Only + applicable to ``underride`` and ``override`` rules. + pattern: + type: string + description: |- + The glob-style pattern to match against. Only applicable to ``content`` + rules. +required: + - actions + - default + - enabled + - rule_id diff --git a/api/client-server/definitions/push_ruleset.yaml b/api/client-server/definitions/push_ruleset.yaml new file mode 100644 index 00000000000..2d8cd67c1e8 --- /dev/null +++ b/api/client-server/definitions/push_ruleset.yaml @@ -0,0 +1,50 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +properties: + content: + items: + allOf: + - $ref: push_rule.yaml + title: PushRule + type: object + type: array + override: + items: + allOf: + - $ref: push_rule.yaml + title: PushRule + type: object + type: array + room: + items: + allOf: + - $ref: push_rule.yaml + title: PushRule + type: object + type: array + sender: + items: + allOf: + - $ref: push_rule.yaml + title: PushRule + type: object + type: array + underride: + items: + allOf: + - $ref: push_rule.yaml + title: PushRule + type: object + type: array +type: object diff --git a/api/client-server/definitions/room_event_filter.yaml b/api/client-server/definitions/room_event_filter.yaml new file mode 100644 index 00000000000..7d9184b5887 --- /dev/null +++ b/api/client-server/definitions/room_event_filter.yaml @@ -0,0 +1,35 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +allOf: +- $ref: event_filter.yaml +title: RoomEventFilter +properties: + not_rooms: + description: A list of room IDs to exclude. If this list is absent then no rooms + are excluded. A matching room will be excluded even if it is listed in the ``'rooms'`` + filter. + items: + type: string + type: array + rooms: + description: A list of room IDs to include. If this list is absent then all rooms + are included. + items: + type: string + type: array + contains_url: + type: boolean + description: If ``true``, includes only events with a url key in their content. If + ``false``, excludes those events. +type: object diff --git a/api/client-server/definitions/security.yaml b/api/client-server/definitions/security.yaml new file mode 100644 index 00000000000..bb78f0e334d --- /dev/null +++ b/api/client-server/definitions/security.yaml @@ -0,0 +1,18 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +accessToken: + type: apiKey + description: The access_token returned by a call to ``/login`` or ``/register`` + name: access_token + in: query diff --git a/api/client-server/definitions/sync_filter.yaml b/api/client-server/definitions/sync_filter.yaml new file mode 100644 index 00000000000..69b245a3177 --- /dev/null +++ b/api/client-server/definitions/sync_filter.yaml @@ -0,0 +1,80 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +properties: + event_fields: + description: List of event fields to include. If this list is absent then all + fields are included. The entries may include '.' charaters to indicate sub-fields. + So ['content.body'] will include the 'body' field of the 'content' object. A + literal '.' character in a field name may be escaped using a '\\'. A server may + include more fields than were requested. + items: + type: string + type: array + event_format: + description: The format to use for events. 'client' will return the events in + a format suitable for clients. 'federation' will return the raw event as receieved + over federation. The default is 'client'. + enum: + - client + - federation + type: string + presence: + allOf: + - $ref: event_filter.yaml + description: The presence updates to include. + account_data: + allOf: + - $ref: event_filter.yaml + description: The user account data that isn't associated with rooms to include. + room: + title: RoomFilter + description: Filters to be applied to room data. + properties: + not_rooms: + description: A list of room IDs to exclude. If this list is absent then no rooms + are excluded. A matching room will be excluded even if it is listed in the ``'rooms'`` + filter. This filter is applied before the filters in ``ephemeral``, + ``state``, ``timeline`` or ``account_data`` + items: + type: string + type: array + rooms: + description: A list of room IDs to include. If this list is absent then all rooms + are included. This filter is applied before the filters in ``ephemeral``, + ``state``, ``timeline`` or ``account_data`` + items: + type: string + type: array + ephemeral: + allOf: + - $ref: room_event_filter.yaml + description: The events that aren't recorded in the room history, e.g. typing + and receipts, to include for rooms. + include_leave: + description: Include rooms that the user has left in the sync, default false + type: boolean + state: + allOf: + - $ref: room_event_filter.yaml + description: The state events to include for rooms. + timeline: + allOf: + - $ref: room_event_filter.yaml + description: The message and state update events to include for rooms. + account_data: + allOf: + - $ref: room_event_filter.yaml + description: The per user account data to include for rooms. + type: object +type: object diff --git a/api/client-server/definitions/timeline_batch.yaml b/api/client-server/definitions/timeline_batch.yaml new file mode 100644 index 00000000000..44baa39f9f5 --- /dev/null +++ b/api/client-server/definitions/timeline_batch.yaml @@ -0,0 +1,25 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +allOf: +- $ref: event_batch.yaml +properties: + limited: + description: True if the number of events returned was limited by the ``limit`` + on the filter + type: boolean + prev_batch: + description: A token that can be supplied to to the ``from`` parameter of the + rooms/{roomId}/messages endpoint + type: string +type: object diff --git a/api/client-server/device_management.yaml b/api/client-server/device_management.yaml new file mode 100644 index 00000000000..ddfbad85037 --- /dev/null +++ b/api/client-server/device_management.yaml @@ -0,0 +1,177 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +swagger: '2.0' +info: + title: "Matrix Client-Server device management API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/devices": + get: + summary: List registered devices for the current user + description: |- + Gets information about all devices for the current user. + security: + - accessToken: [] + responses: + 200: + description: Device information + schema: + type: object + properties: + devices: + type: array + description: A list of all registered devices for this user. + items: + type: object + allOf: + - $ref: "definitions/client_device.yaml" + examples: + application/json: |- + { + "devices": [ + { + "device_id": "QBUAZIFURK", + "display_name": "android", + "last_seen_ip": "1.2.3.4", + "last_seen_ts": 1474491775024 + } + ] + } + tags: + - Device management + "/devices/{deviceId}": + get: + summary: Get a single device + description: |- + Gets information on a single device, by device id. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: deviceId + description: The device to retrieve. + required: true + x-example: "QBUAZIFURK" + responses: + 200: + description: Device information + schema: + type: object + allOf: + - $ref: "definitions/client_device.yaml" + examples: + application/json: |- + { + "device_id": "QBUAZIFURK", + "display_name": "android", + "last_seen_ip": "1.2.3.4", + "last_seen_ts": 1474491775024 + } + 404: + description: The current user has no device with the given ID. + tags: + - Device management + put: + summary: Update a device + description: |- + Updates the metadata on the given device. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: deviceId + description: The device to update. + required: true + x-example: "QBUAZIFURK" + - in: body + name: body + required: true + description: New information for the device. + schema: + type: object + properties: + display_name: + type: string + description: |- + The new display name for this device. If not given, the + display name is unchanged. + example: My other phone + responses: + 200: + description: The device was successfully updated. + examples: + application/json: |- + {} + schema: + type: object # empty json object + 404: + description: The current user has no device with the given ID. + tags: + - Device management + delete: + summary: Delete a device + description: |- + This API endpoint uses the `User-Interactive Authentication API`_. + + Deletes the given device, and invalidates any access token assoicated with it. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: deviceId + description: The device to delete. + required: true + x-example: "QBUAZIFURK" + - in: body + name: body + schema: + type: object + properties: + auth: + description: |- + Additional authentication information for the + user-interactive authentication API. + "$ref": "definitions/auth_data.yaml" + responses: + 200: + description: |- + The device was successfully removed, or had been removed + previously. + schema: + type: object + examples: + application/json: |- + {} + 401: + description: |- + The homeserver requires additional authentication information. + schema: + "$ref": "definitions/auth_response.yaml" + tags: + - Device management diff --git a/api/client-server/directory.yaml b/api/client-server/directory.yaml new file mode 100644 index 00000000000..0a69a8ec446 --- /dev/null +++ b/api/client-server/directory.yaml @@ -0,0 +1,149 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Directory API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION%/directory +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/room/{roomAlias}": + put: + summary: Create a new mapping from room alias to room ID. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomAlias + description: The room alias to set. + required: true + x-example: "#monkeys:matrix.org" + - in: body + name: roomInfo + description: Information about this room alias. + required: true + schema: + type: object + properties: + room_id: + type: string + description: The room ID to set. + example: |- + { + "room_id": "!abnjk1jdasj98:capuchins.com" + } + responses: + 200: + description: The mapping was created. + examples: + application/json: |- + {} + schema: + type: object + 409: + description: A room alias with that name already exists. + examples: + application/json: |- + { + "errcode": "M_UNKNOWN", + "error": "Room alias #monkeys:matrix.org already exists." + } + tags: + - Room directory + get: + summary: Get the room ID corresponding to this room alias. + description: |- + Requests that the server resolve a room alias to a room ID. + + The server will use the federation API to resolve the alias if the + domain part of the alias does not correspond to the server's own + domain. + + parameters: + - in: path + type: string + name: roomAlias + description: The room alias. + required: true + x-example: "#monkeys:matrix.org" + responses: + 200: + description: The room ID and other information for this alias. + schema: + type: object + properties: + room_id: + type: string + description: The room ID for this room alias. + servers: + type: array + description: A list of servers that are aware of this room alias. + items: + type: string + description: A server which is aware of this room alias. + examples: + application/json: |- + { + "room_id": "!abnjk1jdasj98:capuchins.com", + "servers": [ + "capuchins.com", + "matrix.org", + "another.com" + ] + } + 404: + description: There is no mapped room ID for this room alias. + examples: + application/json: |- + { + "errcode": "M_NOT_FOUND", + "error": "Room alias #monkeys:matrix.org not found." + } + tags: + - Room directory + delete: + summary: Remove a mapping of room alias to room ID. + description: |- + Remove a mapping of room alias to room ID. + + Servers may choose to implement additional access control checks here, for instance that room aliases can only be deleted by their creator or a server administrator. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomAlias + description: The room alias to remove. + required: true + x-example: "#monkeys:matrix.org" + responses: + 200: + description: The mapping was deleted. + examples: + application/json: |- + {} + schema: + type: object + tags: + - Room directory diff --git a/api/client-server/event_context.yaml b/api/client-server/event_context.yaml new file mode 100644 index 00000000000..8c5567b2f93 --- /dev/null +++ b/api/client-server/event_context.yaml @@ -0,0 +1,165 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Event Context API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/context/{eventId}": + get: + summary: Get events and state around the specified event. + description: |- + This API returns a number of events that happened just before and + after the specified event. This allows clients to get the context + surrounding an event. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to get events from. + required: true + x-example: "!636q39766251:example.com" + - in: path + type: string + name: eventId + description: The event to get context around. + required: true + x-example: "$f3h4d129462ha:example.com" + - in: query + type: integer + name: limit + description: |- + The maximum number of events to return. Default: 10. + x-example: 3 + responses: + 200: + description: The events and state surrounding the requested event. + schema: + type: object + description: The events and state surrounding the requested event. + properties: + start: + type: string + description: |- + A token that can be used to paginate backwards with. + end: + type: string + description: |- + A token that can be used to paginate forwards with. + events_before: + type: array + description: |- + A list of room events that happened just before the + requested event, in reverse-chronological order. + items: + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + event: + description: |- + Details of the requested event. + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + events_after: + type: array + description: |- + A list of room events that happened just after the + requested event, in chronological order. + items: + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + state: + type: array + description: |- + The state of the room at the last event returned. + items: + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/state_event.yaml" + examples: + application/json: |- + { + "end": "t29-57_2_0_2", + "events_after": [ + { + "age": 91911336, + "content": { + "body": "7", + "msgtype": "m.text" + }, + "event_id": "$14460306086CiUaL:localhost:8480", + "origin_server_ts": 1446030608551, + "room_id": "!sCDvXTtzjpiPxaqkkt:localhost:8480", + "type": "m.room.message", + "sender": "@test:localhost:8480" + } + ], + "events_before": [ + { + "age": 91911903, + "content": { + "body": "5", + "msgtype": "m.text" + }, + "event_id": "$14460306074UYTlh:localhost:8480", + "origin_server_ts": 1446030607984, + "room_id": "!sCDvXTtzjpiPxaqkkt:localhost:8480", + "type": "m.room.message", + "sender": "@test:localhost:8480" + } + ], + "start": "t27-54_2_0_2", + "state": [ + { + "age": 3123715284, + "content": { + "creator": "@test:localhost:8480" + }, + "event_id": "$14429988040dgQAE:localhost:8480", + "origin_server_ts": 1442998804603, + "room_id": "!sCDvXTtzjpiPxaqkkt:localhost:8480", + "state_key": "", + "type": "m.room.create", + "sender": "@test:localhost:8480" + }, + { + "age": 2067105053, + "content": { + "avatar_url": "mxc://localhost:8480/tVWZTAIIfqtXMZZtmGCkVjTD#auto", + "displayname": "Bob2", + "membership": "join" + }, + "event_id": "$14440554144URDbf:localhost:8480", + "origin_server_ts": 1444055414834, + "replaces_state": "$14440552472PgiGk:localhost:8480", + "room_id": "!sCDvXTtzjpiPxaqkkt:localhost:8480", + "state_key": "@test:localhost:8480", + "type": "m.room.member", + "sender": "@test:localhost:8480" + } + ] + } + tags: + - Room participation diff --git a/api/client-server/v2_alpha/filter.yaml b/api/client-server/filter.yaml similarity index 77% rename from api/client-server/v2_alpha/filter.yaml rename to api/client-server/filter.yaml index 37a0a3aaac4..cfd5592087f 100644 --- a/api/client-server/v2_alpha/filter.yaml +++ b/api/client-server/filter.yaml @@ -1,29 +1,38 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v2 filter API" + title: "Matrix Client-Server filter API" version: "1.0.0" host: localhost:8008 schemes: - https -basePath: /_matrix/client/v2_alpha +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/user/{userId}/filter": post: summary: Upload a new filter. description: |- Uploads a new filter definition to the homeserver. - Returns a filter ID that may be used in /sync requests to - retrict which events are returned to the client. + Returns a filter ID that may be used in future requests to + restrict which events are returned to the client. security: - accessToken: [] parameters: @@ -42,7 +51,7 @@ paths: schema: type: object allOf: - - $ref: "definitions/sync_filter.json" + - $ref: "definitions/sync_filter.yaml" example: |- { "room": { @@ -56,7 +65,7 @@ paths: "not_rooms": ["!726s6s6q:example.com"], "not_senders": ["@spam:example.com"] }, - "emphemeral": { + "ephemeral": { "types": ["m.receipt", "m.typing"], "not_rooms": ["!726s6s6q:example.com"], "not_senders": ["@spam:example.com"] @@ -84,9 +93,13 @@ paths: type: string description: |- The ID of the filter that was created. + tags: + - Room participation "/user/{userId}/filter/{filterId}": get: summary: Download a filter + security: + - accessToken: [] parameters: - in: path name: userId @@ -120,7 +133,7 @@ paths: "not_rooms": ["!726s6s6q:example.com"], "not_senders": ["@spam:example.com"] }, - "emphemeral": { + "ephemeral": { "types": ["m.receipt", "m.typing"], "not_rooms": ["!726s6s6q:example.com"], "not_senders": ["@spam:example.com"] @@ -136,4 +149,6 @@ paths: schema: type: object allOf: - - $ref: "definitions/sync_filter.json" + - $ref: "definitions/sync_filter.yaml" + tags: + - Room participation diff --git a/api/client-server/inviting.yaml b/api/client-server/inviting.yaml new file mode 100644 index 00000000000..e73d44fcb39 --- /dev/null +++ b/api/client-server/inviting.yaml @@ -0,0 +1,101 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Joining API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + # With an extra " " to disambiguate from the 3pid invite endpoint + # The extra space makes it sort first for what I'm sure is a good reason. + "/rooms/{roomId}/invite ": + post: + summary: Invite a user to participate in a particular room. + description: |- + .. _invite-by-user-id-endpoint: + + *Note that there are two forms of this API, which are documented separately. + This version of the API requires that the inviter knows the Matrix + identifier of the invitee. The other is documented in the* + `third party invites section`_. + + This API invites a user to participate in a particular room. + They do not start participating in the room until they actually join the + room. + + Only users currently in a particular room can invite other users to + join that room. + + If the user was invited to the room, the homeserver will append a + ``m.room.member`` event to the room. + + .. _third party invites section: `invite-by-third-party-id-endpoint`_ + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier (not alias) to which to invite the user. + required: true + x-example: "!d41d8cd:matrix.org" + - in: body + name: body + required: true + schema: + type: object + example: |- + { + "user_id": "@cheeky_monkey:matrix.org" + } + properties: + user_id: + type: string + description: The fully qualified user ID of the invitee. + required: ["user_id"] + responses: + 200: + description: The user has been invited to join the room. + examples: + application/json: |- + {} + schema: + type: object + 403: + description: |- + You do not have permission to invite the user to the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: + + - The invitee has been banned from the room. + - The invitee is already a member of the room. + - The inviter is not currently in the room. + - The inviter's power level is insufficient to invite users to the room. + examples: + application/json: |- + {"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"} + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Room membership diff --git a/api/client-server/joining.yaml b/api/client-server/joining.yaml new file mode 100644 index 00000000000..92454239e61 --- /dev/null +++ b/api/client-server/joining.yaml @@ -0,0 +1,214 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Inviting API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/join": + post: + summary: Start the requesting user participating in a particular room. + description: |- + *Note that this API requires a room ID, not alias.* ``/join/{roomIdOrAlias}`` *exists if you have a room alias.* + + This API starts a user participating in a particular room, if that user + is allowed to participate in that room. After this call, the client is + allowed to see all current state events in the room, and all subsequent + events associated with the room until the user leaves the room. + + After a user has joined a room, the room will appear as an entry in the + response of the |/initialSync|_ and |/sync|_ APIs. + + If a ``third_party_signed`` was supplied, the homeserver must verify + that it matches a pending ``m.room.third_party_invite`` event in the + room, and perform key validity checking if required by the event. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier (not alias) to join. + required: true + x-example: "!d41d8cd:matrix.org" + - in: body + name: third_party_signed + schema: + type: object + example: |- + { + "third_party_signed": { + "sender": "@cat:the.hat", + "mxid": "@green:eggs.ham", + "token": "random8nonce", + "signatures": { + "horton.hears": { + "ed25519:0": "some9signature" + } + } + } + } + properties: + third_party_signed: + type: object + title: ThirdPartySigned + description: A signature of an ``m.third_party_invite`` token to prove that this user owns a third party identity which has been invited to the room. + properties: + sender: + type: string + description: The Matrix ID of the user who issued the invite. + mxid: + type: string + description: The Matrix ID of the invitee. + token: + type: string + description: The state key of the m.third_party_invite event. + signatures: + type: object + description: A signatures object containing a signature of the entire signed object. + title: Signatures + required: ["sender", "mxid", "token", "signatures"] + responses: + 200: + description: |- + The room has been joined. + + The joined room ID must be returned in the ``room_id`` field. + examples: + application/json: |- + {"room_id": "!d41d8cd:matrix.org"} + schema: + type: object + 403: + description: |- + You do not have permission to join the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejection are: + + - The room is invite-only and the user was not invited. + - The user has been banned from the room. + examples: + application/json: |- + {"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."} + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Room membership + "/join/{roomIdOrAlias}": + post: + summary: Start the requesting user participating in a particular room. + description: |- + *Note that this API takes either a room ID or alias, unlike* ``/room/{roomId}/join``. + + This API starts a user participating in a particular room, if that user + is allowed to participate in that room. After this call, the client is + allowed to see all current state events in the room, and all subsequent + events associated with the room until the user leaves the room. + + After a user has joined a room, the room will appear as an entry in the + response of the |/initialSync|_ and |/sync|_ APIs. + + If a ``third_party_signed`` was supplied, the homeserver must verify + that it matches a pending ``m.room.third_party_invite`` event in the + room, and perform key validity checking if required by the event. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomIdOrAlias + description: The room identifier or alias to join. + required: true + x-example: "#monkeys:matrix.org" + - in: body + name: third_party_signed + schema: + type: object + example: |- + { + "third_party_signed": { + "signed": { + "sender": "@cat:the.hat", + "mxid": "@green:eggs.ham", + "token": "random8nonce", + "signatures": { + "horton.hears": { + "ed25519:0": "some9signature" + } + } + } + } + } + properties: + third_party_signed: + type: object + title: ThirdPartySigned + description: A signature of an ``m.third_party_invite`` token to prove that this user owns a third party identity which has been invited to the room. + properties: + signed: + type: object + title: Signed + properties: + sender: + type: string + description: The Matrix ID of the user who issued the invite. + mxid: + type: string + description: The Matrix ID of the invitee. + token: + type: string + description: The state key of the m.third_party_invite event. + signatures: + type: object + description: A signatures object containing a signature of the entire signed object. + title: Signatures + required: ["sender", "mxid", "token", "signatures"] + required: ["signed"] + responses: + 200: + description: |- + The room has been joined. + + The joined room ID must be returned in the ``room_id`` field. + examples: + application/json: |- + {"room_id": "!d41d8cd:matrix.org"} + schema: + type: object + 403: + description: |- + You do not have permission to join the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejection are: + + - The room is invite-only and the user was not invited. + - The user has been banned from the room. + examples: + application/json: |- + {"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."} + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Room membership diff --git a/api/client-server/keys.yaml b/api/client-server/keys.yaml new file mode 100644 index 00000000000..e77d53254cf --- /dev/null +++ b/api/client-server/keys.yaml @@ -0,0 +1,336 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Client Config API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/keys/upload": + post: + summary: Upload end-to-end encryption keys. + description: |- + Publishes end-to-end encryption keys for the device. + security: + - accessToken: [] + parameters: + - in: body + name: keys + description: |- + The keys to be published + schema: + type: object + properties: + device_keys: + description: |- + Identity keys for the device. May be absent if no new + identity keys are required. + allOf: + - $ref: definitions/device_keys.yaml + one_time_keys: + type: object + description: |- + One-time public keys for "pre-key" messages. The names of + the properties should be in the format + ``:``. The format of the key is determined + by the key algorithm. + + May be absent if no new one-time keys are required. + additionalProperties: + type: + - string + - object + example: + "curve25519:AAAAAQ": "/qyvZvwjiTxGdGU0RCguDCLeR+nmsb3FfNG3/Ve4vU8" + signed_curve25519:AAAAHg: + key: "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs" + signatures: + "@alice:example.com": + ed25519:JLAFKJWSCS: "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw" + signed_curve25519:AAAAHQ: + key: "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw" + signatures: + "@alice:example.com": + ed25519:JLAFKJWSCS: "IQeCEPb9HFk217cU9kw9EOiusC6kMIkoIRnbnfOh5Oc63S1ghgyjShBGpu34blQomoalCyXWyhaaT3MrLZYQAA" + responses: + 200: + description: + The provided keys were sucessfully uploaded. + schema: + type: object + properties: + one_time_key_counts: + type: object + additionalProperties: + type: integer + description: |- + For each key algorithm, the number of unclaimed one-time keys + of that type currently held on the server for this device. + example: + curve25519: 10 + signed_curve25519: 20 + required: + - one_time_key_counts + + tags: + - End-to-end encryption + "/keys/query": + post: + summary: Download device identity keys. + description: |- + Returns the current devices and identity keys for the given users. + security: + - accessToken: [] + parameters: + - in: body + name: query + description: |- + Query defining the keys to be downloaded + schema: + type: object + properties: + timeout: + type: integer + description: |- + The time (in milliseconds) to wait when downloading keys from + remote servers. 10 seconds is the recommended default. + example: 10000 + device_keys: + type: object + description: |- + The keys to be downloaded. A map from user ID, to a list of + device IDs, or to an empty list to indicate all devices for the + corresponding user. + additionalProperties: + type: array + items: + type: string + description: "device ID" + example: + "@alice:example.com": [] + required: + - device_keys + + responses: + 200: + description: + The device information + schema: + type: object + properties: + failures: + type: object + description: |- + If any remote homeservers could not be reached, they are + recorded here. The names of the properties are the names of + the unreachable servers. + + If the homeserver could be reached, but the user or device + was unknown, no failure is recorded. Instead, the corresponding + user or device is missing from the ``device_keys`` result. + additionalProperties: + type: object + example: {} + device_keys: + type: object + description: |- + Information on the queried devices. A map from user ID, to a + map from device ID to device information. For each device, + the information returned will be the same as uploaded via + ``/keys/upload``, with the addition of an ``unsigned`` + property. + additionalProperties: + type: object + additionalProperties: + allOf: + - $ref: definitions/device_keys.yaml + properties: + unsigned: + title: UnsignedDeviceInfo + type: object + description: |- + Additional data added to the device key information + by intermediate servers, and not covered by the + signatures. + properties: + device_display_name: + type: string + description: + The display name which the user set on the device. + example: + "@alice:example.com": + JLAFKJWSCS: { + "user_id": "@alice:example.com", + "device_id": "JLAFKJWSCS", + "algorithms": [ + "m.olm.curve25519-aes-sha256", + "m.megolm.v1.aes-sha" + ], + "keys": { + "curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI", + "ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI" + }, + "signatures": { + "@alice:example.com": { + "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" + } + }, + "unsigned": { + "device_display_name": "Alice's mobile phone" + } + } + + tags: + - End-to-end encryption + "/keys/claim": + post: + summary: Claim one-time encryption keys. + description: |- + Claims one-time keys for use in pre-key messages. + security: + - accessToken: [] + parameters: + - in: body + name: query + description: |- + Query defining the keys to be claimed + schema: + type: object + properties: + timeout: + type: integer + description: |- + The time (in milliseconds) to wait when downloading keys from + remote servers. 10 seconds is the recommended default. + example: 10000 + one_time_keys: + type: object + description: |- + The keys to be claimed. A map from user ID, to a map from + device ID to algorithm name. + additionalProperties: + type: object + additionalProperties: + type: string + description: algorithm + example: "signed_curve25519" + example: + "@alice:example.com": { "JLAFKJWSCS": "curve25519" } + required: + - one_time_keys + responses: + 200: + description: + The claimed keys + schema: + type: object + properties: + failures: + type: object + description: |- + If any remote homeservers could not be reached, they are + recorded here. The names of the properties are the names of + the unreachable servers. + + If the homeserver could be reached, but the user or device + was unknown, no failure is recorded. Instead, the corresponding + user or device is missing from the ``one_time_keys`` result. + additionalProperties: + type: object + example: {} + one_time_keys: + type: object + description: |- + One-time keys for the queried devices. A map from user ID, to a + map from ``:`` to the key object. + additionalProperties: + type: object + additionalProperties: + type: + - string + - object + example: + "@alice:example.com": + JLAFKJWSCS: + signed_curve25519:AAAAHg: + key: "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs" + signatures: + "@alice:example.com": + ed25519:JLAFKJWSCS: "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw" + tags: + - End-to-end encryption + "/keys/changes": + get: + summary: Query users with recent device key updates. + description: |- + Gets a list of users who have updated their device identity keys since a + previous sync token. + + The server should include in the results any users who: + + * currently share a room with the calling user (ie, both users have + membership state ``join``); *and* + * added new device identity keys or removed an existing device with + identity keys, between ``from`` and ``to``. + security: + - accessToken: [] + parameters: + - in: query + name: from + type: string + description: |- + The desired start point of the list. Should be the ``next_batch`` field + from a response to an earlier call to |/sync|. Users who have not + uploaded new device identity keys since this point, nor deleted + existing devices with identity keys since then, will be excluded + from the results. + required: true + x-example: "s72594_4483_1934" + - in: query + name: to + type: string + description: |- + The desired end point of the list. Should be the ``next_batch`` + field from a recent call to |/sync| - typically the most recent + such call. This may be used by the server as a hint to check its + caches are up to date. + required: true + x-example: "s75689_5632_2435" + responses: + 200: + description: + The list of users who updated their devices. + schema: + type: object + properties: + changes: + type: array + items: + type: string + description: |- + The Matrix User IDs of all users who updated their device + identity keys. + example: ["@alice:example.com", "@bob:example.org"] + tags: + - End-to-end encryption diff --git a/api/client-server/kicking.yaml b/api/client-server/kicking.yaml new file mode 100644 index 00000000000..137e72cf357 --- /dev/null +++ b/api/client-server/kicking.yaml @@ -0,0 +1,86 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Kicking API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/kick": + post: + summary: Kick a user from the room. + description: |- + Kick a user from the room. + + The caller must have the required power level in order to perform this operation. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier (not alias) from which the user should be kicked. + required: true + x-example: "!e42d8c:matrix.org" + - in: body + name: body + required: true + schema: + type: object + example: |- + { + "reason": "Telling unfunny jokes", + "user_id": "@cheeky_monkey:matrix.org" + } + properties: + user_id: + type: string + description: The fully qualified user ID of the user being kicked. + reason: + type: string + description: The reason the user has been kicked. + required: ["user_id"] + responses: + 200: + description: The user has been kicked from the room. + examples: + application/json: |- + {} + schema: + type: object + 403: + description: |- + You do not have permission to kick the user from the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: + + - The kicker is not currently in the room. + - The kickee is not currently in the room. + - The kicker's power level is insufficient to kick users from the room. + examples: + application/json: |- + { + "errcode": "M_FORBIDDEN", + "error": "You do not have a high enough power level to kick from this room." + } + tags: + - Room membership diff --git a/api/client-server/leaving.yaml b/api/client-server/leaving.yaml new file mode 100644 index 00000000000..655308d2b40 --- /dev/null +++ b/api/client-server/leaving.yaml @@ -0,0 +1,105 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Leaving API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/leave": + post: + summary: Stop the requesting user participating in a particular room. + description: |- + This API stops a user participating in a particular room. + + If the user was already in the room, they will no longer be able to see + new events in the room. If the room requires an invite to join, they + will need to be re-invited before they can re-join. + + If the user was invited to the room, but had not joined, this call + serves to reject the invite. + + The user will still be allowed to retrieve history from the room which + they were previously allowed to see. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier to leave. + required: true + x-example: "!nkl290a:matrix.org" + responses: + 200: + description: |- + The room has been left. + examples: + application/json: |- + {} + schema: + type: object + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Room membership + "/rooms/{roomId}/forget": + post: + summary: Stop the requesting user remembering about a particular room. + description: |- + This API stops a user remembering about a particular room. + + In general, history is a first class citizen in Matrix. After this API + is called, however, a user will no longer be able to retrieve history + for this room. If all users on a homeserver forget a room, the room is + eligible for deletion from that homeserver. + + If the user is currently joined to the room, they will implicitly leave + the room as part of this API call. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier to forget. + required: true + x-example: "!au1ba7o:matrix.org" + responses: + 200: + description: |- + The room has been forgotten. + examples: + application/json: |- + {} + schema: + type: object + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Room membership diff --git a/api/client-server/list_public_rooms.yaml b/api/client-server/list_public_rooms.yaml new file mode 100644 index 00000000000..3aa449d34fb --- /dev/null +++ b/api/client-server/list_public_rooms.yaml @@ -0,0 +1,301 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Creation API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +paths: + "/publicRooms": + get: + summary: Lists the public rooms on the server. + description: |- + Lists the public rooms on the server. + + This API returns paginated responses. The rooms are ordered by the number + of joined members, with the largest rooms first. + parameters: + - in: query + name: limit + type: number + description: |- + Limit the number of results returned. + - in: query + name: since + type: string + description: |- + A pagination token from a previous request, allowing clients to + get the next (or previous) batch of rooms. + The direction of pagination is specified solely by which token + is supplied, rather than via an explicit flag. + - in: query + name: server + type: string + description: |- + The server to fetch the public room lists from. Defaults to the + local server. + responses: + 200: + description: A list of the rooms on the server. + schema: + type: object + description: A list of the rooms on the server. + required: ["chunk"] + properties: + chunk: + title: "PublicRoomsChunks" + type: array + description: |- + A paginated chunk of public rooms. + items: + type: object + title: "PublicRoomsChunk" + required: + - room_id + - num_joined_members + - world_readable + - guest_can_join + properties: + aliases: + type: array + description: |- + Aliases of the room. May be empty. + items: + type: string + canonical_alias: + type: string + description: |- + The canonical alias of the room, if any. + name: + type: string + description: |- + The name of the room, if any. + num_joined_members: + type: number + description: |- + The number of members joined to the room. + room_id: + type: string + description: |- + The ID of the room. + topic: + type: string + description: |- + The topic of the room, if any. + world_readable: + type: boolean + description: |- + Whether the room may be viewed by guest users without joining. + guest_can_join: + type: boolean + description: |- + Whether guest users may join the room and participate in it. + If they can, they will be subject to ordinary power level + rules like any other user. + avatar_url: + type: string + description: The URL for the room's avatar, if one is set. + next_batch: + type: string + description: |- + A pagination token for the response. The absence of this token + means there are no more results to fetch and the client should + stop paginating. + prev_batch: + type: string + description: |- + A pagination token that allows fetching previous results. The + absence of this token means there are no results before this + batch, i.e. this is the first batch. + total_room_count_estimate: + type: number + description: |- + An estimate on the total number of public rooms, if the + server has an estimate. + examples: + application/json: |- + { + "chunk": [ + { + "aliases": ["#murrays:cheese.bar"], + "avatar_url": "mxc://bleeker.street/CHEDDARandBRIE", + "guest_can_join": false, + "name": "CHEESE", + "num_joined_members": 37, + "room_id": "!ol19s:bleecker.street", + "topic": "Tasty tasty cheese", + "world_readable": true + } + ], + "next_batch": "p190q", + "prev_batch": "p1902", + "total_room_count_estimate": 115 + } + tags: + - Room discovery + post: + summary: Lists the public rooms on the server with optional filter. + description: |- + Lists the public rooms on the server, with optional filter. + + This API returns paginated responses. The rooms are ordered by the number + of joined members, with the largest rooms first. + security: + - accessToken: [] + parameters: + - in: query + name: server + type: string + description: |- + The server to fetch the public room lists from. Defaults to the + local server. + - in: body + name: body + required: true + description: |- + Options for which rooms to return. + schema: + type: object + properties: + limit: + type: number + description: |- + Limit the number of results returned. + since: + type: string + description: |- + A pagination token from a previous request, allowing clients + to get the next (or previous) batch of rooms. The direction + of pagination is specified solely by which token is supplied, + rather than via an explicit flag. + filter: + type: object + title: "Filter" + description: |- + Filter to apply to the results. + properties: + generic_search_term: + type: string + description: |- + A string to search for in the room metadata, e.g. name, + topic, canonical alias etc. (Optional). + example: |- + {"limit": 10, "filter": {"generic_search_term": "foo"}} + responses: + 200: + description: A list of the rooms on the server. + schema: + type: object + description: A list of the rooms on the server. + required: ["chunk"] + properties: + chunk: + title: "PublicRoomsChunks" + type: array + description: |- + A paginated chunk of public rooms. + items: + type: object + title: "PublicRoomsChunk" + required: + - room_id + - num_joined_members + - world_readable + - guest_can_join + properties: + aliases: + type: array + description: |- + Aliases of the room. May be empty. + items: + type: string + canonical_alias: + type: string + description: |- + The canonical alias of the room, if any. + name: + type: string + description: |- + The name of the room, if any. + num_joined_members: + type: number + description: |- + The number of members joined to the room. + room_id: + type: string + description: |- + The ID of the room. + topic: + type: string + description: |- + The topic of the room, if any. + world_readable: + type: boolean + description: |- + Whether the room may be viewed by guest users without joining. + guest_can_join: + type: boolean + description: |- + Whether guest users may join the room and participate in it. + If they can, they will be subject to ordinary power level + rules like any other user. + avatar_url: + type: string + description: The URL for the room's avatar, if one is set. + next_batch: + type: string + description: |- + A pagination token for the response. The absence of this token + means there are no more results to fetch and the client should + stop paginating. + prev_batch: + type: string + description: |- + A pagination token that allows fetching previous results. The + absence of this token means there are no results before this + batch, i.e. this is the first batch. + total_room_count_estimate: + type: number + description: |- + An estimate on the total number of public rooms, if the + server has an estimate. + examples: + application/json: |- + { + "chunk": [ + { + "aliases": ["#murrays:cheese.bar"], + "avatar_url": "mxc://bleeker.street/CHEDDARandBRIE", + "guest_can_join": false, + "name": "CHEESE", + "num_joined_members": 37, + "room_id": "!ol19s:bleecker.street", + "topic": "Tasty tasty cheese", + "world_readable": true + } + ], + "next_batch": "p190q", + "prev_batch": "p1902", + "total_room_count_estimate": 115 + } + tags: + - Room discovery diff --git a/api/client-server/login.yaml b/api/client-server/login.yaml new file mode 100644 index 00000000000..8c0282beaa5 --- /dev/null +++ b/api/client-server/login.yaml @@ -0,0 +1,142 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Registration and Login API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/login": + post: + summary: Authenticates the user. + description: |- + Authenticates the user, and issues an access token they can + use to authorize themself in subsequent requests. + + If the client does not supply a ``device_id``, the server must + auto-generate one. + + The returned access token must be associated with the ``device_id`` + supplied by the client or generated by the server. The server may + invalidate any access token previously associated with that device. See + `Relationship between access tokens and devices`_. + parameters: + - in: body + name: body + schema: + type: object + example: |- + { + "type": "m.login.password", + "user": "cheeky_monkey", + "password": "ilovebananas", + "initial_device_display_name": "Jungle Phone" + } + properties: + type: + type: string + enum: ["m.login.password", "m.login.token"] + description: The login type being used. + user: + type: string + description: The fully qualified user ID or just local part of the user ID, to log in. + medium: + type: string + description: When logging in using a third party identifier, the medium of the identifier. Must be 'email'. + address: + type: string + description: Third party identifier for the user. + password: + type: string + description: |- + Required when ``type`` is ``m.login.password``. The user's + password. + token: + type: string + description: |- + Required when ``type`` is ``m.login.token``. The login token. + device_id: + type: string + description: |- + ID of the client device. If this does not correspond to a + known client device, a new device will be created. The server + will auto-generate a device_id if this is not specified. + initial_device_display_name: + type: string + description: |- + A display name to assign to the newly-created device. Ignored + if ``device_id`` corresponds to a known device. + required: ["type"] + + responses: + 200: + description: The user has been authenticated. + examples: + application/json: |- + { + "user_id": "@cheeky_monkey:matrix.org", + "access_token": "abc123", + "home_server": "matrix.org", + "device_id": "GHTYAJCE" + } + schema: + type: object + properties: + user_id: + type: string + description: The fully-qualified Matrix ID that has been registered. + access_token: + type: string + description: |- + An access token for the account. + This access token can then be used to authorize other requests. + home_server: + type: string + description: The hostname of the homeserver on which the account has been registered. + device_id: + type: string + description: |- + ID of the logged-in device. Will be the same as the + corresponding parameter in the request, if one was specified. + 400: + description: |- + Part of the request was invalid. For example, the login type may not be recognised. + examples: + application/json: |- + { + "errcode": "M_UNKNOWN", + "error": "Bad login type." + } + 403: + description: |- + The login attempt failed. For example, the password may have been incorrect. + examples: + application/json: |- + {"errcode": "M_FORBIDDEN"} + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Session management diff --git a/api/client-server/logout.yaml b/api/client-server/logout.yaml new file mode 100644 index 00000000000..eed4ee44fdc --- /dev/null +++ b/api/client-server/logout.yaml @@ -0,0 +1,45 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Registration and Login API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/logout": + post: + summary: Invalidates a user access token + description: |- + Invalidates an existing access token, so that it can no longer be used for + authorization. + security: + - accessToken: [] + responses: + 200: + description: The access token used in the request was succesfully invalidated. + schema: + type: object + properties: {} + tags: + - Session management diff --git a/api/client-server/message_pagination.yaml b/api/client-server/message_pagination.yaml new file mode 100644 index 00000000000..005b8fb361d --- /dev/null +++ b/api/client-server/message_pagination.yaml @@ -0,0 +1,160 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Rooms API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/messages": + get: + summary: Get a list of events for this room + description: |- + This API returns a list of message and state events for a room. It uses + pagination query parameters to paginate history in the room. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to get events from. + required: true + x-example: "!636q39766251:example.com" + - in: query + type: string + name: from + description: |- + The token to start returning events from. This token can be obtained + from a ``prev_batch`` token returned for each room by the sync API, + or from a ``start`` or ``end`` token returned by a previous request + to this endpoint. + required: true + x-example: "s345_678_333" + - in: query + type: string + name: to + description: |- + The token to stop returning events at. This token can be obtained from + a ``prev_batch`` token returned for each room by the sync endpoint, + or from a ``start`` or ``end`` token returned by a previous request to + this endpoint. + required: false + - in: query + type: string + enum: ["b", "f"] + name: dir + description: |- + The direction to return events from. + required: true + x-example: "b" + - in: query + type: integer + name: limit + description: |- + The maximum number of events to return. Default: 10. + x-example: "3" + - in: query + type: string + name: filter + description: |- + A JSON RoomEventFilter to filter returned events with. + x-example: |- + {"contains_url":true} + responses: + 200: + description: A list of messages with a new token to request more. + schema: + type: object + description: A list of messages with a new token to request more. + properties: + start: + type: string + description: |- + The token the pagination starts from. If ``dir=b`` this will be + the token supplied in ``from``. + end: + type: string + description: |- + The token the pagination ends at. If ``dir=b`` this token should + be used again to request even earlier events. + chunk: + type: array + description: |- + A list of room events. + items: + type: object + title: RoomEvent + examples: + application/json: |- + { + "start": "t47429-4392820_219380_26003_2265", + "end": "t47409-4357353_219380_26003_2265", + "chunk": [ + { + "origin_server_ts": 1444812213737, + "sender": "@alice:example.com", + "event_id": "$1444812213350496Caaaa:example.com", + "content": { + "body": "hello world", + "msgtype":"m.text" + }, + "room_id":"!Xq3620DUiqCaoxq:example.com", + "type":"m.room.message", + "age": 1042 + }, + { + "origin_server_ts": 1444812194656 , + "sender": "@bob:example.com", + "event_id": "$1444812213350496Cbbbb:example.com", + "content": { + "body": "the world is big", + "msgtype":"m.text" + }, + "room_id":"!Xq3620DUiqCaoxq:example.com", + "type":"m.room.message", + "age": 20123 + }, + { + "origin_server_ts": 1444812163990, + "sender": "@bob:example.com", + "event_id": "$1444812213350496Ccccc:example.com", + "content": { + "name": "New room name" + }, + "prev_content": { + "name": "Old room name" + }, + "state_key": "", + "room_id":"!Xq3620DUiqCaoxq:example.com", + "type":"m.room.name", + "age": 50789 + } + ] + } + 403: + description: > + You aren't a member of the room. + tags: + - Room participation diff --git a/api/client-server/notifications.yaml b/api/client-server/notifications.yaml new file mode 100644 index 00000000000..8a5e9553bba --- /dev/null +++ b/api/client-server/notifications.yaml @@ -0,0 +1,142 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +swagger: '2.0' +info: + title: "Matrix Client-Server Notifications API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/notifications": + get: + summary: Gets a list of events that the user has been notified about + description: |- + This API is used to paginate through the list of events that the + user has been, or would have been notified about. + security: + - accessToken: [] + parameters: + - in: query + type: string + name: from + description: Pagination token given to retrieve the next set of events. + required: false + x-example: "xxxxx" + - in: query + type: number + name: limit + description: Limit on the number of events to return in this request. + required: false + x-example: "20" + - in: query + name: only + type: string + description: |- + Allows basic filtering of events returned. Supply ``highlight`` + to return only events where the notification had the highlight + tweak set. + required: false + x-example: "highlight" + responses: + 200: + description: A batch of events is being returned + examples: + application/json: |- + { + "next_token": "abcdef", + "notifications": [ + { + "actions": [ + "notify" + ], + "profile_tag": "hcbvkzxhcvb", + "read": true, + "room_id": "!abcdefg:example.com", + "ts": 1475508881945, + "event": { + "sender": "@alice:example.com", + "type": "m.room.message", + "age": 124524, + "txn_id": "1234", + "content": { + "body": "I am a fish", + "msgtype": "m.text" + }, + "origin_server_ts": 1417731086797, + "event_id": "$74686972643033:example.com" + } + } + ] + } + schema: + type: object + required: ["notifications"] + properties: + next_token: + type: string + description: |- + The token to supply in the ``from`` param of the next + ``/notifications`` request in order to request more + events. If this is absent, there are no more results. + notifications: + type: array + items: + type: object + required: ["actions", "event", "read", "room_id", "ts"] + title: Notification + properties: + actions: + type: array + description: |- + The action(s) to perform when the conditions for this rule are met. + See `Push Rules: API`_. + items: + type: + - object + - string + event: + type: object + title: Event + description: The Event object for the event that triggered the notification. + allOf: + - "$ref": "definitions/event.yaml" + profile_tag: + type: string + description: The profile tag of the rule that matched this event. + read: + type: boolean + description: |- + Indicates whether the user has sent a read receipt indicating + that they have read this message. + room_id: + type: string + description: The ID of the room in which the event was posted. + ts: + type: integer + description: |- + The unix timestamp at which the event notification was sent, + in milliseconds. + description: The list of events that triggered notifications. + tags: + - Push notifications diff --git a/api/client-server/v1/sync.yaml b/api/client-server/old_sync.yaml similarity index 74% rename from api/client-server/v1/sync.yaml rename to api/client-server/old_sync.yaml index 7c1d43f3e9c..e3e1ea8a2a0 100644 --- a/api/client-server/v1/sync.yaml +++ b/api/client-server/old_sync.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Sync API" + title: "Matrix Client-Server Sync API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/events": get: @@ -24,13 +33,20 @@ paths: description: |- This will listen for new events and return them to the caller. This will block until an event is received, or until the ``timeout`` is reached. + + This endpoint was deprecated in r0 of this specification. Clients + should instead call the |/sync|_ API with a ``since`` parameter. See + the `migration guide + `_. security: - accessToken: [] parameters: - in: query type: string name: from - description: The token to stream from. + description: |- + The token to stream from. This token is either from a previous + request to this API or from the initial sync API. required: false x-example: "s3456_9_0" - in: query @@ -39,16 +55,6 @@ paths: description: The maximum time in milliseconds to wait for an event. required: false x-example: "35000" - - in: query - type: string - name: archived - description: |- - Whether to include rooms that the user has left. If absent then - only rooms that the user has been invited to or has joined are - included. If set to "true" then rooms that the user has left are - included as well. - required: false - x-example: "true" responses: 200: description: "The events received, which may be none." @@ -68,7 +74,7 @@ paths: "origin_server_ts": 1432804485886, "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "type": "m.room.message", - "user_id": "@bob:localhost" + "sender": "@bob:localhost" } ] } @@ -78,29 +84,37 @@ paths: start: type: string description: |- - A token which correlates to the first value in ``chunk``. Used - for pagination. + A token which correlates to the first value in ``chunk``. This + is usually the same token supplied to ``from=``. end: type: string description: |- - A token which correlates to the last value in ``chunk``. Used - for pagination. + A token which correlates to the last value in ``chunk``. This + token should be used in the next request to ``/events``. chunk: type: array description: "An array of events." items: type: object - title: RoomEvent + title: Event allOf: - - "$ref": "core-event-schema/room_event.json" + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" 400: description: "Bad pagination ``from`` parameter." + tags: + - Room participation + deprecated: true "/initialSync": get: summary: Get the user's current state. description: |- This returns the full state for this user, with an optional limit on the number of messages per room to return. + + This endpoint was deprecated in r0 of this specification. Clients + should instead call the |/sync|_ API with no ``since`` parameter. See + the `migration guide + `_. security: - accessToken: [] parameters: @@ -110,6 +124,16 @@ paths: description: The maximum number of messages to return for each room. required: false x-example: "2" + - in: query + type: boolean + name: archived + description: |- + Whether to include rooms that the user has left. If ``false`` then + only rooms that the user has been invited to or has joined are + included. If set to ``true`` then rooms that the user has left are + included as well. By default this is ``false``. + required: false + x-example: "true" responses: 200: description: The user's current state. @@ -129,6 +153,14 @@ paths: "type": "m.presence" } ], + "account_data": [ + { + "type": "org.example.custom.config", + "content": { + "custom_config_key": "custom_config_value" + } + } + ], "rooms": [ { "membership": "join", @@ -144,7 +176,7 @@ paths: "origin_server_ts": 1432804485886, "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "type": "m.room.message", - "user_id": "@alice:localhost" + "sender": "@alice:localhost" }, { "age": 343511809, @@ -156,7 +188,7 @@ paths: "origin_server_ts": 1432804487480, "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "type": "m.room.message", - "user_id": "@bob:localhost" + "sender": "@bob:localhost" } ], "end": "s3456_9_0", @@ -174,13 +206,12 @@ paths: "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "state_key": "", "type": "m.room.join_rules", - "user_id": "@alice:localhost" + "sender": "@alice:localhost" }, { "age": 6547561012, "content": { "avatar_url": "mxc://localhost/fzysBrHpPEeTGANCVLXWXNMI#auto", - "displayname": null, "membership": "join" }, "event_id": "$1426600438280zExKY:localhost", @@ -189,7 +220,7 @@ paths: "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "state_key": "@alice:localhost", "type": "m.room.member", - "user_id": "@alice:localhost" + "sender": "@alice:localhost" }, { "age": 7148267200, @@ -201,7 +232,7 @@ paths: "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "state_key": "", "type": "m.room.create", - "user_id": "@alice:localhost" + "sender": "@alice:localhost" }, { "age": 1622568720, @@ -216,7 +247,7 @@ paths: "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "state_key": "@bob:localhost", "type": "m.room.member", - "user_id": "@bob:localhost" + "sender": "@bob:localhost" }, { "age": 7148267004, @@ -240,10 +271,22 @@ paths: "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", "state_key": "", "type": "m.room.power_levels", - "user_id": "@alice:localhost" + "sender": "@alice:localhost" } ], - "visibility": "private" + "visibility": "private", + "account_data": [ + { + "type": "m.tag", + "content": {"tags": {"work": {"order": 1}}} + }, + { + "type": "org.example.custom.room.config", + "content": { + "custom_config_key": "custom_config_value" + } + } + ] } ] } @@ -263,7 +306,7 @@ paths: type: object title: Event allOf: - - "$ref": "core-event-schema/event.json" + - "$ref": "definitions/event-schemas/schema/core-event-schema/event.yaml" rooms: type: array items: @@ -282,7 +325,7 @@ paths: title: "InviteEvent" description: "The invite event if ``membership`` is ``invite``" allOf: - - "$ref": "v1-event-schema/m.room.member" + - "$ref": "definitions/event-schemas/schema/m.room.member" messages: type: object title: PaginationChunk @@ -310,7 +353,7 @@ paths: type: object title: RoomEvent allOf: - - "$ref": "core-event-schema/room_event.json" + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" required: ["start", "end", "chunk"] state: type: array @@ -323,23 +366,48 @@ paths: title: StateEvent type: object allOf: - - "$ref": "core-event-schema/state_event.json" + - "$ref": "definitions/event-schemas/schema/core-event-schema/state_event.yaml" visibility: type: string enum: ["private", "public"] description: |- Whether this room is visible to the ``/publicRooms`` API or not." + account_data: + type: array + description: |- + The private data that this user has attached to + this room. + items: + title: Event + type: object + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/event.yaml" required: ["room_id", "membership"] + account_data: + type: array + description: |- + The global private data created by this user. + items: + title: Event + type: object + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/event.yaml" required: ["end", "rooms", "presence"] 404: description: There is no avatar URL for this user or this user does not exist. + tags: + - Room participation + deprecated: true "/events/{eventId}": get: summary: Get a single event by event ID. description: |- Get a single event based on ``event_id``. You must have permission to retrieve this event e.g. by being a member in the room for this event. + + This endpoint was deprecated in r0 of this specification. Clients + should instead call the |/rooms/{roomId}/context/{eventId}|_ API. security: - accessToken: [] parameters: @@ -359,13 +427,16 @@ paths: "body": "Hello world!", "msgtype": "m.text" }, - "room_id:": "!wfgy43Sg4a:matrix.org", - "user_id": "@bob:matrix.org", + "room_id": "!wfgy43Sg4a:matrix.org", + "sender": "@bob:matrix.org", "event_id": "$asfDuShaf7Gafaw:matrix.org", "type": "m.room.message" } schema: allOf: - - "$ref": "core-event-schema/event.json" + - "$ref": "definitions/event-schemas/schema/core-event-schema/event.yaml" 404: description: The event was not found or you do not have permission to read this event. + tags: + - Room participation + deprecated: true diff --git a/api/client-server/peeking_events.yaml b/api/client-server/peeking_events.yaml new file mode 100644 index 00000000000..e33a4ce36e2 --- /dev/null +++ b/api/client-server/peeking_events.yaml @@ -0,0 +1,114 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Sync Guest API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/events": + get: + summary: Listen on the event stream. + description: |- + This will listen for new events related to a particular room and return + them to the caller. This will block until an event is received, or until + the ``timeout`` is reached. + + This API is the same as the normal ``/events`` endpoint, but can be + called by users who have not joined the room. + + Note that the normal ``/events`` endpoint has been deprecated. This + API will also be deprecated at some point, but its replacement is not + yet known. + security: + - accessToken: [] + parameters: + - in: query + type: string + name: from + description: |- + The token to stream from. This token is either from a previous + request to this API or from the initial sync API. + required: false + x-example: "s3456_9_0" + - in: query + type: integer + name: timeout + description: The maximum time in milliseconds to wait for an event. + required: false + x-example: "35000" + - in: query + type: string + name: room_id + description: |- + The room ID for which events should be returned. + x-example: + - "!somewhere:over.the.rainbow" + responses: + 200: + description: "The events received, which may be none." + examples: + application/json: |- + { + "start": "s3456_9_0", + "end": "s3457_9_0", + "chunk": [ + { + "age": 32, + "content": { + "body": "incoming message", + "msgtype": "m.text" + }, + "event_id": "$14328055551tzaee:localhost", + "origin_server_ts": 1432804485886, + "room_id": "!TmaZBKYIFrIPVGoUYp:localhost", + "type": "m.room.message", + "sender": "@bob:localhost" + } + ] + } + schema: + type: object + properties: + start: + type: string + description: |- + A token which correlates to the first value in ``chunk``. This + is usually the same token supplied to ``from=``. + end: + type: string + description: |- + A token which correlates to the last value in ``chunk``. This + token should be used in the next request to ``/events``. + chunk: + type: array + description: "An array of events." + items: + type: object + title: Event + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + 400: + description: "Bad pagination ``from`` parameter." + # No tags to exclude this from the swagger UI - use the normal version instead. diff --git a/api/client-server/v1/presence.yaml b/api/client-server/presence.yaml similarity index 81% rename from api/client-server/v1/presence.yaml rename to api/client-server/presence.yaml index 5684398b01b..7a963067769 100644 --- a/api/client-server/v1/presence.yaml +++ b/api/client-server/presence.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Presence API" + title: "Matrix Client-Server Presence API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/presence/{userId}/status": put: @@ -49,7 +58,7 @@ paths: properties: presence: type: string - enum: ["online", "offline", "unavailable", "free_for_chat"] + enum: ["online", "offline", "unavailable"] description: The new presence state. status_msg: type: string @@ -67,6 +76,8 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" + tags: + - Presence get: summary: Get this user's presence state. description: |- @@ -85,15 +96,14 @@ paths: application/json: |- { "presence": "unavailable", - "last_active_ago": 420845, - "status_msg": null + "last_active_ago": 420845 } schema: type: object properties: presence: type: string - enum: ["online", "offline", "unavailable", "free_for_chat"] + enum: ["online", "offline", "unavailable"] description: This user's presence. last_active_ago: type: integer @@ -103,10 +113,16 @@ paths: status_msg: type: [string, "null"] description: The state message for this user if one was set. + currently_active: + type: boolean + description: "Whether the user is currently active" + required: ["presence"] 404: description: |- There is no presence state for this user. This user may not exist or isn't exposing presence information to you. + tags: + - Presence "/presence/list/{userId}": post: summary: Add or remove users from this presence list. @@ -161,6 +177,8 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" + tags: + - Presence get: summary: Get presence events for this presence list. description: |- @@ -180,8 +198,6 @@ paths: [ { "content": { - "avatar_url": "mxc://matrix.org/AfwefuigfWEfhuiPP", - "displayname": "Alice Margatroid", "last_active_ago": 395, "presence": "offline", "user_id": "@alice:matrix.org" @@ -190,11 +206,10 @@ paths: }, { "content": { - "avatar_url": "mxc://matrix.org/FWEhuiwegfWEfhuiwf", - "displayname": "Marisa Kirisame", "last_active_ago": 16874, "presence": "online", - "user_id": "@marisa:matrix.org" + "user_id": "@marisa:matrix.org", + "currently_active": true }, "type": "m.presence" } @@ -205,4 +220,6 @@ paths: type: object title: PresenceEvent allOf: - - "$ref": "core-event-schema/event.json" + - "$ref": "definitions/event-schemas/schema/core-event-schema/event.yaml" + tags: + - Presence diff --git a/api/client-server/v1/profile.yaml b/api/client-server/profile.yaml similarity index 84% rename from api/client-server/v1/profile.yaml rename to api/client-server/profile.yaml index e19466bf7bf..89ebdc2abd8 100644 --- a/api/client-server/v1/profile.yaml +++ b/api/client-server/profile.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Profile API" + title: "Matrix Client-Server Profile API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/profile/{userId}/displayname": put: @@ -59,6 +68,8 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" + tags: + - User data get: summary: Get the user's display name. description: |- @@ -85,9 +96,11 @@ paths: properties: displayname: type: string - description: The user's display name if they have set one. + description: The user's display name if they have set one, otherwise not present. 404: description: There is no display name for this user or this user does not exist. + tags: + - User data "/profile/{userId}/avatar_url": put: summary: Set the user's avatar URL. @@ -129,6 +142,8 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" + tags: + - User data get: summary: Get the user's avatar URL. description: |- @@ -155,9 +170,11 @@ paths: properties: avatar_url: type: string - description: The user's avatar URL if they have set one. + description: The user's avatar URL if they have set one, otherwise not present. 404: description: There is no avatar URL for this user or this user does not exist. + tags: + - User data "/profile/{userId}": get: summary: Get this user's profile information. @@ -170,7 +187,7 @@ paths: - in: path type: string name: userId - description: The user whose avatar URL to get. + description: The user whose profile information to get. required: true x-example: "@alice:example.com" responses: @@ -187,9 +204,11 @@ paths: properties: avatar_url: type: string - description: The user's avatar URL if they have set one. + description: The user's avatar URL if they have set one, otherwise not present. displayname: type: string - description: The user's display name if they have set one. + description: The user's display name if they have set one, otherwise not present. 404: - description: There is no profile information for this user or this user does not exist. \ No newline at end of file + description: There is no profile information for this user or this user does not exist. + tags: + - User data diff --git a/api/client-server/v1/pusher.yaml b/api/client-server/pusher.yaml similarity index 50% rename from api/client-server/v1/pusher.yaml rename to api/client-server/pusher.yaml index 8c243f2bcbd..5807b00752e 100644 --- a/api/client-server/v1/pusher.yaml +++ b/api/client-server/pusher.yaml @@ -1,23 +1,122 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Push API" + title: "Matrix Client-Server Push API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: + "/pushers": + get: + summary: Gets the current pushers for the authenticated user + description: |- + Gets all currently active pushers for the authenticated user + security: + - accessToken: [] + responses: + 200: + description: The pushers for this user + examples: + application/json: |- + { + "pushers": [ + { + "pushkey": "Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=", + "kind": "http", + "app_id": "face.mcapp.appy.prod", + "app_display_name": "Appy McAppface", + "device_display_name": "Alice's Phone", + "profile_tag": "xyz", + "lang": "en-US", + "data": { + "url": "https://example.com/_matrix/push/v1/notify" + } + } + ] + } + schema: + type: object + properties: + pushers: + type: array + title: Pushers + description: |- + An array containing the current pushers for the user + items: + type: object + title: Pusher + properties: + pushkey: + type: string + description: |- + This is a unique identifier for this pusher. See `/set` for + more detail. + Max length, 512 bytes. + kind: + type: string + description: |- + The kind of pusher. ``"http"`` is a pusher that + sends HTTP pokes. + app_id: + type: string + description: |- + This is a reverse-DNS style identifier for the application. + Max length, 64 chars. + app_display_name: + type: string + description: |- + A string that will allow the user to identify what application + owns this pusher. + device_display_name: + type: string + description: |- + A string that will allow the user to identify what device owns + this pusher. + profile_tag: + type: string + description: |- + This string determines which set of device specific rules this + pusher executes. + lang: + type: string + description: |- + The preferred language for receiving notifications (e.g. 'en' + or 'en-US') + data: + type: object + description: |- + A dictionary of information for the pusher implementation + itself. + title: PusherData + properties: + url: + type: string + description: |- + Required if ``kind`` is ``http``. The URL to use to send + notifications to. + tags: + - Push notifications "/pushers/set": post: summary: Modify a pusher for this user on the homeserver. @@ -40,8 +139,8 @@ paths: "kind": "http", "app_display_name": "Mat Rix", "device_display_name": "iPhone 9", + "profile_tag": "xxyyzz", "app_id": "com.example.app.ios", - "profile_tag": "4bea66906d0111e59d70feff819cdc9f", "pushkey": "APA91bHPRgkF3JUikC4ENAHEeMrd41Zxv3hVZjC9KtT8OvPVGJ-hQMRKRrZuJAEcl7B338qju59zJMjw2DELjzEvxwYv7hH5Ynpc1ODQ0aT4U4OFEeco8ohsN5PjL1iC2dNtk2BAokeMCg2ZXKqpc8FXKmhX94kIxQ", "data": { "url": "https://push-gateway.location.here" @@ -60,21 +159,9 @@ paths: Max length, 512 bytes. kind: type: string - enum: ["http", null] description: |- The kind of pusher to configure. ``"http"`` makes a pusher that sends HTTP pokes. ``null`` deletes the pusher. - profile_tag: - type: string - description: |- - This is a string that determines what set of device rules will - be matched when evaluating push rules for this pusher. It is - an arbitrary string. Multiple devices may use the same - ``profile_tag``. It is advised that when an app's data is - copied or restored to a different device, this value remain - the same. Client apps should offer ways to change the - ``profile_tag``, optionally copying rules from the old - profile tag. Max length, 32 bytes. app_id: type: string description: |- @@ -92,6 +179,11 @@ paths: description: |- A string that will allow the user to identify what device owns this pusher. + profile_tag: + type: string + description: |- + This string determines which set of device specific rules this + pusher executes. lang: type: string description: |- @@ -103,6 +195,7 @@ paths: A dictionary of information for the pusher implementation itself. If ``kind`` is ``http``, this should contain ``url`` which is the URL to use to send notifications to. + title: PusherData properties: url: type: string @@ -114,10 +207,10 @@ paths: description: |- If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others with - different user IDs. Otherwise, the Home Server must remove any + different user IDs. Otherwise, the homeserver must remove any other pushers with the same App ID and pushkey for different users. The default is ``false``. - required: ['profile_tag', 'kind', 'app_id', 'app_display_name', + required: ['kind', 'app_id', 'app_display_name', 'device_display_name', 'pushkey', 'lang', 'data'] responses: 200: @@ -141,4 +234,5 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" - + tags: + - Push notifications diff --git a/api/client-server/v1/pushrules.yaml b/api/client-server/pushrules.yaml similarity index 69% rename from api/client-server/v1/pushrules.yaml rename to api/client-server/pushrules.yaml index 31e84f55462..0ba8d481284 100644 --- a/api/client-server/v1/pushrules.yaml +++ b/api/client-server/pushrules.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Push Rules API" + title: "Matrix Client-Server Push Rules API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/pushrules/": get: @@ -33,31 +42,18 @@ paths: description: All the push rulesets for this user. schema: type: object - required: ["device", "global"] + required: ["global"] properties: - device: - type: object - title: Devices - description: A dictionary of profile tags to rulesets. - additionalProperties: - x-pattern: "$PROFILE_TAG" - type: object - description: The ruleset for this profile tag. - title: Ruleset - allOf: [ - "$ref": "definitions/push_ruleset.json" - ] global: type: object description: The global ruleset. title: Ruleset allOf: [ - "$ref": "definitions/push_ruleset.json" + "$ref": "definitions/push_ruleset.yaml" ] examples: application/json: |- { - "device": {}, "global": { "content": [ { @@ -245,6 +241,8 @@ paths: ] } } + tags: + - Push notifications "/pushrules/{scope}/{kind}/{ruleId}": get: summary: Retrieve a push rule. @@ -259,13 +257,12 @@ paths: required: true x-example: "global" description: |- - Either ``global`` or ``device/`` to specify global - rules or device rules for the given ``profile_tag``. + ``global`` to specify global rules. - in: path type: string name: kind required: true - x-example: room + x-example: content enum: ["override", "underride", "sender", "room", "content"] description: | The kind of rule @@ -273,7 +270,7 @@ paths: type: string name: ruleId required: true - x-example: "#spam:example.com" + x-example: "nocake" description: | The identifier for the rule. responses: @@ -287,16 +284,19 @@ paths: "actions": [ "dont_notify" ], - "rule_id": "#spam:matrix.org", - "enabled": true + "pattern": "cake*lie", + "rule_id": "nocake", + "enabled": true, + "default": false } schema: type: object description: The push rule. - title: PushRule allOf: [ - "$ref": "definitions/push_rule.json" + "$ref": "definitions/push_rule.yaml" ] + tags: + - Push notifications delete: summary: Delete a push rule. description: |- @@ -310,13 +310,12 @@ paths: required: true x-example: "global" description: |- - Either ``global`` or ``device/`` to specify global - rules or device rules for the given ``profile_tag``. + ``global`` to specify global rules. - in: path type: string name: kind required: true - x-example: room + x-example: content enum: ["override", "underride", "sender", "room", "content"] description: | The kind of rule @@ -324,7 +323,7 @@ paths: type: string name: ruleId required: true - x-example: "#spam:example.com" + x-example: "nocake" description: | The identifier for the rule. responses: @@ -335,6 +334,8 @@ paths: {} schema: type: object # empty json object + tags: + - Push notifications put: summary: Add or change a push rule. description: |- @@ -350,13 +351,12 @@ paths: required: true x-example: "global" description: |- - Either ``global`` or ``device/`` to specify global - rules or device rules for the given ``profile_tag``. + ``global`` to specify global rules. - in: path type: string name: kind required: true - x-example: room + x-example: content enum: ["override", "underride", "sender", "room", "content"] description: | The kind of rule @@ -364,7 +364,7 @@ paths: type: string name: ruleId required: true - x-example: "#spam:example.com" + x-example: "nocake" description: | The identifier for the rule. - in: query @@ -374,7 +374,8 @@ paths: x-example: someRuleId description: |- Use 'before' with a ``rule_id`` as its value to make the new rule the - next-most important rule with respect to the given rule. + next-most important rule with respect to the given user defined rule. + It is not possible to add a rule relative to a predefined server rule. - in: query type: string name: after @@ -382,7 +383,8 @@ paths: x-example: anotherRuleId description: |- This makes the new rule the next-less important rule relative to the - given rule. + given user defined rule. It is not possible to add a rule relative + to a predefined server rule. - in: body name: pushrule description: |- @@ -410,11 +412,14 @@ paths: description: |- The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions - always matches. + always matches. Only applicable to ``underride`` and ``override`` rules. items: type: object - title: conditions - allOf: [ "$ref": "definitions/push_condition.json" ] + allOf: [ "$ref": "definitions/push_condition.yaml" ] + pattern: + type: string + description: |- + Only applicable to ``content`` rules. The glob-style pattern to match against. required: ["actions"] responses: 200: @@ -438,13 +443,116 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" + tags: + - Push notifications "/pushrules/{scope}/{kind}/{ruleId}/enabled": + get: + summary: "Get whether a push rule is enabled" + description: + This endpoint gets whether the specified push rule is enabled. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: scope + required: true + x-example: "global" + description: |- + Either ``global`` or ``device/`` to specify global + rules or device rules for the given ``profile_tag``. + - in: path + type: string + name: kind + required: true + x-example: cake + enum: ["override", "underride", "sender", "room", "content"] + description: | + The kind of rule + - in: path + type: string + name: ruleId + required: true + x-example: nocake + description: | + The identifier for the rule. + responses: + 200: + description: Whether the push rule is enabled. + examples: + application/json: |- + { + "enabled": true + } + schema: + type: object + properties: + enabled: + type: boolean + description: Whether the push rule is enabled or not. + required: ["enabled"] put: summary: "Enable or disable a push rule." description: |- This endpoint allows clients to enable or disable the specified push rule. security: - accessToken: [] + parameters: + - in: path + type: string + name: scope + required: true + x-example: "global" + description: |- + ``global`` to specify global rules. + - in: path + type: string + name: kind + required: true + x-example: content + enum: ["override", "underride", "sender", "room", "content"] + description: | + The kind of rule + - in: path + type: string + name: ruleId + required: true + x-example: "nocake" + description: | + The identifier for the rule. + - in: body + name: body + description: | + Whether the push rule is enabled or not. + required: true + schema: + type: object + properties: + enabled: + type: boolean + description: Whether the push rule is enabled or not. + required: ["enabled"] + example: |- + { + "enabled": true + } + responses: + 200: + description: The push rule was enabled or disabled. + examples: + application/json: |- + {} + schema: + type: object + tags: + - Push notifications + "/pushrules/{scope}/{kind}/{ruleId}/actions": + get: + summary: "The actions for a push rule" + description: + This endpoint get the actions for the specified push rule. + security: + - accessToken: [] parameters: - in: path type: string @@ -454,6 +562,53 @@ paths: description: |- Either ``global`` or ``device/`` to specify global rules or device rules for the given ``profile_tag``. + - in: path + type: string + name: kind + required: true + x-example: content + enum: ["override", "underride", "sender", "room", "content"] + description: | + The kind of rule + - in: path + type: string + name: ruleId + required: true + x-example: nocake + description: | + The identifier for the rule. + responses: + 200: + description: The actions for this push rule. + examples: + application/json: |- + { + "actions": ["notify"] + } + schema: + type: object + properties: + actions: + type: array + description: The action(s) to perform for this rule. + items: + type: string + required: ["actions"] + put: + summary: "Set the actions for a push rule." + description: |- + This endpoint allows clients to change the actions of a push rule. + This can be used to change the actions of builtin rules. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: scope + required: true + x-example: "global" + description: |- + ``global`` to specify global rules. - in: path type: string name: kind @@ -470,19 +625,32 @@ paths: description: | The identifier for the rule. - in: body - name: + name: body description: | - Whether the push rule is enabled or not. + The action(s) to perform when the conditions for this rule are met. required: true schema: - type: boolean + type: object + properties: + actions: + type: array + description: The action(s) to perform for this rule. + items: + type: string + enum: ["notify", "dont_notify", "coalesce", "set_tweak"] + # TODO: type: object e.g. {"set_sound":"beeroclock.wav"} :/ + required: ["actions"] example: |- - true + { + "actions": ["notify"] + } responses: 200: - description: The push rule was enabled or disabled. + description: The actions for the push rule were set. examples: application/json: |- {} schema: - type: object # empty json object + type: object + tags: + - Push notifications diff --git a/api/client-server/v2_alpha/receipts.yaml b/api/client-server/receipts.yaml similarity index 69% rename from api/client-server/v2_alpha/receipts.yaml rename to api/client-server/receipts.yaml index b60f72e6bfd..9bd7f56cbf7 100644 --- a/api/client-server/v2_alpha/receipts.yaml +++ b/api/client-server/receipts.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v2 Receipts API" + title: "Matrix Client-Server Receipts API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/v2_alpha +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/rooms/{roomId}/receipt/{receiptType}/{eventId}": post: @@ -67,3 +76,5 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" + tags: + - Room participation diff --git a/api/client-server/redaction.yaml b/api/client-server/redaction.yaml new file mode 100644 index 00000000000..df60f817238 --- /dev/null +++ b/api/client-server/redaction.yaml @@ -0,0 +1,93 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server message redaction API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/redact/{eventId}/{txnId}": + put: + summary: Strips all non-integrity-critical information out of an event. + description: |- + Strips all information out of an event which isn't critical to the + integrity of the server-side representation of the room. + + This cannot be undone. + + Users may redact their own events, and any user with a power level + greater than or equal to the `redact` power level of the room may + redact events there. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room from which to redact the event. + required: true + x-example: "!637q39766251:example.com" + - in: path + type: string + name: eventId + description: The ID of the event to redact + required: true + x-example: "bai2b1i9:matrix.org" + - in: path + name: txnId + type: string + description: |- + The transaction ID for this event. Clients should generate a + unique ID; it will be used by the server to ensure idempotency of requests. + required: true + x-example: "37" + - in: body + name: body + schema: + type: object + example: |- + { + "reason": "Indecent material" + } + properties: + reason: + type: string + description: The reason for the event being redacted. + responses: + 200: + description: "An ID for the redaction event." + examples: + application/json: |- + { + "event_id": "YUwQidLecu" + } + schema: + type: object + properties: + event_id: + type: string + description: |- + A unique identifier for the event. + tags: + - Room participation diff --git a/api/client-server/registration.yaml b/api/client-server/registration.yaml new file mode 100644 index 00000000000..d09cd431efc --- /dev/null +++ b/api/client-server/registration.yaml @@ -0,0 +1,347 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Registration API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +paths: + "/register": + post: + summary: Register for an account on this homeserver. + description: |- + This API endpoint uses the `User-Interactive Authentication API`_. + + Register for an account on this homeserver. + + There are two kinds of user account: + + - `user` accounts. These accounts may use the full API described in this specification. + + - `guest` accounts. These accounts may have limited permissions and may not be supported by all servers. + + If registration is successful, this endpoint will issue an access token + the client can use to authorize itself in subsequent requests. + + If the client does not supply a ``device_id``, the server must + auto-generate one. + + The returned access token must be associated with the ``device_id`` + supplied by the client or generated by the server. The server may + invalidate any access token previously associated with that device. See + `Relationship between access tokens and devices`_. + parameters: + - in: query + name: kind + type: string + # swagger-UI overrides the default with the example, so better make the + # example the default. + x-example: user + required: false + default: user + enum: + - guest + - user + description: The kind of account to register. Defaults to `user`. + - in: body + name: body + schema: + type: object + properties: + auth: + description: |- + Additional authentication information for the + user-interactive authentication API. Note that this + information is *not* used to define how the registered user + should be authenticated, but is instead used to + authenticate the ``register`` call itself. It should be + left empty, or omitted, unless an earlier call returned an + response with status code 401. + "$ref": "definitions/auth_data.yaml" + bind_email: + type: boolean + description: |- + If true, the server binds the email used for authentication to + the Matrix ID with the ID Server. + example: false + username: + type: string + description: |- + The local part of the desired Matrix ID. If omitted, + the homeserver MUST generate a Matrix ID local part. + example: cheeky_monkey + password: + type: string + description: The desired password for the account. + example: ilovebananas + device_id: + type: string + description: |- + ID of the client device. If this does not correspond to a + known client device, a new device will be created. The server + will auto-generate a device_id if this is not specified. + example: GHTYAJCE + initial_device_display_name: + type: string + description: |- + A display name to assign to the newly-created device. Ignored + if ``device_id`` corresponds to a known device. + example: Jungle Phone + responses: + 200: + description: The account has been registered. + examples: + application/json: |- + { + "user_id": "@cheeky_monkey:matrix.org", + "access_token": "abc123", + "home_server": "matrix.org", + "device_id": "GHTYAJCE" + } + schema: + type: object + properties: + user_id: + type: string + description: The fully-qualified Matrix ID that has been registered. + access_token: + type: string + description: |- + An access token for the account. + This access token can then be used to authorize other requests. + home_server: + type: string + description: The hostname of the homeserver on which the account has been registered. + device_id: + type: string + description: |- + ID of the registered device. Will be the same as the + corresponding parameter in the request, if one was specified. + 400: + description: |- + Part of the request was invalid. This may include one of the following error codes: + + * ``M_USER_IN_USE`` : The desired user ID is already taken. + * ``M_INVALID_USERNAME`` : The desired user ID is not a valid user name. + * ``M_EXCLUSIVE`` : The desired user ID is in the exclusive namespace + claimed by an application service. + + These errors may be returned at any stage of the registration process, + including after authentication if the requested user ID was registered + whilst the client was performing authentication. + + Homeservers MUST perform the relevant checks and return these codes before + performing User-Interactive Authentication, although they may also return + them after authentication is completed if, for example, the requested user ID + was registered whilst the client was performing authentication. + examples: + application/json: |- + { + "errcode": "M_USER_IN_USE", + "error": "Desired user ID is already taken." + } + 401: + description: |- + The homeserver requires additional authentication information. + schema: + "$ref": "definitions/auth_response.yaml" + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - User data + "/register/email/requestToken": + post: + summary: Requests a validation token be sent to the given email address for the purpose of registering an account + description: |- + Proxies the identity server API ``validate/email/requestToken``, but + first checks that the given email address is not already associated + with an account on this Home Server. Note that, for consistency, + this API takes JSON objects, though the Identity Server API takes + ``x-www-form-urlencoded`` parameters. See the Identity Server API for + further information. + parameters: + - in: body + name: body + schema: + type: object + properties: + id_server: + type: string + description: The ID server to send the onward request to as a hostname with an appended colon and port number if the port is not the default. + example: "id.matrix.org" + client_secret: + type: string + description: Client-generated secret string used to protect this session + example: "this_is_my_secret_string" + email: + type: string + description: The email address + example: "example@example.com" + send_attempt: + type: number + description: Used to distinguish protocol level retries from requests to re-send the email. + example: "1" + required: ["client_secret", "email", "send_attempt"] + responses: + 200: + description: |- + An email has been sent to the specified address. + Note that this may be an email containing the validation token or it may be informing + the user of an error. + examples: + application/json: "{}" + schema: + type: object + 400: + description: |- + Part of the request was invalid. This may include one of the following error codes: + + * ``M_THREEPID_IN_USE`` : The email address is already registered to an account on this server. + However, if the home server has the ability to send email, it is recommended that the server + instead send an email to the user with instructions on how to reset their password. + This prevents malicious parties from being able to determine if a given email address + has an account on the Home Server in question. + * ``M_SERVER_NOT_TRUSTED`` : The ``id_server`` parameter refers to an ID server + that is not trusted by this Home Server. + examples: + application/json: |- + { + "errcode": "M_THREEPID_IN_USE", + "error": "The specified address is already in use" + } + schema: + type: object + "/account/password": + post: + summary: "Changes a user's password." + description: |- + Changes the password for an account on this homeserver. + + This API endpoint uses the `User-Interactive Authentication API`_. + + An access token should be submitted to this endpoint if the client has + an active session. + + The homeserver may change the flows available depending on whether a + valid access token is provided. + security: + - accessToken: [] + parameters: + - in: body + name: body + schema: + type: object + properties: + new_password: + type: string + description: The new password for the account. + example: "ihatebananas" + auth: + description: |- + Additional authentication information for the user-interactive authentication API. + "$ref": "definitions/auth_data.yaml" + required: ["new_password"] + responses: + 200: + description: The password has been changed. + examples: + application/json: "{}" + schema: + type: object + 401: + description: |- + The homeserver requires additional authentication information. + schema: + "$ref": "definitions/auth_response.yaml" + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - User data + "/account/password/email/requestToken": + post: + summary: Requests a validation token be sent to the given email address for the purpose of resetting a user's password + description: |- + Proxies the identity server API ``validate/email/requestToken``, but + first checks that the given email address **is** associated with an account + on this Home Server. This API should be used to request + validation tokens when authenticating for the + `account/password` endpoint. This API's parameters and response are + identical to that of the HS API |/register/email/requestToken|_ except that + `M_THREEPID_NOT_FOUND` may be returned if no account matching the + given email address could be found. The server may instead send an + email to the given address prompting the user to create an account. + `M_THREEPID_IN_USE` may not be returned. + + .. |/register/email/requestToken| replace:: ``/register/email/requestToken`` + + .. _/register/email/requestToken: #post-matrix-client-%CLIENT_MAJOR_VERSION%-register-email-requesttoken + responses: + 200: + description: An email was sent to the given address + "/account/deactivate": + post: + summary: "Deactivate a user's account." + description: |- + Deactivate the user's account, removing all ability for the user to + login again. + + This API endpoint uses the `User-Interactive Authentication API`_. + + An access token should be submitted to this endpoint if the client has + an active session. + + The homeserver may change the flows available depending on whether a + valid access token is provided. + security: + - accessToken: [] + parameters: + - in: body + name: body + schema: + type: object + properties: + auth: + description: |- + Additional authentication information for the user-interactive authentication API. + "$ref": "definitions/auth_data.yaml" + responses: + 200: + description: The account has been deactivated. + examples: + application/json: "{}" + schema: + type: object + 401: + description: |- + The homeserver requires additional authentication information. + schema: + "$ref": "definitions/auth_response.yaml" + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - User data diff --git a/api/client-server/room_initial_sync.yaml b/api/client-server/room_initial_sync.yaml new file mode 100644 index 00000000000..bf403606f11 --- /dev/null +++ b/api/client-server/room_initial_sync.yaml @@ -0,0 +1,233 @@ +swagger: '2.0' +info: + title: "Matrix Client-Server Rooms API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/initialSync": + get: + summary: Snapshot the current state of a room and its most recent messages. + description: |- + Get a copy of the current state and the most recent messages in a room. + + This endpoint was deprecated in r0 of this specification. There is no + direct replacement; the relevant information is returned by the + |/sync|_ API. See the `migration guide + `_. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to get the data. + required: true + x-example: "!636q39766251:example.com" + responses: + 200: + description: The current state of the room + examples: + application/json: |- + { + "membership": "join", + "messages": { + "chunk": [ + { + "age": 343513403, + "content": { + "body": "foo", + "msgtype": "m.text" + }, + "event_id": "$14328044851tzTJS:example.com", + "origin_server_ts": 1432804485886, + "room_id": "!636q39766251:example.com", + "type": "m.room.message", + "sender": "@alice:example.com" + }, + { + "age": 343511809, + "content": { + "body": "bar", + "msgtype": "m.text" + }, + "event_id": "$14328044872spjFg:example.com", + "origin_server_ts": 1432804487480, + "room_id": "!636q39766251:example.com", + "type": "m.room.message", + "sender": "@bob:example.com" + } + ], + "end": "s3456_9_0", + "start": "t44-3453_9_0" + }, + "room_id": "!636q39766251:example.com", + "state": [ + { + "age": 7148266897, + "content": { + "join_rule": "public" + }, + "event_id": "$14259997323TLwtb:example.com", + "origin_server_ts": 1425999732392, + "room_id": "!636q39766251:example.com", + "state_key": "", + "type": "m.room.join_rules", + "sender": "@alice:example.com" + }, + { + "age": 6547561012, + "content": { + "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto", + "membership": "join" + }, + "event_id": "$1426600438280zExKY:example.com", + "membership": "join", + "origin_server_ts": 1426600438277, + "room_id": "!636q39766251:example.com", + "state_key": "@alice:example.com", + "type": "m.room.member", + "sender": "@alice:example.com" + }, + { + "age": 7148267200, + "content": { + "creator": "@alice:example.com" + }, + "event_id": "$14259997320KhbwJ:example.com", + "origin_server_ts": 1425999732089, + "room_id": "!636q39766251:example.com", + "state_key": "", + "type": "m.room.create", + "sender": "@alice:example.com" + }, + { + "age": 1622568720, + "content": { + "avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto", + "displayname": "Bob", + "membership": "join" + }, + "event_id": "$1431525430134MxlLX:example.com", + "origin_server_ts": 1431525430569, + "replaces_state": "$142652023736BSXcM:example.com", + "room_id": "!636q39766251:example.com", + "state_key": "@bob:example.com", + "type": "m.room.member", + "sender": "@bob:example.com" + }, + { + "age": 7148267004, + "content": { + "ban": 50, + "events": { + "m.room.name": 100, + "m.room.power_levels": 100 + }, + "events_default": 0, + "kick": 50, + "redact": 50, + "state_default": 50, + "users": { + "@alice:example.com": 100 + }, + "users_default": 0 + }, + "event_id": "$14259997322mqfaq:example.com", + "origin_server_ts": 1425999732285, + "room_id": "!636q39766251:example.com", + "state_key": "", + "type": "m.room.power_levels", + "sender": "@alice:example.com" + } + ], + "visibility": "private", + "account_data": [{ + "type": "m.tag", + "content": {"tags": {"work": {"order": "1"}}} + }] + } + schema: + title: RoomInfo + type: object + properties: + room_id: + type: string + description: "The ID of this room." + membership: + type: string + description: "The user's membership state in this room." + enum: ["invite", "join", "leave", "ban"] + messages: + type: object + title: PaginationChunk + description: "The pagination chunk for this room." + properties: + start: + type: string + description: |- + A token which correlates to the first value in ``chunk``. + Used for pagination. + end: + type: string + description: |- + A token which correlates to the last value in ``chunk``. + Used for pagination. + chunk: + type: array + description: |- + If the user is a member of the room this will be a + list of the most recent messages for this room. If + the user has left the room this will be the + messages that preceeded them leaving. This array + will consist of at most ``limit`` elements. + items: + type: object + title: RoomEvent + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + required: ["start", "end", "chunk"] + state: + type: array + description: |- + If the user is a member of the room this will be the + current state of the room as a list of events. If the + user has left the room this will be the state of the + room when they left it. + items: + title: StateEvent + type: object + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/state_event.yaml" + visibility: + type: string + enum: ["private", "public"] + description: |- + Whether this room is visible to the ``/publicRooms`` API + or not." + account_data: + type: array + description: |- + The private data that this user has attached to this room. + items: + title: Event + type: object + allOf: + - "$ref": "definitions/event-schemas/schema/core-event-schema/event.yaml" + required: ["room_id"] + 403: + description: > + You aren't a member of the room and weren't previously a + member of the room. + tags: + - Room participation + deprecated: true diff --git a/api/client-server/room_send.yaml b/api/client-server/room_send.yaml new file mode 100644 index 00000000000..64dbf4e7540 --- /dev/null +++ b/api/client-server/room_send.yaml @@ -0,0 +1,90 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server message event send API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/send/{eventType}/{txnId}": + put: + summary: Send a message event to the given room. + description: |- + This endpoint is used to send a message event to a room. Message events + allow access to historical events and pagination, making them suited + for "once-off" activity in a room. + + The body of the request should be the content object of the event; the + fields in this object will vary depending on the type of event. See + `Room Events`_ for the m. event specification. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to send the event to. + required: true + x-example: "!636q39766251:example.com" + - in: path + type: string + name: eventType + description: The type of event to send. + required: true + x-example: "m.room.message" + - in: path + name: txnId + type: string + description: |- + The transaction ID for this event. Clients should generate an + ID unique across requests with the same access token; it will be + used by the server to ensure idempotency of requests. + required: true + x-example: "35" + - in: body + name: body + schema: + type: object + example: |- + { + "msgtype": "m.text", + "body": "hello" + } + responses: + 200: + description: "An ID for the sent event." + examples: + application/json: |- + { + "event_id": "YUwRidLecu" + } + schema: + type: object + properties: + event_id: + type: string + description: |- + A unique identifier for the event. + tags: + - Room participation diff --git a/api/client-server/room_state.yaml b/api/client-server/room_state.yaml new file mode 100644 index 00000000000..f6a452f275a --- /dev/null +++ b/api/client-server/room_state.yaml @@ -0,0 +1,148 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server state event send API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/state/{eventType}/{stateKey}": + put: + summary: Send a state event to the given room. + description: | + State events can be sent using this endpoint. These events will be + overwritten if ````, ```` and ```` all + match. + + Requests to this endpoint **cannot use transaction IDs** + like other ``PUT`` paths because they cannot be differentiated from the + ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + + The body of the request should be the content object of the event; the + fields in this object will vary depending on the type of event. See + `Room Events`_ for the ``m.`` event specification. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to set the state in + required: true + x-example: "!636q39766251:example.com" + - in: path + type: string + name: eventType + description: The type of event to send. + required: true + x-example: "m.room.member" + - in: path + type: string + name: stateKey + description: The state_key for the state to send. Defaults to the empty string. + required: true + x-example: "@alice:example.com" + - in: body + name: body + schema: + type: object + example: |- + { + "membership": "join", + "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto", + "displayname": "Alice Margatroid" + } + responses: + 200: + description: "An ID for the sent event." + examples: + application/json: |- + { + "event_id": "YUwRidLecu" + } + schema: + type: object + properties: + event_id: + type: string + description: |- + A unique identifier for the event. + tags: + - Room participation + "/rooms/{roomId}/state/{eventType}": + put: + summary: Send a state event to the given room. + description: | + State events can be sent using this endpoint. This endpoint is + equivalent to calling `/rooms/{roomId}/state/{eventType}/{stateKey}` + with an empty `stateKey`. Previous state events with matching + `` and ``, and empty ``, will be overwritten. + + Requests to this endpoint **cannot use transaction IDs** + like other ``PUT`` paths because they cannot be differentiated from the + ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + + The body of the request should be the content object of the event; the + fields in this object will vary depending on the type of event. See + `Room Events`_ for the ``m.`` event specification. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to set the state in + required: true + x-example: "!636q39766251:example.com" + - in: path + type: string + name: eventType + description: The type of event to send. + required: true + x-example: "m.room.name" + - in: body + name: body + schema: + type: object + example: |- + { + "name": "New name for the room" + } + responses: + 200: + description: "An ID for the sent event." + examples: + application/json: |- + { + "event_id": "YUwRidLecu" + } + schema: + type: object + properties: + event_id: + type: string + description: |- + A unique identifier for the event. + tags: + - Room participation diff --git a/api/client-server/v1/rooms.yaml b/api/client-server/rooms.yaml similarity index 51% rename from api/client-server/v1/rooms.yaml rename to api/client-server/rooms.yaml index 9300d7d10e5..0bb268ddac8 100644 --- a/api/client-server/v1/rooms.yaml +++ b/api/client-server/rooms.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Rooms API" + title: "Matrix Client-Server Rooms API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/rooms/{roomId}/state/{eventType}/{stateKey}": get: @@ -44,7 +53,7 @@ paths: - in: path type: string name: stateKey - description: The key of the state to look up. Defaults to the empty string. + description: The key of the state to look up. required: true x-example: "" responses: @@ -61,7 +70,49 @@ paths: description: > You aren't a member of the room and weren't previously a member of the room. + tags: + - Room participation + "/rooms/{roomId}/state/{eventType}": + get: + summary: Get the state identified by the type, with the empty state key. + description: |- + Looks up the contents of a state event in a room. If the user is + joined to the room then the state is taken from the current + state of the room. If the user has left the room then the state is + taken from the state of the room when they left. + This looks up the state event with the empty state key. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room to look up the state in. + required: true + x-example: "!636q39766251:example.com" + - in: path + type: string + name: eventType + description: The type of state to look up. + required: true + x-example: "m.room.name" + responses: + 200: + description: The content of the state event. + examples: + application/json: |- + {"name": "Example room name"} + schema: + type: object + 404: + description: The room has no state with the given type or key. + 403: + description: > + You aren't a member of the room and weren't previously a + member of the room. + tags: + - Room participation "/rooms/{roomId}/state": get: summary: Get all state events in the current state of a room. @@ -92,13 +143,12 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "", "type": "m.room.join_rules", - "user_id": "@alice:example.com" + "sender": "@alice:example.com" }, { "age": 6547561012, "content": { "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto", - "displayname": null, "membership": "join" }, "event_id": "$1426600438280zExKY:example.com", @@ -107,7 +157,7 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "@alice:example.com", "type": "m.room.member", - "user_id": "@alice:example.com" + "sender": "@alice:example.com" }, { "age": 7148267200, @@ -119,7 +169,7 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "", "type": "m.room.create", - "user_id": "@alice:example.com" + "sender": "@alice:example.com" }, { "age": 1622568720, @@ -134,7 +184,7 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "@bob:example.com", "type": "m.room.member", - "user_id": "@bob:example.com" + "sender": "@bob:example.com" }, { "age": 7148267004, @@ -158,7 +208,7 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "", "type": "m.room.power_levels", - "user_id": "@alice:example.com" + "sender": "@alice:example.com" } ] schema: @@ -173,210 +223,13 @@ paths: title: StateEvent type: object allOf: - - "$ref": "core-event-schema/state_event.json" - 403: - description: > - You aren't a member of the room and weren't previously a - member of the room. - - "/rooms/{roomId}/initialSync": - get: - summary: Snapshot the current state of a room and its most recent messages. - description: |- - Get a copy of the current state and the most recent messages in a room. - security: - - accessToken: [] - parameters: - - in: path - type: string - name: roomId - description: The room to get the data. - required: true - x-example: "!636q39766251:example.com" - responses: - 200: - description: The current state of the room - examples: - application/json: |- - { - "membership": "join", - "messages": { - "chunk": [ - { - "age": 343513403, - "content": { - "body": "foo", - "msgtype": "m.text" - }, - "event_id": "$14328044851tzTJS:example.com", - "origin_server_ts": 1432804485886, - "room_id": "!636q39766251:example.com", - "type": "m.room.message", - "user_id": "@alice:example.com" - }, - { - "age": 343511809, - "content": { - "body": "bar", - "msgtype": "m.text" - }, - "event_id": "$14328044872spjFg:example.com", - "origin_server_ts": 1432804487480, - "room_id": "!636q39766251:example.com", - "type": "m.room.message", - "user_id": "@bob:example.com" - } - ], - "end": "s3456_9_0", - "start": "t44-3453_9_0" - }, - "room_id": "!636q39766251:example.com", - "state": [ - { - "age": 7148266897, - "content": { - "join_rule": "public" - }, - "event_id": "$14259997323TLwtb:example.com", - "origin_server_ts": 1425999732392, - "room_id": "!636q39766251:example.com", - "state_key": "", - "type": "m.room.join_rules", - "user_id": "@alice:example.com" - }, - { - "age": 6547561012, - "content": { - "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto", - "displayname": null, - "membership": "join" - }, - "event_id": "$1426600438280zExKY:example.com", - "membership": "join", - "origin_server_ts": 1426600438277, - "room_id": "!636q39766251:example.com", - "state_key": "@alice:example.com", - "type": "m.room.member", - "user_id": "@alice:example.com" - }, - { - "age": 7148267200, - "content": { - "creator": "@alice:example.com" - }, - "event_id": "$14259997320KhbwJ:example.com", - "origin_server_ts": 1425999732089, - "room_id": "!636q39766251:example.com", - "state_key": "", - "type": "m.room.create", - "user_id": "@alice:example.com" - }, - { - "age": 1622568720, - "content": { - "avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto", - "displayname": "Bob", - "membership": "join" - }, - "event_id": "$1431525430134MxlLX:example.com", - "origin_server_ts": 1431525430569, - "replaces_state": "$142652023736BSXcM:example.com", - "room_id": "!636q39766251:example.com", - "state_key": "@bob:example.com", - "type": "m.room.member", - "user_id": "@bob:example.com" - }, - { - "age": 7148267004, - "content": { - "ban": 50, - "events": { - "m.room.name": 100, - "m.room.power_levels": 100 - }, - "events_default": 0, - "kick": 50, - "redact": 50, - "state_default": 50, - "users": { - "@alice:example.com": 100 - }, - "users_default": 0 - }, - "event_id": "$14259997322mqfaq:example.com", - "origin_server_ts": 1425999732285, - "room_id": "!636q39766251:example.com", - "state_key": "", - "type": "m.room.power_levels", - "user_id": "@alice:example.com" - } - ], - "visibility": "private" - } - schema: - title: RoomInfo - type: object - properties: - room_id: - type: string - description: "The ID of this room." - membership: - type: string - description: "The user's membership state in this room." - enum: ["invite", "join", "leave", "ban"] - messages: - type: object - title: PaginationChunk - description: "The pagination chunk for this room." - properties: - start: - type: string - description: |- - A token which correlates to the first value in ``chunk``. - Used for pagination. - end: - type: string - description: |- - A token which correlates to the last value in ``chunk``. - Used for pagination. - chunk: - type: array - description: |- - If the user is a member of the room this will be a - list of the most recent messages for this room. If - the user has left the room this will be the - messages that preceeded them leaving. This array - will consist of at most ``limit`` elements. - items: - type: object - title: RoomEvent - allOf: - - "$ref": "core-event-schema/room_event.json" - required: ["start", "end", "chunk"] - state: - type: array - description: |- - If the user is a member of the room this will be the - current state of the room as a list of events. If the - user has left the room this will be the state of the - room when they left it. - items: - title: StateEvent - type: object - allOf: - - "$ref": "core-event-schema/state_event.json" - visibility: - type: string - enum: ["private", "public"] - description: |- - Whether this room is visible to the ``/publicRooms`` API - or not." - required: ["room_id", "membership"] + - "$ref": "definitions/event-schemas/schema/core-event-schema/state_event.yaml" 403: description: > You aren't a member of the room and weren't previously a member of the room. - + tags: + - Room participation "/rooms/{roomId}/members": get: summary: Get the m.room.member events for the room. @@ -393,7 +246,7 @@ paths: 200: description: |- A list of members of the room. If you are joined to the room then - this will be the current members of the room. If you have left te + this will be the current members of the room. If you have left the room then this will be the members of the room when you left. examples: application/json: |- @@ -403,7 +256,6 @@ paths: "age": 6547561012, "content": { "avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto", - "displayname": null, "membership": "join" }, "event_id": "$1426600438280zExKY:example.com", @@ -412,7 +264,7 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "@alice:example.com", "type": "m.room.member", - "user_id": "@alice:example.com" + "sender": "@alice:example.com" }, { "age": 1622568720, @@ -427,7 +279,7 @@ paths: "room_id": "!636q39766251:example.com", "state_key": "@bob:example.com", "type": "m.room.member", - "user_id": "@bob:example.com" + "sender": "@bob:example.com" } ] } @@ -440,9 +292,10 @@ paths: title: MemberEvent type: object allOf: - - "$ref": "v1-event-schema/m.room.member" + - "$ref": "definitions/event-schemas/schema/m.room.member" 403: description: > You aren't a member of the room and weren't previously a member of the room. - + tags: + - Room participation diff --git a/api/client-server/search.yaml b/api/client-server/search.yaml new file mode 100644 index 00000000000..a1c7b9fea8a --- /dev/null +++ b/api/client-server/search.yaml @@ -0,0 +1,344 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Search API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/search": + post: + summary: Perform a server-side search. + description: |- + Performs a full text search across different categories. + security: + - accessToken: [] + parameters: + - in: query + name: next_batch + type: string + description: |- + The point to return events from. If given, this should be a + `next_batch` result from a previous call to this endpoint. + x-example: "YWxsCgpOb25lLDM1ODcwOA" + - in: body + name: body + schema: + type: object + example: |- + { + "search_categories": { + "room_events": { + "keys": [ + "content.body" + ], + "search_term": "martians and men" + } + }, + "order_by": "recent", + "groupings": { + "group_by": [ + { + "key": "room_id" + } + ] + } + } + properties: + search_categories: + type: object + title: "Categories" + description: Describes which categories to search in and + their criteria. + properties: + room_events: + type: object + title: "Room Events" + description: Mapping of category name to search criteria. + properties: + search_term: + type: string + description: The string to search events for + keys: + type: array + items: + type: string + enum: ["content.body", "content.name", "content.topic"] + description: The keys to search. Defaults to all. + filter: + type: object + title: Filter + description: |- + This takes a `filter `_. + order_by: + title: "Ordering" + type: string + enum: ["recent", "rank"] + description: "The order in which to search for results." + event_context: + title: "Event Context" + type: object + description: |- + Configures whether any context for the events + returned are included in the response. + properties: + before_limit: + type: integer + title: "Before limit" + description: |- + How many events before the result are + returned. + after_limit: + type: integer + title: "After limit" + description: |- + How many events after the result are + returned. + include_profile: + type: boolean + title: "Return profile information" + description: |- + Requests that the server returns the + historic profile information for the users + that sent the events that were returned. + include_state: + type: boolean + title: Include current state + description: |- + Requests the server return the current state for + each room returned. + groupings: + type: object + title: Groupings + description: |- + Requests that the server partitions the result set + based on the provided list of keys. + properties: + group_by: + type: array + title: Groups + description: List of groups to request. + items: + type: object + title: Group + description: Configuration for group. + properties: + key: + type: string + title: Group Key + description: |- + Key that defines the group. + enum: ["room_id", "sender"] + required: ["search_term"] + required: ["search_categories"] + responses: + 200: + description: Results of the search. + schema: + type: object + title: Results + required: ["search_categories"] + properties: + search_categories: + type: object + title: Categories + description: Describes which categories to search in and + their criteria. + properties: + room_events: + type: object + title: Room Event Results + description: Mapping of category name to search criteria. + properties: + count: + type: number + description: An approximate count of the total number of results found. + results: + type: array + title: Results + description: List of results in the requested order. + items: + type: object + title: Result + description: The result object. + properties: + rank: + type: number + description: A number that describes how closely + this result matches the search. Higher is + closer. + result: + type: object + title: Event + description: The event that matched. + "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + context: + type: object + title: Event Context + description: Context for result, if requested. + properties: + start: + type: string + title: Start Token + description: |- + Pagination token for the start of the chunk + end: + type: string + title: End Token + description: |- + Pagination token for the end of the chunk + profile_info: + type: object + title: Profile Information + description: |- + The historic profile information of the + users that sent the events returned. + additionalProperties: + type: object + title: User Profile + properties: + displayname: + type: string + title: Display name + avatar_url: + type: string + title: Avatar Url + events_before: + type: array + title: Events Before + description: Events just before the result. + items: + title: Event + type: object + "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + events_after: + type: array + title: Events After + description: Events just after the result. + items: + title: Event + type: object + "$ref": "definitions/event-schemas/schema/core-event-schema/room_event.yaml" + state: + type: object + title: Current state + description: |- + The current state for every room in the results. + This is included if the request had the + ``include_state`` key set with a value of ``true``. + additionalProperties: + type: array + title: Room State + items: + "$ref": "definitions/event-schemas/schema/core-event-schema/state_event.yaml" + groups: + type: object + title: Groups + description: Any groups that were requested. + additionalProperties: + type: object + title: Group Key + description: The results for a given group. + additionalProperties: + type: object + title: Group Value + description: |- + The results for a particular group value. + properties: + next_batch: + type: string + title: Next Batch in Group + description: |- + Token that can be used to get the next batch + of results in the group, by passing as the + `next_batch` parameter to the next call. If + this field is absent, there are no more + results in this group. + order: + type: integer + title: Group Order + description: |- + Key that can be used to order different + groups. + results: + type: array + description: |- + Which results are in this group. + items: + type: string + title: Result Event ID + next_batch: + type: string + title: Next Batch + description: |- + Token that can be used to get the next batch of + results, by passing as the `next_batch` parameter to + the next call. If this field is absent, there are no + more results. + examples: + application/json: |- + { + "search_categories": { + "room_events": { + "groups": { + "room_id": { + "!qPewotXpIctQySfjSy:localhost": { + "order": 1, + "next_batch": "BdgFsdfHSf-dsFD", + "results": [ + "$144429830826TWwbB:localhost" + ] + } + } + }, + "next_batch": "5FdgFsd234dfgsdfFD", + "count": 1224, + "results": [ + { + "rank": 0.00424866, + "result": { + "age": 526228296, + "content": { + "body": "Test content martians and men", + "msgtype": "m.text" + }, + "event_id": "$144429830826TWwbB:localhost", + "origin_server_ts": 1444298308034, + "room_id": "!qPewotXpIctQySfjSy:localhost", + "type": "m.room.message", + "sender": "@test:localhost" + } + } + ] + } + } + } + 400: + description: Part of the request was invalid. + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Search diff --git a/api/client-server/v2_alpha/sync.yaml b/api/client-server/sync.yaml similarity index 50% rename from api/client-server/v2_alpha/sync.yaml rename to api/client-server/sync.yaml index 266f27bc4c7..be2a82174eb 100644 --- a/api/client-server/v2_alpha/sync.yaml +++ b/api/client-server/sync.yaml @@ -1,28 +1,37 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v2 sync API" + title: "Matrix Client-Server sync API" version: "1.0.0" host: localhost:8008 schemes: - https -basePath: /_matrix/client/v2_alpha +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/sync": get: summary: Synchronise the client's state and receive new messages. description: |- Synchronise the client's state with the latest state on the server. - Client's use this API when they first log in to get an initial snapshot + Clients use this API when they first log in to get an initial snapshot of the state on the server, and then continue to call this API to get incremental deltas to the state, and to receive new messages. security: @@ -32,7 +41,13 @@ paths: name: filter type: string description: |- - The ID of a filter created using the filter API. + The ID of a filter created using the filter API or a filter JSON + object encoded as a string. The server will detect whether it is + an ID or a JSON object by whether the first character is a ``"{"`` + open brace. Passing the JSON inline is best suited to one off + requests. Creating a filter using the filter API is recommended for + clients that reuse the same filter multiple times, for example in + long poll requests. x-example: "66696p746572" - in: query name: since @@ -40,6 +55,24 @@ paths: description: |- A point in time to continue a sync from. x-example: "s72594_4483_1934" + - in: query + name: full_state + type: boolean + description: |- + Controls whether to include the full state for all rooms the user + is a member of. + + If this is set to ``true``, then all state events will be returned, + even if ``since`` is non-empty. The timeline will still be limited + by the ``since`` parameter. In this case, the ``timeout`` parameter + will be ignored and the query will return immediately, possibly with + an empty timeline. + + If ``false``, and ``since`` is non-empty, only state which has + changed since the point indicated by ``since`` will be returned. + + By default, this is ``false``. + x-example: "false" - in: query name: set_presence type: string @@ -55,8 +88,12 @@ paths: name: timeout type: integer description: |- - The maximum time to poll in milliseconds before returning this - request. + The maximum time to wait, in milliseconds, before returning this + request. If no events (or other data) become available before this + time elapses, the server will return a response with empty fields. + + By default, this is ``0``, so the server will return immediately + even if the response is empty. x-example: 30000 responses: 200: @@ -77,33 +114,26 @@ paths: description: |- Updates to rooms. properties: - joined: - title: Joined + join: + title: Joined Rooms type: object + description: |- + The rooms that the user has joined. additionalProperties: title: Joined Room type: object properties: - event_map: - title: EventMap - type: object - description: |- - A map from event ID to events for this room. The - events are referenced from the ``timeline`` and - ``state`` keys for this room. - additionalProperties: - title: Event - description: An event object. - type: object - allOf: - - $ref: "core-event-schema/event.json" state: title: State type: object description: |- - The state updates for the room. + Updates to the state, between the time indicated by + the ``since`` parameter, and the start of the + ``timeline`` (or all state up to the start of the + ``timeline``, if ``since`` is not given, or + ``full_state`` is true). allOf: - - $ref: "definitions/room_event_batch.json" + - $ref: "definitions/event_batch.yaml" timeline: title: Timeline type: object @@ -111,7 +141,7 @@ paths: The timeline of messages and state changes in the room. allOf: - - $ref: "definitions/timeline_batch.json" + - $ref: "definitions/timeline_batch.yaml" ephemeral: title: Ephemeral type: object @@ -120,9 +150,33 @@ paths: recorded in the timeline or state of the room. e.g. typing. allOf: - - $ref: "definitions/event_batch.json" - invited: - title: Invited + - $ref: "definitions/event_batch.yaml" + account_data: + title: Account Data + type: object + description: |- + The private data that this user has attached to + this room. + allOf: + - $ref: "definitions/event_batch.yaml" + "unread_notifications": + title: Unread Notification Counts + type: object + description: |- + Counts of unread notifications for this room + properties: + highlight_count: + title: Highlighted notification count + type: integer + description: The number of unread notifications + for this room with the highlight flag set + notification_count: + title: Total notification count + type: integer + description: The total number of unread notifications + for this room + invite: + title: Invited Rooms type: object description: |- The rooms that the user has been invited to. @@ -135,7 +189,7 @@ paths: type: object description: |- The state of a room that the user has been invited - to. These state events may only have the `sender``, + to. These state events may only have the ``sender``, ``type``, ``state_key`` and ``content`` keys present. These events do not replace any state that the client already has for the room, for example if @@ -147,38 +201,23 @@ paths: delta against the archived ``state`` not the ``invite_state``. allOf: - - $ref: "definitions/event_batch.json" - archived: - title: Archived + - $ref: "definitions/event_batch.yaml" + leave: + title: Left rooms type: object description: |- - The rooms that the user has left or been banned from. The - entries in the room_map will lack an ``ephemeral`` key. + The rooms that the user has left or been banned from. additionalProperties: - title: Archived Room + title: Left Room type: object properties: - event_map: - title: EventMap - type: object - description: |- - A map from event ID to events for this room. The - events are referenced from the ``timeline`` and - ``state`` keys for this room. - additionalProperties: - title: Event - description: An event object. - type: object - allOf: - - $ref: "core-event-schema/event.json" state: title: State type: object description: |- - The state updates for the room up to the point when - the user left. + The state updates for the room up to the start of the timeline. allOf: - - $ref: "definitions/room_event_batch.json" + - $ref: "definitions/event_batch.yaml" timeline: title: Timeline type: object @@ -186,14 +225,33 @@ paths: The timeline of messages and state changes in the room up to the point when the user left. allOf: - - $ref: "definitions/timeline_batch.json" + - $ref: "definitions/timeline_batch.yaml" presence: title: Presence type: object description: |- The updates to the presence status of other users. allOf: - - $ref: "definitions/event_batch.json" + - $ref: "definitions/event_batch.yaml" + account_data: + title: Account Data + type: object + description: |- + The global private data created by this user. + allOf: + - $ref: "definitions/event_batch.yaml" + to_device: + title: ToDevice + type: object + description: |- + Information on the send-to-device messages for the client + device, as defined in |send_to_device_sync|_. + device_lists: + title: DeviceLists + type: object + description: |- + Information on end-to-end device updates, as specified in + |device_lists_sync|_. examples: application/json: |- { @@ -207,45 +265,54 @@ paths: } ] }, + "account_data": { + "events": [ + { + "type": "org.example.custom.config", + "content": { + "custom_config_key": "custom_config_value" + } + } + ] + }, "rooms": { - "joined": { + "join": { "!726s6s6q:example.com": { - "event_map": { - "$66697273743031:example.com": { - "sender": "@alice:example.com", - "type": "m.room.member", - "state_key": "@alice:example.com", - "content": {"membership": "join"}, - "origin_server_ts": 1417731086795 - }, - "$7365636s6r6432:example.com": { - "sender": "@bob:example.com", - "type": "m.room.member", - "state_key": "@bob:example.com", - "content": {"membership": "join"}, - "origin_server_ts": 1417731086795 - }, - "$74686972643033:example.com": { - "sender": "@alice:example.com", - "type": "m.room.message", - "unsigned": {"age": "124524", "txn_id": "1234"}, - "content": { - "body": "I am a fish", - "msgtype": "m.text" - }, - "origin_server_ts": 1417731086797 - } - }, "state": { "events": [ - "$66697273743031:example.com", - "$7365636s6r6432:example.com" + { + "sender": "@alice:example.com", + "type": "m.room.member", + "state_key": "@alice:example.com", + "content": {"membership": "join"}, + "origin_server_ts": 1417731086795, + "event_id": "$66697273743031:example.com" + } ] }, "timeline": { "events": [ - "$7365636s6r6432:example.com", - "$74686972643033:example.com" + { + "sender": "@bob:example.com", + "type": "m.room.member", + "state_key": "@bob:example.com", + "content": {"membership": "join"}, + "prev_content": {"membership": "invite"}, + "origin_server_ts": 1417731086795, + "event_id": "$7365636s6r6432:example.com" + }, + { + "sender": "@alice:example.com", + "type": "m.room.message", + "age": 124524, + "txn_id": "1234", + "content": { + "body": "I am a fish", + "msgtype": "m.text" + }, + "origin_server_ts": 1417731086797, + "event_id": "$74686972643033:example.com" + } ], "limited": true, "prev_batch": "t34-23535_0_0" @@ -253,15 +320,28 @@ paths: "ephemeral": { "events": [ { - "room_id": "!726s6s6q:example.com", "type": "m.typing", "content": {"user_ids": ["@alice:example.com"]} } ] + }, + "account_data": { + "events": [ + { + "type": "m.tag", + "content": {"tags": {"work": {"order": 1}}} + }, + { + "type": "org.example.custom.room.config", + "content": { + "custom_config_key": "custom_config_value" + } + } + ] } } }, - "invited": { + "invite": { "!696r7674:example.com": { "invite_state": { "events": [ @@ -281,6 +361,8 @@ paths: } } }, - "archived": {} + "leave": {} } } + tags: + - Room participation diff --git a/api/client-server/tags.yaml b/api/client-server/tags.yaml new file mode 100644 index 00000000000..7add84799b7 --- /dev/null +++ b/api/client-server/tags.yaml @@ -0,0 +1,162 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server tag API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/user/{userId}/rooms/{roomId}/tags": + get: + summary: List the tags for a room. + description: |- + List the tags set by a user on a room. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: userId + required: true + description: |- + The id of the user to get tags for. The access token must be + authorized to make requests for this user id. + x-example: "@alice:example.com" + - in: path + type: string + name: roomId + required: true + description: |- + The id of the room to get tags for. + x-example: "!726s6s6q:example.com" + responses: + 200: + description: + The list of tags for the user for the room. + schema: + type: object + properties: + tags: + title: Tags + type: object + examples: + application/json: |- + { + "tags": { + "work": {"order": "1"}, + "pinned": {} + } + } + tags: + - User data + "/user/{userId}/rooms/{roomId}/tags/{tag}": + put: + summary: Add a tag to a room. + description: |- + Add a tag to the room. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: userId + required: true + description: |- + The id of the user to add a tag for. The access token must be + authorized to make requests for this user id. + x-example: "@alice:example.com" + - in: path + type: string + name: roomId + required: true + description: |- + The id of the room to add a tag to. + x-example: "!726s6s6q:example.com" + - in: path + type: string + name: tag + required: true + description: |- + The tag to add. + x-example: "work" + - in: body + name: body + required: true + description: |- + Extra data for the tag, e.g. ordering. + schema: + type: object + example: |- + {"order": "1"} + responses: + 200: + description: + The tag was successfully added. + schema: + type: object + examples: + application/json: |- + {} + tags: + - User data + delete: + summary: Remove a tag from the room. + description: |- + Remove a tag from the room. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: userId + required: true + description: |- + The id of the user to remove a tag for. The access token must be + authorized to make requests for this user id. + x-example: "@alice:example.com" + - in: path + type: string + name: roomId + required: true + description: |- + The id of the room to remove a tag from. + x-example: "!726s6s6q:example.com" + - in: path + type: string + name: tag + required: true + description: |- + The tag to remove. + x-example: "work" + responses: + 200: + description: + The tag was successfully removed + schema: + type: object + examples: + application/json: |- + {} + tags: + - User data diff --git a/api/client-server/third_party_membership.yaml b/api/client-server/third_party_membership.yaml new file mode 100644 index 00000000000..a2e403728a4 --- /dev/null +++ b/api/client-server/third_party_membership.yaml @@ -0,0 +1,134 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Room Membership API for third party identifiers" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/rooms/{roomId}/invite": + post: + summary: Invite a user to participate in a particular room. + description: |- + .. _invite-by-third-party-id-endpoint: + + *Note that there are two forms of this API, which are documented separately. + This version of the API does not require that the inviter know the Matrix + identifier of the invitee, and instead relies on third party identifiers. + The homeserver uses an identity server to perform the mapping from + third party identifier to a Matrix identifier. The other is documented in the* + `joining rooms section`_. + + This API invites a user to participate in a particular room. + They do not start participating in the room until they actually join the + room. + + Only users currently in a particular room can invite other users to + join that room. + + If the identity server did know the Matrix user identifier for the + third party identifier, the homeserver will append a ``m.room.member`` + event to the room. + + If the identity server does not know a Matrix user identifier for the + passed third party identifier, the homeserver will issue an invitation + which can be accepted upon providing proof of ownership of the third + party identifier. This is achieved by the identity server generating a + token, which it gives to the inviting homeserver. The homeserver will + add an ``m.room.third_party_invite`` event into the graph for the room, + containing that token. + + When the invitee binds the invited third party identifier to a Matrix + user ID, the identity server will give the user a list of pending + invitations, each containing: + + - The room ID to which they were invited + + - The token given to the homeserver + + - A signature of the token, signed with the identity server's private key + + - The matrix user ID who invited them to the room + + If a token is requested from the identity server, the homeserver will + append a ``m.room.third_party_invite`` event to the room. + + .. _joining rooms section: `invite-by-user-id-endpoint`_ + security: + - accessToken: [] + parameters: + - in: path + type: string + name: roomId + description: The room identifier (not alias) to which to invite the user. + required: true + x-example: "!d41d8cd:matrix.org" + - in: body + name: body + required: true + schema: + type: object + example: |- + { + "id_server": "matrix.org", + "medium": "email", + "address": "cheeky@monkey.com" + } + properties: + id_server: + type: string + description: The hostname+port of the identity server which should be used for third party identifier lookups. + medium: + type: string + # TODO: Link to identity service spec when it eixsts + description: The kind of address being passed in the address field, for example ``email``. + address: + type: string + description: The invitee's third party identifier. + required: ["id_server", "medium", "address"] + responses: + 200: + description: The user has been invited to join the room. + examples: + application/json: |- + {} + schema: + type: object + 403: + description: |- + You do not have permission to invite the user to the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: + + - The invitee has been banned from the room. + - The invitee is already a member of the room. + - The inviter is not currently in the room. + - The inviter's power level is insufficient to invite users to the room. + examples: + application/json: |- + {"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"} + 429: + description: This request was rate-limited. + schema: + "$ref": "definitions/error.yaml" + tags: + - Room membership diff --git a/api/client-server/to_device.yaml b/api/client-server/to_device.yaml new file mode 100644 index 00000000000..16af1182437 --- /dev/null +++ b/api/client-server/to_device.yaml @@ -0,0 +1,89 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +swagger: '2.0' +info: + title: "Matrix Client-Server Send-to-device API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/sendToDevice/{eventType}/{txnId}": + put: + summary: Send an event to a given set of devices. + description: |- + This endpoint is used to send send-to-device events to a set of + client devices. + security: + - accessToken: [] + parameters: + - in: path + type: string + name: eventType + description: The type of event to send. + required: true + x-example: "m.new_device" + - in: path + name: txnId + type: string + description: |- + The transaction ID for this event. Clients should generate an + ID unique across requests with the same access token; it will be + used by the server to ensure idempotency of requests. + required: true + x-example: "35" + - in: body + name: body + required: true + schema: + type: object + title: body + properties: + messages: + type: object + description: |- + The messages to send. A map from user ID, to a map from + device ID to message body. The device ID may also be `*`, + meaning all known devices for the user. + additionalProperties: + type: object + additionalProperties: + type: object + title: EventContent + description: Message content + example: { + "@alice:example.com": { + "TLLBEANAAG": { + "example_content_key": "value" + } + } + } + responses: + 200: + description: + The message was successfully sent. + examples: + application/json: |- + {} + tags: + - Send-to-Device messaging diff --git a/api/client-server/v1/typing.yaml b/api/client-server/typing.yaml similarity index 73% rename from api/client-server/v1/typing.yaml rename to api/client-server/typing.yaml index 737c69284b5..fea941082b9 100644 --- a/api/client-server/v1/typing.yaml +++ b/api/client-server/typing.yaml @@ -1,22 +1,31 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Typing API" + title: "Matrix Client-Server Typing API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: "/rooms/{roomId}/typing/{userId}": put: @@ -74,4 +83,5 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" - + tags: + - Room participation diff --git a/api/client-server/v1/core-event-schema b/api/client-server/v1/core-event-schema deleted file mode 120000 index 045aecb02ec..00000000000 --- a/api/client-server/v1/core-event-schema +++ /dev/null @@ -1 +0,0 @@ -v1-event-schema/core-event-schema \ No newline at end of file diff --git a/api/client-server/v1/definitions/error.yaml b/api/client-server/v1/definitions/error.yaml deleted file mode 100644 index 20312ae4d6f..00000000000 --- a/api/client-server/v1/definitions/error.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: object -description: A Matrix-level Error -properties: - errcode: - type: string - description: An error code. - error: - type: string - description: A human-readable error message. -required: ["errcode"] \ No newline at end of file diff --git a/api/client-server/v1/definitions/push_condition.json b/api/client-server/v1/definitions/push_condition.json deleted file mode 100644 index 1d84955cedb..00000000000 --- a/api/client-server/v1/definitions/push_condition.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": ["event_match", "profile_tag", "contains_display_name", "room_member_count"] - } - } -} \ No newline at end of file diff --git a/api/client-server/v1/definitions/push_rule.json b/api/client-server/v1/definitions/push_rule.json deleted file mode 100644 index 4df93f67b39..00000000000 --- a/api/client-server/v1/definitions/push_rule.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "object", - "properties": { - "default": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "rule_id": { - "type": "string" - }, - "actions": { - "items": { - "type": ["object", "string"] - }, - "type": "array" - } - } -} \ No newline at end of file diff --git a/api/client-server/v1/definitions/push_ruleset.json b/api/client-server/v1/definitions/push_ruleset.json deleted file mode 100644 index e03727012aa..00000000000 --- a/api/client-server/v1/definitions/push_ruleset.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "type": "object", - "properties": { - "content": { - "items": { - "type": "object", - "allOf": [ - { - "$ref": "push_rule.json" - } - ] - }, - "type": "array" - }, - "override": { - "items": { - "type": "object", - "allOf": [ - { - "$ref": "push_rule.json" - } - ] - }, - "type": "array" - }, - "sender": { - "items": { - "type": "object", - "allOf": [ - { - "$ref": "push_rule.json" - } - ] - }, - "type": "array" - }, - "underride": { - "items": { - "type": "object", - "allOf": [ - { - "$ref": "push_rule.json" - } - ] - }, - "type": "array" - }, - "room": { - "items": { - "type": "object", - "allOf": [ - { - "$ref": "push_rule.json" - } - ] - }, - "type": "array" - } - } -} \ No newline at end of file diff --git a/api/client-server/v1/directory.yaml b/api/client-server/v1/directory.yaml deleted file mode 100644 index c70b9f6ba5f..00000000000 --- a/api/client-server/v1/directory.yaml +++ /dev/null @@ -1,87 +0,0 @@ -swagger: '2.0' -info: - title: "Matrix Client-Server v1 Directory API" - version: "1.0.0" -host: localhost:8008 -schemes: - - https - - http -basePath: /_matrix/client/api/v1/directory -consumes: - - application/json -produces: - - application/json -securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query -paths: - "/room/{roomAlias}": - put: - summary: Create a new mapping from room alias to room ID. - security: - - accessToken: [] - parameters: - - in: path - type: string - name: roomAlias - description: The room alias to set. - required: true - - in: body - name: roomInfo - description: Information about this room alias. - required: true - schema: - type: object - properties: - room_id: - type: string - description: The room ID to set. - responses: - 200: - description: The mapping was created. - schema: - type: object # empty json object - get: - summary: Get the room ID corresponding to this room alias. - parameters: - - in: path - type: string - name: roomAlias - description: The room alias. - required: true - responses: - 200: - description: The room ID and other information for this alias. - schema: - type: object - properties: - room_id: - type: string - description: The room ID for this room alias. - servers: - type: array - description: A list of servers that are aware of this room ID. - items: - type: string - description: A server which is aware of this room ID. - 404: - description: There is no mapped room ID for this room alias. - delete: - summary: Remove a mapping of room alias to room ID. - security: - - accessToken: [] - parameters: - - in: path - type: string - name: roomAlias - description: The room alias to remove. - required: true - responses: - 200: - description: The mapping was removed. - schema: - type: object # empty json object - diff --git a/api/client-server/v1/login.yaml b/api/client-server/v1/login.yaml deleted file mode 100644 index 3d415c2930a..00000000000 --- a/api/client-server/v1/login.yaml +++ /dev/null @@ -1,147 +0,0 @@ -swagger: '2.0' -info: - title: "Matrix Client-Server v1 Registration and Login API" - version: "1.0.0" -host: localhost:8008 -schemes: - - https - - http -basePath: /_matrix/client/api/v1 -consumes: - - application/json -produces: - - application/json -securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query -paths: - "/login": - post: - summary: Authenticates the user. - description: |- - Authenticates the user by password, and issues an access token they can - use to authorize themself in subsequent requests. - security: - - accessToken: [] - parameters: - - in: body - name: body - schema: - type: object - example: |- - { - "username": "cheeky_monkey", - "password": "ilovebananas" - } - properties: - username: - type: string - description: The fully qualified user ID or just local part of the user ID, to log in. - password: - type: string - description: The user's password. - required: ["username", "password"] - responses: - 200: - description: The user has been authenticated. - examples: - application/json: |- - { - "user_id": "@cheeky_monkey:matrix.org", - "access_token": "abc123", - "home_server": "matrix.org" - } - schema: - type: object - properties: - user_id: - type: string - description: The fully-qualified Matrix ID that has been registered. - access_token: - type: string - description: |- - An access token for the account. - This access token can then be used to authorize other requests. - The access token may expire at some point, and if so, it SHOULD come with a ``refresh_token``. - There is no specific error message to indicate that a request has failed because - an access token has expired; instead, if a client has reason to believe its - access token is valid, and it receives an auth error, they should attempt to - refresh for a new token on failure, and retry the request with the new token. - refresh_token: - type: string - # TODO: Work out how to linkify /tokenrefresh - description: |- - (optional) A ``refresh_token`` may be exchanged for a new ``access_token`` using the /tokenrefresh API endpoint. - home_server: - type: string - description: The hostname of the Home Server on which the account has been registered. - 403: - description: |- - The login attempt failed. For example, the password may have been incorrect. - examples: - application/json: |- - {"errcode": "M_FORBIDDEN"} - 429: - description: This request was rate-limited. - schema: - "$ref": "definitions/error.yaml" - "/tokenrefresh": - post: - summary: Exchanges a refresh token for an access token. - description: |- - Exchanges a refresh token for a new access token. - This is intended to be used if the access token has expired. - security: - - accessToken: [] - parameters: - - in: body - name: body - schema: - type: object - example: |- - { - "refresh_token": "a1b2c3" - } - properties: - refresh_token: - type: string - description: The refresh token which was issued by the server. - required: ["refresh_token"] - responses: - 200: - description: |- - The refresh token was accepted, and a new access token has been issued. - The passed refresh token is no longer valid and cannot be used. - A new refresh token will have been returned unless some policy does - not allow the user to continue to renew their session. - examples: - application/json: |- - { - "access_token": "bearwithme123", - "refresh_token": "exchangewithme987" - } - schema: - type: object - properties: - access_token: - type: string - description: |- - An access token for the account. - This access token can then be used to authorize other requests. - The access token may expire at some point, and if so, it SHOULD come with a ``refresh_token``. - refresh_token: - type: string - description: (optional) A ``refresh_token`` may be exchanged for a new ``access_token`` using the TODO Linkify /tokenrefresh API endpoint. - 403: - description: |- - The exchange attempt failed. For example, the refresh token may have already been used. - examples: - application/json: |- - {"errcode": "M_FORBIDDEN"} - 429: - description: This request was rate-limited. - schema: - "$ref": "definitions/error.yaml" diff --git a/api/client-server/v1/membership.yaml b/api/client-server/v1/membership.yaml deleted file mode 100644 index efc82578601..00000000000 --- a/api/client-server/v1/membership.yaml +++ /dev/null @@ -1,234 +0,0 @@ -swagger: '2.0' -info: - title: "Matrix Client-Server v1 Room Membership API" - version: "1.0.0" -host: localhost:8008 -schemes: - - https - - http -basePath: /_matrix/client/api/v1 -consumes: - - application/json -produces: - - application/json -securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query -paths: - "/rooms/{roomId}/join": - post: - summary: Start the requesting user participating in a particular room. - description: |- - This API starts a user participating in a particular room, if that user - is allowed to participate in that room. After this call, the client is - allowed to see all current state events in the room, and all subsequent - events associated with the room until the user leaves the room. - - After a user has joined a room, the room will appear as an entry in the - response of the |initialSync| API. - security: - - accessToken: [] - parameters: - - in: path - type: string - name: roomId - description: The room identifier or room alias to join. - required: true - x-example: "#monkeys:matrix.org" - responses: - 200: - description: |- - The room has been joined. - - The joined room ID must be returned in the ``room_id`` field. - examples: - application/json: |- - {"room_id": "!d41d8cd:matrix.org"} - schema: - type: object - 403: - description: |- - You do not have permission to join the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejection are: - - The room is invite-only and the user was not invited. - - The user has been banned from the room. - examples: - application/json: |- - {"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."} - 429: - description: This request was rate-limited. - schema: - "$ref": "definitions/error.yaml" - x-alias: - canonical-link: "post-matrix-client-api-v1-rooms-roomid-join" - aliases: - - /join/{roomId} - - # With an extra " " to disambiguate from the 3pid invite endpoint - # The extra space makes it sort first for what I'm sure is a good reason. - "/rooms/{roomId}/invite ": - post: - summary: Invite a user to participate in a particular room. - description: |- - *Note that there are two forms of this API, which are documented separately. - This version of the API requires that the inviter knows the Matrix - identifier of the invitee.* - - This API invites a user to participate in a particular room. - They do not start participating in the room until they actually join the - room. - - Only users currently in a particular room can invite other users to - join that room. - - If the user was invited to the room, the home server will append a - ``m.room.member`` event to the room. - security: - - accessToken: [] - parameters: - - in: path - type: string - name: roomId - description: The room identifier (not alias) to which to invite the user. - required: true - x-example: "!d41d8cd:matrix.org" - - in: body - name: body - required: true - schema: - type: object - example: |- - { - "user_id": "@cheeky_monkey:matrix.org" - } - properties: - user_id: - type: string - description: The fully qualified user ID of the invitee. - required: ["user_id"] - responses: - 200: - description: The user has been invited to join the room. - examples: - application/json: |- - {} - schema: - type: object - 403: - description: |- - You do not have permission to invite the user to the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: - - The invitee has been banned from the room. - - The invitee is already a member of the room. - - The inviter is not currently in the room. - - The inviter's power level is insufficient to invite users to the room. - examples: - application/json: |- - {"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"} - 429: - description: This request was rate-limited. - schema: - "$ref": "definitions/error.yaml" - - "/rooms/{roomId}/invite": - post: - summary: Invite a user to participate in a particular room. - description: |- - *Note that there are two forms of this API, which are documented separately. - This version of the API does not require that the inviter know the Matrix - identifier of the invitee, and instead relies on third party identifiers. - The homeserver uses an identity server to perform the mapping from - third party identifier to a Matrix identifier.* - - This API invites a user to participate in a particular room. - They do not start participating in the room until they actually join the - room. - - Only users currently in a particular room can invite other users to - join that room. - - If the identity server did know the Matrix user identifier for the - third party identifier, the home server will append a ``m.room.member`` - event to the room. - - If the identity server does not know a Matrix user identifier for the - passed third party identifier, the homeserver will issue an invitation - which can be accepted upon providing proof of ownership of the third - party identifier. This is achieved by the identity server generating a - token, which it gives to the inviting homeserver. The homeserver will - add an ``m.room.third_party_invite`` event into the graph for the room, - containing that token. - - When the invitee binds the invited third party identifier to a Matrix - user ID, the identity server will give the user a list of pending - invitations, each containing: - - - The room ID to which they were invited - - - The token given to the homeserver - - - A signature of the token, signed with the identity server's private key - - - The matrix user ID who invited them to the room - - If a token is requested from the identity server, the home server will - append a ``m.room.third_party_invite`` event to the room. - security: - - accessToken: [] - parameters: - - in: path - type: string - name: roomId - description: The room identifier (not alias) to which to invite the user. - required: true - x-example: "!d41d8cd:matrix.org" - - in: body - name: body - required: true - schema: - type: object - example: |- - { - "id_server": "matrix.org", - "medium": "email", - "address": "cheeky@monkey.com", - "display_name": "A very cheeky monkey" - } - properties: - id_server: - type: string - description: The hostname+port of the identity server which should be used for third party identifier lookups. - medium: - type: string - # TODO: Link to identity service spec when it eixsts - description: The kind of address being passed in the address field, for example ``email``. - address: - type: string - description: The invitee's third party identifier. - display_name: - type: string - description: A user-friendly string describing who has been invited. It should not contain the address of the invitee, to avoid leaking mappings between third party identities and matrix user IDs. - required: ["id_server", "medium", "address", "display_name"] - responses: - 200: - description: The user has been invited to join the room. - examples: - application/json: |- - {} - schema: - type: object - 403: - description: |- - You do not have permission to invite the user to the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are: - - The invitee has been banned from the room. - - The invitee is already a member of the room. - - The inviter is not currently in the room. - - The inviter's power level is insufficient to invite users to the room. - examples: - application/json: |- - {"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"} - 429: - description: This request was rate-limited. - schema: - "$ref": "definitions/error.yaml" diff --git a/api/client-server/v1/push_notifier.yaml b/api/client-server/v1/push_notifier.yaml deleted file mode 100644 index be3b5f74d39..00000000000 --- a/api/client-server/v1/push_notifier.yaml +++ /dev/null @@ -1,192 +0,0 @@ -swagger: '2.0' -info: - title: "Matrix Push Notification API" - version: "1.0.0" -host: localhost:8008 -schemes: - - https - - http -basePath: /_matrix/push/v1 -consumes: - - application/json -produces: - - application/json -paths: - "/notify": - post: - summary: Notify a push gateway about an event. - description: |- - This endpoint is invoked by HTTP pushers to notify a push gateway about - an event. - *NB: Notifications are sent to the URL configured when the pusher is - created. This means that the HTTP path may be different depending on the - push gateway.* - parameters: - - in: body - name: notification - description: Information about the push notification. - required: true - schema: - type: object - example: |- - { - "notification": { - "id": "$3957tyerfgewrf384", - "room_id": "!slw48wfj34rtnrf:example.com", - "type": "m.room.message", - "sender": "@exampleuser:matrix.org", - "sender_display_name": "Major Tom", - "room_name": "Mission Control", - "room_alias": "#exampleroom:matrix.org", - "prio": "high", - "content": { - "msgtype": "m.text", - "body": "I'm floating in a most peculiar way." - } - }, - "counts": { - "unread" : 2, - "missed_calls": 1 - }, - "devices": [ - { - "app_id": "org.matrix.matrixConsole.ios", - "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/", - "pushkey_ts": 12345678, - "data" : {}, - "tweaks": { - "sound": "bing" - } - } - ] - } - required: ["notification", "counts", "devices"] - properties: - notification: - type: object - description: Information about the push notification - required: ["id", "room_id", "type", "sender"] - properties: - id: - type: string - description: |- - An identifier for this notification that may be used to - detect duplicate notification requests. This is not - necessarily the ID of the event that triggered the - notification. - room_id: - type: string - description: The ID of the room in which this event occurred. - type: - type: string - description: The type of the event as in the event's ``type`` field. - sender: - type: string - description: The sender of the event as in the corresponding event field. - sender_display_name: - type: string - description: |- - The current display name of the sender in the room in which - the event occurred. - room_name: - type: string - description: The name of the room in which the event occurred. - room_alias: - type: string - description: An alias to display for the room in which the event occurred. - user_is_target: - type: boolean - description: |- - This is true if the user receiving the notification is the - subject of a member event (i.e. the ``state_key`` of the - member event is equal to the user's Matrix ID). - prio: - type: string - enum: ["high", "low"] - description: |- - The priority of the notification. If omitted, ``high`` is - assumed. This may be used by push gateways to deliver less - time-sensitive notifications in a way that will preserve - battery power on mobile devices. - content: - type: object - title: EventContent - description: |- - The ``content`` field from the event, if present. If the - event had no content field, this field is omitted. - counts: - type: object - description: |- - This is a dictionary of the current number of unacknowledged - communications for the recipient user. Counts whose value is - zero are omitted. - properties: - unread: - type: integer - description: |- - The number of unread messages a user has across all of the - rooms they are a member of. - missed_calls: - type: integer - description: |- - The number of unacknowledged missed calls a user has - across all rooms of which they are a member. - devices: - type: array - title: Devices - description: |- - This is an array of devices that the notification should be sent to. - items: - type: object - properties: - app_id: - type: string - description: |- - The app_id given when the pusher was created. - pushkey: - type: string - description: The pushkey given when the pusher was created. - pushkey_ts: - type: integer - description: |- - The unix timestamp (in seconds) when the - pushkey was last updated. - data: - type: object - title: PusherData - description: |- - A dictionary of additional pusher-specific data. For - 'http' pushers, this is the data dictionary passed in at - pusher creation minus the ``url`` key. - tweaks: - type: object - title: Tweaks - description: |- - A dictionary of customisations made to the way this - notification is to be presented. These are added by push rules. - responses: - 200: - description: A list of rejected push keys. - examples: - application/json: |- - { - "rejected": [ "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/" ] - } - schema: - type: object # empty json object - properties: - rejected: - type: array - description: |- - A list of all pushkeys given in the notification request that - are not valid. These could have been rejected by an upstream - gateway because they have expired or have never been valid. - Homeservers must cease sending notification requests for these - pushkeys and remove the associated pushers. It may not - necessarily be the notification in the request that failed: - it could be that a previous notification to the same pushkey - failed. - items: - type: string - description: A pushkey - diff --git a/api/client-server/v1/v1-event-schema b/api/client-server/v1/v1-event-schema deleted file mode 120000 index 7a0d0326b45..00000000000 --- a/api/client-server/v1/v1-event-schema +++ /dev/null @@ -1 +0,0 @@ -../../../event-schemas/schema/v1 \ No newline at end of file diff --git a/api/client-server/v2_alpha/core-event-schema b/api/client-server/v2_alpha/core-event-schema deleted file mode 120000 index b020e6da409..00000000000 --- a/api/client-server/v2_alpha/core-event-schema +++ /dev/null @@ -1 +0,0 @@ -../../../event-schemas/schema/v1/core-event-schema \ No newline at end of file diff --git a/api/client-server/v2_alpha/definitions/definitions b/api/client-server/v2_alpha/definitions/definitions deleted file mode 120000 index 945c9b46d68..00000000000 --- a/api/client-server/v2_alpha/definitions/definitions +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/api/client-server/v2_alpha/definitions/error.yaml b/api/client-server/v2_alpha/definitions/error.yaml deleted file mode 100644 index 20312ae4d6f..00000000000 --- a/api/client-server/v2_alpha/definitions/error.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: object -description: A Matrix-level Error -properties: - errcode: - type: string - description: An error code. - error: - type: string - description: A human-readable error message. -required: ["errcode"] \ No newline at end of file diff --git a/api/client-server/v2_alpha/definitions/event_batch.json b/api/client-server/v2_alpha/definitions/event_batch.json deleted file mode 100644 index 75762d7585a..00000000000 --- a/api/client-server/v2_alpha/definitions/event_batch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "properties": { - "events": { - "type": "array", - "description": "List of events", - "items": { - "type": "object" - } - } - } -} diff --git a/api/client-server/v2_alpha/definitions/event_filter.json b/api/client-server/v2_alpha/definitions/event_filter.json deleted file mode 100644 index 1cdcb1f41a9..00000000000 --- a/api/client-server/v2_alpha/definitions/event_filter.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "type": "object", - "properties": { - "limit": { - "type": "integer", - "description": - "The maximum number of events to return." - }, - "types": { - "type": "array", - "description": - "A list of event types to include. If this list is absent then all event types are included. A '*' can be used as a wildcard to match any sequence of characters.", - "items": { - "type": "string" - } - }, - "not_types": { - "type": "array", - "description": - "A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will be excluded even if it is listed in the 'types' filter. A '*' can be used as a wildcard to match any sequence of characters.", - "items": { - "type": "string" - } - }, - "senders": { - "type": "array", - "description": - "A list of senders IDs to include. If this list is absent then all senders are included. A '*' can be used as a wildcard to match any sequence of characters.", - "items": { - "type": "string" - } - }, - "not_senders": { - "type": "array", - "description": - "A list of sender IDs to exclude. If this list is absent then no senders are excluded. A matching sender will be excluded even if it is listed in the 'senders' filter. A '*' can be used as a wildcard to match any sequence of characters.", - "items": { - "type": "string" - } - } - } -} diff --git a/api/client-server/v2_alpha/definitions/room_event_batch.json b/api/client-server/v2_alpha/definitions/room_event_batch.json deleted file mode 100644 index fcf148f3699..00000000000 --- a/api/client-server/v2_alpha/definitions/room_event_batch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "properties": { - "events": { - "type": "array", - "description": "List of event ids", - "items": { - "type": "string" - } - } - } -} diff --git a/api/client-server/v2_alpha/definitions/room_event_filter.json b/api/client-server/v2_alpha/definitions/room_event_filter.json deleted file mode 100644 index 86375781c08..00000000000 --- a/api/client-server/v2_alpha/definitions/room_event_filter.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "allOf": [{"$ref": "definitions/event_filter.json"}], - "properties": { - "rooms": { - "type": "array", - "description": - "A list of room IDs to include. If this list is absent then all rooms are included. A '*' can be used as a wildcard to match any sequence of characters.", - "items": { - "type": "string" - } - }, - "not_rooms": { - "type": "array", - "description": - "A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the 'rooms' filter. A '*' can be used as a wildcard to match any sequence of characters.", - "items": { - "type": "string" - } - } - } -} diff --git a/api/client-server/v2_alpha/definitions/sync_filter.json b/api/client-server/v2_alpha/definitions/sync_filter.json deleted file mode 100644 index 0cd6a798631..00000000000 --- a/api/client-server/v2_alpha/definitions/sync_filter.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "type": "object", - "properties": { - "room": { - "type": "object", - "properties": { - "state": { - "description": - "The state events to include for rooms.", - "allOf": [{"$ref": "definitions/room_event_filter.json"}] - }, - "timeline": { - "description": - "The message and state update events to include for rooms.", - "allOf": [{"$ref": "definitions/room_event_filter.json"}] - }, - "ephemeral": { - "description": - "The events that aren't recorded in the room history, e.g. typing and receipts, to include for rooms.", - "allOf": [{"$ref": "definitions/room_event_filter.json"}] - } - } - }, - "presence": { - "description": - "The presence updates to include.", - "allOf": [{"$ref": "definitions/event_filter.json"}] - }, - "event_format": { - "description": - "The format to use for events. 'client' will return the events in a format suitable for clients. 'federation' will return the raw event as receieved over federation. The default is 'client'.", - "type": "string", - "enum": ["client", "federation"] - }, - "event_fields": { - "type": "array", - "description": - "List of event fields to include. If this list is absent then all fields are included. The entries may include '.' charaters to indicate sub-fields. So ['content.body'] will include the 'body' field of the 'content' object. A literal '.' character in a field name may be escaped using a '\\'. A server may include more fields than were requested.", - "items": { - "type": "string" - } - } - } -} diff --git a/api/client-server/v2_alpha/definitions/timeline_batch.json b/api/client-server/v2_alpha/definitions/timeline_batch.json deleted file mode 100644 index ddf8d341619..00000000000 --- a/api/client-server/v2_alpha/definitions/timeline_batch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "allOf": [{"$ref":"definitions/room_event_batch.json"}], - "properties": { - "limited": { - "type": "boolean", - "description": "Whether there are more events on the server" - }, - "prev_batch": { - "type": "string", - "description": "If the batch was limited then this is a token that can be supplied to the server to retrieve more events" - } - } -} diff --git a/api/client-server/versions.yaml b/api/client-server/versions.yaml new file mode 100644 index 00000000000..1352279aff1 --- /dev/null +++ b/api/client-server/versions.yaml @@ -0,0 +1,54 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Client-Server Versions API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/client +produces: + - application/json +paths: + "/versions": + get: + summary: Gets the versions of the specification supported by the server. + description: |- + Gets the versions of the specification supported by the server. + + Values will take the form ``rX.Y.Z``. + + Only the latest ``Z`` value will be reported for each supported ``X.Y`` value. + i.e. if the server implements ``r0.0.0``, ``r0.0.1``, and ``r1.2.0``, it will report ``r0.0.1`` and ``r1.2.0``. + responses: + 200: + description: The versions supported by the server. + examples: + application/json: |- + { + "versions": ["r0.0.1"] + } + schema: + type: object + properties: + versions: + type: array + description: The supported versions. + items: + type: string + description: The supported versions + tags: + - Server administration diff --git a/api/client-server/v1/voip.yaml b/api/client-server/voip.yaml similarity index 68% rename from api/client-server/v1/voip.yaml rename to api/client-server/voip.yaml index 5fdf1ca7a99..300005405f1 100644 --- a/api/client-server/v1/voip.yaml +++ b/api/client-server/voip.yaml @@ -1,24 +1,33 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. swagger: '2.0' info: - title: "Matrix Client-Server v1 Voice over IP API" + title: "Matrix Client-Server Voice over IP API" version: "1.0.0" host: localhost:8008 schemes: - https - http -basePath: /_matrix/client/api/v1 +basePath: /_matrix/client/%CLIENT_MAJOR_VERSION% consumes: - application/json produces: - application/json securityDefinitions: - accessToken: - type: apiKey - description: The user_id or application service access_token - name: access_token - in: query + $ref: definitions/security.yaml paths: - "/turnServer": + "/voip/turnServer": get: summary: Obtain TURN server credentials. description: |- @@ -65,4 +74,5 @@ paths: description: This request was rate-limited. schema: "$ref": "definitions/error.yaml" - + tags: + - VOIP diff --git a/api/files/css b/api/files/css index 24bbf3fb48b..3b5a0643df4 100644 --- a/api/files/css +++ b/api/files/css @@ -3,7 +3,7 @@ font-family: 'Droid Sans'; font-style: normal; font-weight: 400; - src: local('Droid Sans'), local('DroidSans'), url(http://fonts.gstatic.com/s/droidsans/v5/s-BiyweUPV0v-yRb-cjciPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2'); + src: local('Droid Sans'), local('DroidSans'), url(http://fonts.gstatic.com/s/droidsans/v5/s-BiyweUPV0v-yRb-cjciPk_vArhqVIZ0nv9q090hN8.woff2), format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } /* latin */ @@ -11,6 +11,6 @@ font-family: 'Droid Sans'; font-style: normal; font-weight: 700; - src: local('Droid Sans Bold'), local('DroidSans-Bold'), url(http://fonts.gstatic.com/s/droidsans/v5/EFpQQyG9GqCrobXxL-KRMYWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2'); + src: local('Droid Sans Bold'), local('DroidSans-Bold'), url(http://fonts.gstatic.com/s/droidsans/v5/EFpQQyG9GqCrobXxL-KRMYWiMMZ7xLd792ULpGE4W_Y.woff2), format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } diff --git a/api/identity/lookup.yaml b/api/identity/lookup.yaml new file mode 100644 index 00000000000..83c3b661b17 --- /dev/null +++ b/api/identity/lookup.yaml @@ -0,0 +1,86 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Identity Service Lookup API" + version: "1.0.0" +host: localhost:8090 +schemes: + - https + - http +basePath: /_matrix/identity/api/v1 +produces: + - application/json +paths: + "/lookup": + get: + summary: Look up the Matrix user ID for a 3pid. + description: Look up the Matrix user ID for a 3pid. + parameters: + - in: query + type: string + name: medium + required: true + description: The literal string "email". + x-example: "email" + - in: query + type: string + name: address + required: true + description: The email address being looked up. + x-example: "louise@bobs.burgers" + responses: + 200: + description: + The association for that 3pid, or the empty object if no association is known. + examples: + application/json: |- + { + "address": "louise@bobs.burgers", + "medium": "email", + "mxid": "@ears:matrix.org", + "not_before": 1428825849161, + "not_after": 4582425849161, + "ts": 1428825849161, + + "signatures": { + "matrix.org": { + "ed25519:0": "ENiU2YORYUJgE6WBMitU0mppbQjidDLanAusj8XS2nVRHPu+0t42OKA/r6zV6i2MzUbNQ3c3MiLScJuSsOiVDQ" + } + } + } + schema: + type: object + properties: + address: + type: string + description: The 3pid address of the user being looked up. + medium: + type: string + description: The literal string "email". + mxid: + type: string + description: The Matrix user ID associated with the 3pid. + not_before: + type: integer + description: A unix timestamp before which the association is not known to be valid. + not_after: + type: integer + description: A unix timestamp after which the association is not known to be valid. + ts: + type: integer + description: The unix timestamp at which the association was verified. + signatures: + type: object + description: The signatures of the verifying identity service which show that the association should be trusted, if you trust the verifying identity service. diff --git a/api/identity/pubkey.yaml b/api/identity/pubkey.yaml new file mode 100644 index 00000000000..40d2a23768f --- /dev/null +++ b/api/identity/pubkey.yaml @@ -0,0 +1,110 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Identity Service Public Key API" + version: "1.0.0" +host: localhost:8090 +schemes: + - https + - http +basePath: /_matrix/identity/api/v1 +produces: + - application/json +paths: + "/pubkey/{keyId}": + get: + summary: Get a public key. + description: |- + Get the public key for the passed key ID. + parameters: + - in: path + type: string + name: keyId + required: true + description: |- + The ID of the key. This should take the form algorithm:identifier + where algorithm identifies the signing algorithm, and the identifier + is an opaque string. + x-example: "ed25519:0" + responses: + 200: + description: + The public key exists. + examples: + application/json: |- + { + "public_key": "VXuGitF39UH5iRfvbIknlvlAVKgD1BsLDMvBf0pmp7c" + } + schema: + type: object + properties: + public_key: + type: string + "/pubkey/isvalid": + get: + summary: Check whether a long-term public key is valid. + description: |- + Check whether a long-term public key is valid. + parameters: + - in: query + type: string + name: public_key + required: true + description: |- + The unpadded base64-encoded public key to check. + x-example: "VXuGitF39UH5iRfvbIknlvlAVKgD1BsLDMvBf0pmp7c" + responses: + 200: + description: + The validity of the public key. + examples: + application/json: |- + { + "valid": true + } + schema: + type: object + properties: + valid: + type: boolean + description: Whether the public key is recognised and is currently valid. + "/pubkey/ephemeral/isvalid": + get: + summary: Check whether a short-term public key is valid. + description: |- + Check whether a short-term public key is valid. + parameters: + - in: query + type: string + name: public_key + required: true + description: |- + The unpadded base64-encoded public key to check. + x-example: "VXuGitF39UH5iRfvbIknlvlAVKgD1BsLDMvBf0pmp7c" + responses: + 200: + description: + The validity of the public key. + examples: + application/json: |- + { + "valid": true + } + schema: + type: object + properties: + valid: + type: boolean + description: Whether the public key is recognised and is currently valid. diff --git a/api/push-gateway/push_notifier.yaml b/api/push-gateway/push_notifier.yaml new file mode 100644 index 00000000000..0effef081a0 --- /dev/null +++ b/api/push-gateway/push_notifier.yaml @@ -0,0 +1,230 @@ +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +swagger: '2.0' +info: + title: "Matrix Push Notification API" + version: "1.0.0" +host: localhost:8008 +schemes: + - https + - http +basePath: /_matrix/push/%CLIENT_MAJOR_VERSION% +consumes: + - application/json +produces: + - application/json +paths: + "/notify": + post: + summary: Notify a push gateway about an event. + description: |- + This endpoint is invoked by HTTP pushers to notify a push gateway about + an event or update the number of unread notifications a user has. + In the former case it will contain selected information about the event. + In either case it may contain numeric counts of the number of unread + events of different types the user has. The counts may be sent along + with a notification about an event or by themselves. + + Notifications about a particular event will normally cause the user to be + alerted in some way. It is therefore necessary to perform duplicate + suppression for such notifications using the `event_id` field to avoid + retries of this HTTP API causing duplicate alerts. The operation of + updating counts of unread notifications should be idempotent and + therefore do not require duplicate suppression. + + Notifications are sent to the URL configured when the pusher is + created. This means that the HTTP path may be different depending on the + push gateway. + + parameters: + - in: body + name: notification + description: Information about the push notification. + required: true + schema: + type: object + example: |- + { + "notification": { + "id": "$3957tyerfgewrf384", + "room_id": "!slw48wfj34rtnrf:example.com", + "type": "m.room.message", + "sender": "@exampleuser:matrix.org", + "sender_display_name": "Major Tom", + "room_name": "Mission Control", + "room_alias": "#exampleroom:matrix.org", + "prio": "high", + "content": { + "msgtype": "m.text", + "body": "I'm floating in a most peculiar way." + }, + "counts": { + "unread" : 2, + "missed_calls": 1 + }, + "devices": [ + { + "app_id": "org.matrix.matrixConsole.ios", + "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/", + "pushkey_ts": 12345678, + "data" : {}, + "tweaks": { + "sound": "bing" + } + } + ] + } + } + required: ["notification"] + properties: + notification: + type: object + title: Notification + description: Information about the push notification + required: ["devices"] + properties: + event_id: + type: string + description: |- + The Matrix event ID of the event being notified about. + This is required if the notification is about a + particular Matrix event. It may be omitted for notifications + that only contain updated badge counts. This ID can and + should be used to detect duplicate notification requests. + room_id: + type: string + description: |- + The ID of the room in which this event occurred. + Required if the notification relates to a specific + Matrix event. + type: + type: string + description: |- + The type of the event as in the event's ``type`` field. + Required if the notification relates to a specific + Matrix event. + sender: + type: string + description: |- + The sender of the event as in the corresponding event field. + Required if the notification relates to a specific + Matrix event. + sender_display_name: + type: string + description: |- + The current display name of the sender in the room in which + the event occurred. + room_name: + type: string + description: The name of the room in which the event occurred. + room_alias: + type: string + description: An alias to display for the room in which the event occurred. + user_is_target: + type: boolean + description: |- + This is true if the user receiving the notification is the + subject of a member event (i.e. the ``state_key`` of the + member event is equal to the user's Matrix ID). + prio: + type: string + enum: ["high", "low"] + description: |- + The priority of the notification. If omitted, ``high`` is + assumed. This may be used by push gateways to deliver less + time-sensitive notifications in a way that will preserve + battery power on mobile devices. + content: + type: object + title: EventContent + description: |- + The ``content`` field from the event, if present. If the + event had no content field, this field is omitted. + counts: + type: object + title: Counts + description: |- + This is a dictionary of the current number of unacknowledged + communications for the recipient user. Counts whose value is + zero are omitted. + properties: + unread: + type: integer + description: |- + The number of unread messages a user has across all of the + rooms they are a member of. + missed_calls: + type: integer + description: |- + The number of unacknowledged missed calls a user has + across all rooms of which they are a member. + devices: + type: array + title: Devices + description: |- + This is an array of devices that the notification should be sent to. + items: + type: object + title: Device + properties: + app_id: + type: string + description: |- + The app_id given when the pusher was created. + pushkey: + type: string + description: The pushkey given when the pusher was created. + pushkey_ts: + type: integer + description: |- + The unix timestamp (in seconds) when the + pushkey was last updated. + data: + type: object + title: PusherData + description: |- + A dictionary of additional pusher-specific data. For + 'http' pushers, this is the data dictionary passed in at + pusher creation minus the ``url`` key. + tweaks: + type: object + title: Tweaks + description: |- + A dictionary of customisations made to the way this + notification is to be presented. These are added by push rules. + responses: + 200: + description: A list of rejected push keys. + examples: + application/json: |- + { + "rejected": [ "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/" ] + } + schema: + type: object # empty json object + properties: + rejected: + type: array + description: |- + A list of all pushkeys given in the notification request that + are not valid. These could have been rejected by an upstream + gateway because they have expired or have never been valid. + Homeservers must cease sending notification requests for these + pushkeys and remove the associated pushers. It may not + necessarily be the notification in the request that failed: + it could be that a previous notification to the same pushkey + failed. + items: + type: string + description: A pushkey diff --git a/api/swagger.html b/api/swagger.html deleted file mode 100644 index be6fb6b98a9..00000000000 --- a/api/swagger.html +++ /dev/null @@ -1,76 +0,0 @@ - - - Matrix Client-Server API Documentation - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
- -
Can't read from server. It may not have the appropriate access-control-origin settings.
-
- - - diff --git a/attic/v1_registration_login.rst b/attic/v1_registration_login.rst index 4b80600b8a1..1364319bb6e 100644 --- a/attic/v1_registration_login.rst +++ b/attic/v1_registration_login.rst @@ -1,17 +1,17 @@ Registration and Login ---------------------- -Clients must register with a home server in order to use Matrix. After +Clients must register with a homeserver in order to use Matrix. After registering, the client will be given an access token which must be used in ALL -requests to that home server as a query parameter 'access_token'. +requests to that homeserver as a query parameter 'access_token'. If the client has already registered, they need to be able to login to their -account. The home server may provide many different ways of logging in, such as +account. The homeserver may provide many different ways of logging in, such as user/password auth, login via a social network (OAuth2), login by confirming a token sent to their email address, etc. This specification does not define how -home servers should authorise their users who want to login to their existing +homeservers should authorise their users who want to login to their existing accounts, but instead defines the standard interface which implementations -should follow so that ANY client can login to ANY home server. Clients login +should follow so that ANY client can login to ANY homeserver. Clients login using the |login|_ API. Clients register using the |register|_ API. Registration follows the same general procedure as login, but the path requests are sent to and the details contained in them are different. @@ -26,7 +26,7 @@ In order to determine up-front what the server's requirements are, the client can request from the server a complete description of all of its acceptable flows of the registration or login process. It can then inspect the list of returned flows looking for one for which it believes it can complete all of the -required stages, and perform it. As each home server may have different ways of +required stages, and perform it. As each homeserver may have different ways of logging in, the client needs to know how they should login. All distinct login stages MUST have a corresponding ``type``. A ``type`` is a namespaced string which details the mechanism for logging in. @@ -64,12 +64,12 @@ ID and a new access token MUST be returned:: "access_token": "abcdef0123456789" } -The ``user_id`` key is particularly useful if the home server wishes to support +The ``user_id`` key is particularly useful if the homeserver wishes to support localpart entry of usernames (e.g. "user" rather than "@user:matrix.org"), as the client may not be able to determine its ``user_id`` in this case. -If the flow has multiple stages to it, the home server may wish to create a -session to store context between requests. If a home server responds with a +If the flow has multiple stages to it, the homeserver may wish to create a +session to store context between requests. If a homeserver responds with a ``session`` key to a request, clients MUST submit it in subsequent requests until the flow is completed:: @@ -99,7 +99,7 @@ To respond to this type, reply with:: "password": "" } -The home server MUST respond with either new credentials, the next stage of the +The homeserver MUST respond with either new credentials, the next stage of the login process, or a standard error response. Captcha-based @@ -123,7 +123,7 @@ To respond to this type, reply with:: Recaptcha.get_challenge(); Recaptcha.get_response(); -The home server MUST respond with either new credentials, the next stage of the +The homeserver MUST respond with either new credentials, the next stage of the login process, or a standard error response. OAuth2-based @@ -146,24 +146,24 @@ The server MUST respond with:: "uri": } -The home server acts as a 'confidential' client for the purposes of OAuth2. If +The homeserver acts as a 'confidential' client for the purposes of OAuth2. If the uri is a ``sevice selection URI``, it MUST point to a webpage which prompts the user to choose which service to authorize with. On selection of a service, this MUST link through to an ``Authorization Request URI``. If there is only 1 -service which the home server accepts when logging in, this indirection can be +service which the homeserver accepts when logging in, this indirection can be skipped and the "uri" key can be the ``Authorization Request URI``. The client then visits the ``Authorization Request URI``, which then shows the OAuth2 Allow/Deny prompt. Hitting 'Allow' returns the ``redirect URI`` with the -auth code. Home servers can choose any path for the ``redirect URI``. The +auth code. Homeservers can choose any path for the ``redirect URI``. The client should visit the ``redirect URI``, which will then finish the OAuth2 -login process, granting the home server an access token for the chosen service. -When the home server gets this access token, it verifies that the cilent has +login process, granting the homeserver an access token for the chosen service. +When the homeserver gets this access token, it verifies that the cilent has authorised with the 3rd party, and can now complete the login. The OAuth2 ``redirect URI`` (with auth code) MUST respond with either new credentials, the next stage of the login process, or a standard error response. -For example, if a home server accepts OAuth2 from Google, it would return the +For example, if a homeserver accepts OAuth2 from Google, it would return the Authorization Request URI for Google:: { @@ -171,7 +171,7 @@ Authorization Request URI for Google:: client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos" } -The client then visits this URI and authorizes the home server. The client then +The client then visits this URI and authorizes the homeserver. The client then visits the REDIRECT_URI with the auth code= query parameter which returns:: { @@ -195,7 +195,7 @@ To respond to this type, reply with:: "email": "" } -After validating the email address, the home server MUST send an email +After validating the email address, the homeserver MUST send an email containing an authentication code and return:: { @@ -212,7 +212,7 @@ code:: "code": "" } -The home server MUST respond to this with either new credentials, the next +The homeserver MUST respond to this with either new credentials, the next stage of the login process, or a standard error response. Email-based (url) @@ -231,7 +231,7 @@ To respond to this type, reply with:: "email": "" } -After validating the email address, the home server MUST send an email +After validating the email address, the homeserver MUST send an email containing an authentication URL and return:: { @@ -247,7 +247,7 @@ client should perform another request:: "session": "" } -The home server MUST respond to this with either new credentials, the next +The homeserver MUST respond to this with either new credentials, the next stage of the login process, or a standard error response. A common client implementation will be to periodically poll until the link is @@ -264,7 +264,7 @@ Email-based (identity server) Prior to submitting this, the client should authenticate with an identity server. After authenticating, the session information should be submitted to -the home server. +the homeserver. To respond to this type, reply with:: @@ -293,7 +293,7 @@ of a previous login stage:: "next": "" } -If a home server implements N-factor authentication, it MUST respond with all +If a homeserver implements N-factor authentication, it MUST respond with all ``stages`` when initially queried for their login requirements:: { diff --git a/changelogs/client_server.rst b/changelogs/client_server.rst new file mode 100644 index 00000000000..ec4a09b2357 --- /dev/null +++ b/changelogs/client_server.rst @@ -0,0 +1,275 @@ + +==================== + +- Breaking changes: + + - Change the rule kind of `.m.rule.contains_display_name` from + `underride` to `override`. This works with all known clients + which support push rules, but any other clients implementing + the push rules API should be aware of this change. This + makes it simple to mute rooms correctly in the API + (`#373 `_). + - Remove ``/tokenrefresh`` from the API + (`#395 `_). + - Remove requirement that tokens used in token-based login be macaroons + (`#395 `_). + +- Changes to the API which will be backwards-compatible for clients: + + - Add ``filename`` parameter to ``POST /_matrix/media/r0/upload`` + (`#364 `_). + - Document CAS-based client login and the use of ``m.login.token`` in + ``/login`` (`#367 `_). + - Make ``origin_server_ts`` a mandatory field of room events + (`#379 `_). + - Add top-level ``account_data`` key to the responses to ``GET /sync`` and + ``GET /initialSync`` + (`#380 `_). + - Add ``is_direct`` flag to ``POST /createRoom`` and invite member event. + Add 'Direct Messaging' module + (`#389 `_). + - Add ``contains_url`` option to ``RoomEventFilter`` + (`#390 `_). + - Add ``filter`` optional query param to ``/messages`` + (`#390 `_). + - Add 'Send-to-Device messaging' module + (`#386 `_). + - Add 'Device management' module + (`#402 `_). + - Require that User-Interactive auth fallback pages call + ``window.postMessage`` to notify apps of completion + (`#398 `_). + - Add pagination and filter support to ``/publicRooms``. Change response to + omit fields rather than return ``null``. Add estimate of total number of + rooms in list. + (`#388 `_). + - Allow guest accounts to use a number of endpoints which are required for + end-to-end encryption. + (`#751 `_). + - Add key distribution APIs, for use with end-to-end encryption. + (`#894 `_). + +- Spec clarifications: + + - Add endpoints and logic for invites and third-party invites to the federation + spec and update the JSON of the request sent by the identity server upon 3PID + binding + (`#997 `) + - Fix response format and 404 example for room alias lookup + (`#960 `) + - Fix examples of ``m.room.member`` event and room state change, + and added a clarification on the membership event sent upon profile update + (`#950 `_). + - Spell out the way that state is handled by ``POST /createRoom`` + (`#362 `_). + - Clarify the fields which are applicable to different types of push rule + (`#365 `_). + - A number of clarifications to authentication + (`#371 `_). + - Correct references to ``user_id`` which should have been ``sender`` + (`#376 `_). + - Correct inconsistent specification of ``redacted_because`` fields and their + values (`#378 `_). + - Mark required fields in response objects as such + (`#394 `_). + - Make ``m.notice`` description a bit harder in its phrasing to try to + dissuade the same issues that occurred with IRC + (`#750 `_). + - ``GET /user/{userId}/filter/{filterId}`` requires authentication + (`#1003 `_). + +r0.2.0 +====== + +- Spec clarifications: + + - Room aliases (`#337 `_): + + - Make it clear that ``GET /directory/room/{roomAlias}`` must work for + federated aliases. + + - ``GET /directory/room/{roomAlias}`` cannot return a 409; the ``PUT`` + endpoint can, however. + + - Power levels: + + - Clarify the defaults to be used for various fields of the + ``m.room.power_levels`` event + (`#286 `_, + `#341 `_). + + - Add suggestions for mapping of names to power levels + (`#336 `_). + + - Clarify the room naming algorithm in certain edge cases + (`#351 `_). + + - Remove outdated references to the pre-r0 ``/events`` API, and clarify the + section on syncing + (`#352 `_). + + +- Changes to the API which will be backwards-compatible for clients: + + - New endpoints: + + - ``POST /register/email/requestToken`` + (`#343 `_). + + - ``POST /account/3pid/email/requestToken`` + (`#346 `_). + + - ``POST /account/password/email/requestToken`` + (`#346 `_). + + - ``POST /account/deactivate`` + (`#361 `_). + + - Updates to the Presence module + (`#278 `_, + `#342 `_): + + - Remove unused ``free_for_chat`` presence state + - Add ``currently_active`` flag to the ``m.presence`` event and the ``GET + /presence/{userId}/status`` response. + - Make idle timeout the responsibility of the server + - Remove requirements on servers to propagate profile information via + ``m.presence`` events. + + - Add new predefined push rules + (`#274 `_, + `#340 `_). + + - ``/sync`` should always return a ``prev_batch`` token + (`#345 `_). + + - add ``to`` parameter to ``GET /rooms/{roomId}/messages`` API + (`#348 `_). + +r0.1.0 +====== + +This release includes the following changes since r0.0.1: + +- Breaking changes to the API [#]_: + + - ``POST /rooms/{roomId}/join`` no longer permits use of a room alias instead + of a room id. (``POST /join/{roomIdOrAlias}`` continues to allow either.) + - ``POST /account/3pid``: correct the name of the ``three_pid_creds`` + parameter + - The "Push Rules" module no longer supports device-specific rules: + + - ``GET /pushrules`` no longer returns a ``device`` property + - ``device/{profile_tag}`` is no longer a valid ``scope`` for push rules + - ``profile_tag`` is no longer a valid kind of condition on push rules. + + (Device-specific push rules will be reintroduced in the future; in the + meantime, their specification has been moved to a `draft branch`__.) + + __ https://matrix.org/speculator/spec/drafts%2Freinstate_device_push_rules/ + +- Changes to the API which will be backwards-compatible for clients: + + - New endpoints: + + - ``POST /logout`` + - ``POST /rooms/{roomId}/unban`` + - ``POST /rooms/{roomId}/kick`` + - ``GET /pushers`` + - ``GET /pushrules/{scope}/{kind}/{ruleId}/enabled`` + (previously ``PUT``-only) + - ``GET`` and ``PUT /pushrules/{scope}/{kind}/{ruleId}/actions`` + + - Add ``third_party_signed`` parameter to ``POST /rooms/{roomId}/join`` + - Add ``M_INVALID_USERNAME`` as valid response to ``POST /register`` + - Add ``unread_notifications`` field to ``GET /sync`` response + - Add optional ``invite`` property to ``m.room.power_levels`` state event + - Add optional ``public_key`` and ``public_keys`` to + ``m.room.third_party_invite`` state event + - Password-based ``/login`` may now use a third-party identifier instead of + a matrix user id. + +- Spec clarifications + + - Make the state diagram for room membership explicit + - Note that a user may not be invited to a room while banned + - Clarify the expected order of events in the response to + ``GET /rooms/{roomId}/context/{eventId}``, as well as correcting the + example for that API + - Clarify the behaviour of the "Room History Visibility" module; in + particular, the behaviour of the ``shared`` history visibilty, and how + events at visibility boundaries should be handled + - Separate the "Room Previews" module from "Guest access" + - Reword the description of the ``profile_tag`` property in + ``PUT /pushers/set``, and note that it is not mandatory. + + +.. [#] Our `versioning policy <../index.html#specification-versions>`_ would + strictly require that a breaking change be denoted by a new major + specification version. However we are not aware of any clients which + rely on the old behaviour here, nor server implementations which offer + it, so we have chosen to retain the r0 designation on this occasion. + +r0.0.1 +====== + +This release includes the following changes since r0.0.0: + +- API changes: + - Added new ``/versions`` API + - ``/createRoom`` takes an optional ``invite_3pid`` parameter + - ``/publicRooms`` returns an ``avatar_url`` result +- The following APIs are now deprecated: + - ``/initialSync`` + - ``/events`` + - ``/events/:eventId`` + - ``/rooms/:roomId/initialSync`` +- Spec clarifications + - Document inter-version compatibility + - Document the parameters to the ``/user/:userId/filter`` API + - Document the ``next_batch`` parameter on ``/search`` + - Document the membership states on ``m.room.member`` events + - Minor clarifications/corrections to: + - Guest access module + - Search module + - ``/login`` API + - ``/rooms/:roomId/send/:eventType/:txnId`` API + - ``/rooms/:roomId/context/:eventId`` API + +r0.0.0 +====== + +This is the first release of the client-server specification. It is largely a dump of what has currently been implemented, and there are several inconsistencies. + +An upcoming minor release will deprecate many of these inconsistencies, and they will be removed in the next major release. + +Since the draft stage, the following major changes have been made: +- /api/v1 and /v2_alpha path segments have been replaced with the major version of the release (i.e. 'r0'). +- Some POST versions of APIs with both POST and PUT have been removed. +- The specification has been split into one specification per API. This is the client-server API. The server-server API can be found documented separately. +- All APIs are now documented using Swagger +- The following modules have been added: + - Content repository + - Instant messaging + - Push notification + - History visibility + - Search + - Invites based on third party identifiers + - Room tagging + - Guest access + - Client config +- The following APIs were added: + - ``/sync`` + - ``/publicRooms`` + - ``/rooms/{roomId}/forget`` + - ``/admin/whois`` + - ``/rooms/{roomId}/redact`` + - ``/user/{userId}/filter`` +- The following APIs have been significantly modified: + - Invitations now contain partial room state + - Invitations can now be rejected + - ``/directory`` +- The following events have been added: + - ``m.room.avatar`` +- Example signed json is included for reference +- Commentary on display name calculation was added diff --git a/drafts/address-book-repo.rst b/drafts/address-book-repo.rst deleted file mode 100644 index d6315a9657a..00000000000 --- a/drafts/address-book-repo.rst +++ /dev/null @@ -1,14 +0,0 @@ -Address book repository -======================= - -.. NOTE:: - This section is a work in progress. - -.. TODO-spec - Do we even need it? Clients can use out-of-band addressbook servers for now; - this should definitely not be core. - - format: POST(?) wodges of json, some possible processing, then return wodges of json on GET. - - processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of - contacts), etc. - - Standard json format for contacts? Piggy back off vcards? - diff --git a/drafts/ancient_federated_versioning_design_notes.rst b/drafts/ancient_federated_versioning_design_notes.rst index ffda60633f2..036989ebf15 100644 --- a/drafts/ancient_federated_versioning_design_notes.rst +++ b/drafts/ancient_federated_versioning_design_notes.rst @@ -1,11 +1,11 @@ -Versioning is, like, hard for backfilling backwards because of the number of Home Servers involved. +Versioning is, like, hard for backfilling backwards because of the number of homeservers involved. -The way we solve this is by doing versioning as an acyclic directed graph of PDUs. For backfilling purposes, this is done on a per context basis. +The way we solve this is by doing versioning as an acyclic directed graph of PDUs. For backfilling purposes, this is done on a per context basis. When we send a PDU we include all PDUs that have been received for that context that hasn't been subsequently listed in a later PDU. The trivial case is a simple list of PDUs, e.g. A <- B <- C. However, if two servers send out a PDU at the same to, both B and C would point at A - a later PDU would then list both B and C. Problems with opaque version strings: - - How do you do clustering without mandating that a cluster can only have one transaction in flight to a given remote home server at a time. + - How do you do clustering without mandating that a cluster can only have one transaction in flight to a given remote homeserver at a time. If you have multiple transactions sent at once, then you might drop one transaction, receive another with a version that is later than the dropped transaction and which point ARGH WE LOST A TRANSACTION. - - How do you do backfilling? A version string defines a point in a stream w.r.t. a single home server, not a point in the context. + - How do you do backfilling? A version string defines a point in a stream w.r.t. a single homeserver, not a point in the context. We only need to store the ends of the directed graph, we DO NOT need to do the whole one table of nodes and one of edges. diff --git a/drafts/as-http-api.rst b/drafts/as-http-api.rst deleted file mode 100644 index 2c8f8e37ca1..00000000000 --- a/drafts/as-http-api.rst +++ /dev/null @@ -1,574 +0,0 @@ -.. TODO - Sometimes application services need to create rooms (e.g. when lazy loading - from room aliases). Created rooms need to have a user that created them, so - federation works (as it relies on an entry existing in m.room.member). We - should be able to add metadata to m.room.member to state that this user is an - application service, a virtual user, etc. - - -Application Services HTTP API -============================= - -.. contents:: Table of Contents - -.. sectnum:: - -Application Service -> Home Server ----------------------------------- -This contains home server APIs which are used by the application service. - -Registration API ``[Draft]`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This API registers the application service with its host homeserver to offer its -services. - -Inputs: - - Credentials (e.g. some kind of string token) - - Namespace[users] - - Namespace[room aliases] - - URL base to receive inbound comms -Output: - - The credentials the HS will use to query the AS with in return. (e.g. some - kind of string token) -Side effects: - - The HS will start delivering events to the URL base specified if this 200s. -API called when: - - The application service wants to register with a brand new home server. -Notes: - - An application service can state whether they should be the only ones who - can manage a specified namespace. This is referred to as an "exclusive" - namespace. An exclusive namespace prevents humans and other application - services from creating/deleting entities in that namespace. Typically, - exclusive namespaces are used when the rooms represent real rooms on - another service (e.g. IRC). Non-exclusive namespaces are used when the - application service is merely augmenting the room itself (e.g. providing - logging or searching facilities). - - Namespaces are represented by POSIX extended regular expressions in JSON. - They look like:: - - users: [ - { - "exclusive": true, - "regex": "@irc\.freenode\.net/.*" - } - ] - -:: - - POST /register - - Request format - { - url: "https://my.application.service.com/matrix/", - as_token: "some_AS_token", - namespaces: { - users: [ - { - "exclusive": true, - "regex": "@irc\.freenode\.net/.*" - } - ], - aliases: [ - { - "exclusive": true, - "regex": "#irc\.freenode\.net/.*" - } - ], - rooms: [ - { - "exclusive": true, - "regex": "!irc\.freenode\.net/.*" - } - ] - } - } - - - Returns: - 200 : Registration accepted. - 400 : Namespaces do not conform to regex - 401 : Credentials need to be supplied. - 403 : AS credentials rejected. - - - 200 OK response format - - { - hs_token: "string" - } - -Unregister API ``[Draft]`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ -This API unregisters a previously registered AS from the home server. - -Inputs: - - AS token -Output: - - None. -Side effects: - - The HS will stop delivering events to the URL base specified for this AS if - this 200s. -API called when: - - The application service wants to stop receiving all events from the HS. - -:: - - POST /unregister - - Request format - { - as_token: "string" - } - - -Home Server -> Application Service ----------------------------------- -This contains application service APIs which are used by the home server. - -User Query ``[Draft]`` -~~~~~~~~~~~~~~~~~~~~~~ - -This API is called by the HS to query the existence of a user on the Application -Service's namespace. - -Inputs: - - User ID - - HS Credentials -Output: - - Whether the user exists. -Side effects: - - User is created on the HS by the AS via CS APIs during the processing of this request. -API called when: - - HS receives an event for an unknown user ID in the AS's namespace, e.g. an - invite event to a room. -Notes: - - When the AS receives this request, if the user exists, it must create the user via - the CS API. - - It can also set arbitrary information about the user (e.g. display name, join rooms, etc) - using the CS API. - - When this setup is complete, the AS should respond to the HS request. This means the AS - blocks the HS until the user is created. - - This is deemed more flexible than alternative methods (e.g. returning a JSON blob with the - user's display name and get the HS to provision the user). -Retry notes: - - The home server cannot respond to the client's request until the response to - this API is obtained from the AS. - - Recommended that home servers try a few times then time out, returning a - 408 Request Timeout to the client. - -:: - - GET /users/$user_id?access_token=$hs_token - - Returns: - 200 : User is recognised. - 404 : User not found. - 401 : Credentials need to be supplied. - 403 : HS credentials rejected. - - - 200 OK response format - - {} - -Room Alias Query ``[Draft]`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This API is called by the HS to query the existence of a room alias on the -Application Service's namespace. - -Inputs: - - Room alias - - HS Credentials -Output: - - Whether the room exists. -Side effects: - - Room is created on the HS by the AS via CS APIs during the processing of - this request. -API called when: - - HS receives an event to join a room alias in the AS's namespace. -Notes: - - When the AS receives this request, if the room exists, it must create the room via - the CS API. - - It can also set arbitrary information about the room (e.g. name, topic, etc) - using the CS API. - - It can send messages as other users in order to populate scrollback. - - When this setup is complete, the AS should respond to the HS request. This means the AS - blocks the HS until the room is created and configured. - - This is deemed more flexible than alternative methods (e.g. returning an initial sync - style JSON blob and get the HS to provision the room). It also means that the AS knows - the room ID -> alias mapping. -Retry notes: - - The home server cannot respond to the client's request until the response to - this API is obtained from the AS. - - Recommended that home servers try a few times then time out, returning a - 408 Request Timeout to the client. - -:: - - GET /rooms/$room_alias?access_token=$hs_token - - Returns: - 200 : Room is recognised. - 404 : Room not found. - 401 : Credentials need to be supplied. - 403 : HS credentials rejected. - - - 200 OK response format - - {} - -Pushing ``[Draft]`` -~~~~~~~~~~~~~~~~~~~ -This API is called by the HS when the HS wants to push an event (or batch of -events) to the AS. - -Inputs: - - HS Credentials - - Event(s) to give to the AS - - HS-generated transaction ID -Output: - - None. - -Data flows: - -:: - - Typical - HS ---> AS : Home server sends events with transaction ID T. - <--- : AS sends back 200 OK. - - AS ACK Lost - HS ---> AS : Home server sends events with transaction ID T. - <-/- : AS 200 OK is lost. - HS ---> AS : Home server retries with the same transaction ID of T. - <--- : AS sends back 200 OK. If the AS had processed these events - already, it can NO-OP this request (and it knows if it is the same - events based on the transacton ID). - - -Retry notes: - - If the HS fails to pass on the events to the AS, it must retry the request. - - Since ASes by definition cannot alter the traffic being passed to it (unlike - say, a Policy Server), these requests can be done in parallel to general HS - processing; the HS doesn't need to block whilst doing this. - - Home servers should use exponential backoff as their retry algorithm. - - Home servers MUST NOT alter (e.g. add more) events they were going to - send within that transaction ID on retries, as the AS may have already - processed the events. - -Ordering notes: - - The events sent to the AS should be linearised, as they are from the event - stream. - - The home server will need to maintain a queue of transactions to send to - the AS. - -:: - - PUT /transactions/$transaction_id?access_token=$hs_token - - Request format - { - events: [ - ... - ] - } - -Client-Server v2 API Extensions -------------------------------- - -Identity assertion -~~~~~~~~~~~~~~~~~~ -The client-server API infers the user ID from the ``access_token`` provided in -every request. It would be an annoying amount of book-keeping to maintain tokens -for every virtual user. It would be preferable if the application service could -use the CS API with its own ``as_token`` instead, and specify the virtual user -they wish to be acting on behalf of. For real users, this would require -additional permissions granting the AS permission to masquerade as a matrix user. - -Inputs: - - Application service token (``access_token``) - - Either: - - User ID in the AS namespace to act as. - Or: - - OAuth2 token of real user (which may end up being an access token) -Notes: - - This will apply on all aspects of the CS API, except for Account Management. - - The ``as_token`` is inserted into ``access_token`` which is usually where the - client token is. This is done on purpose to allow application services to - reuse client SDKs. - -:: - - /path?access_token=$token&user_id=$userid - - Query Parameters: - access_token: The application service token - user_id: The desired user ID to act as. - - /path?access_token=$token&user_token=$token - - Query Parameters: - access_token: The application service token - user_token: The token granted to the AS by the real user - -Timestamp massaging -~~~~~~~~~~~~~~~~~~~ -The application service may want to inject events at a certain time (reflecting -the time on the network they are tracking e.g. irc, xmpp). Application services -need to be able to adjust the ``origin_server_ts`` value to do this. - -Inputs: - - Application service token (``as_token``) - - Desired timestamp -Notes: - - This will only apply when sending events. - -:: - - /path?access_token=$token&ts=$timestamp - - Query Parameters added to the send event APIs only: - access_token: The application service token - ts: The desired timestamp - -Server admin style permissions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The home server needs to give the application service *full control* over its -namespace, both for users and for room aliases. This means that the AS should -be able to create/edit/delete any room alias in its namespace, as well as -create/delete any user in its namespace. No additional API changes need to be -made in order for control of room aliases to be granted to the AS. Creation of -users needs API changes in order to: - -- Work around captchas. -- Have a 'passwordless' user. - -This involves bypassing the registration flows entirely. This is achieved by -including the AS token on a ``/register`` request, along with a login type of -``m.login.application_service`` to set the desired user ID without a password. - -:: - - /register?access_token=$as_token - - Content: - { - type: "m.login.application_service", - user: "" - } - -Application services which attempt to create users or aliases *outside* of -their defined namespaces will receive an error code ``M_EXCLUSIVE``. Similarly, -normal users who attempt to create users or alises *inside* an application -service-defined namespace will receive the same ``M_EXCLUSIVE`` error code. - -ID conventions ``[Draft]`` --------------------------- -.. NOTE:: - - Giving HSes the freedom to namespace still feels like the Right Thing here. - - Exposing a public API provides the consistency which was the main complaint - against namespacing. - - This may have knock-on effects for the AS registration API. E.g. why don't - we let ASes specify the *URI* regex they want? - -This concerns the well-defined conventions for mapping 3P network IDs to matrix -IDs, which we expect clients to be able to do by themselves. - -User IDs -~~~~~~~~ -Matrix users may wish to directly contact a virtual user, e.g. to send an email. -The URI format is a well-structured way to represent a number of different ID -types, including: - -- MSISDNs (``tel``) -- Email addresses (``mailto``) -- IRC nicks (``irc`` - https://tools.ietf.org/html/draft-butcher-irc-url-04) -- XMPP (xep-0032) -- SIP URIs (RFC 3261) - -As a result, virtual user IDs SHOULD relate to their URI counterpart. This -mapping from URI to user ID can be expressed in a number of ways: - -- Expose a C-S API on the HS which takes URIs and responds with user IDs. -- Munge the URI with the user ID. - -Exposing an API would allow HSes to internally map user IDs however they like, -at the cost of an extra round trip (of which the response can be cached). -Munging the URI would allow clients to apply the mapping locally, but would force -user X on service Y to always map to the same munged user ID. Considering the -exposed API could just be applying this munging, there is more flexibility if -an API is exposed. - -:: - - GET /_matrix/app/v1/user?uri=$url_encoded_uri - - Returns 200 OK: - { - user_id: - } - -Room Aliases -~~~~~~~~~~~~ -We may want to expose some 3P network rooms so Matrix users can join them directly, -e.g. IRC rooms. We don't want to expose every 3P network room though, e.g. mailto, -tel. Rooms which are publicly accessible (e.g. IRC rooms) can be exposed as an alias by -the application service. Private rooms (e.g. sending an email to someone) should not -be exposed in this way, but should instead operate using normal invite/join semantics. -Therefore, the ID conventions discussed below are only valid for public rooms which -expose room aliases. - -Matrix users may wish to join XMPP rooms (e.g. using XEP-0045) or IRC rooms. In both -cases, these rooms can be expressed as URIs. For consistency, these "room" URIs -SHOULD be mapped in the same way as "user" URIs. - -:: - - GET /_matrix/app/v1/alias?uri=$url_encoded_uri - - Returns 200 OK: - { - alias: - } - -Event fields -~~~~~~~~~~~~ -We recommend that any gatewayed events should include an ``external_url`` field -in their content to provide a way for Matrix clients to link into the 'native' -client from which the event originated. For instance, this could contain the -message-ID for emails/nntp posts, or a link to a blog comment when gatewaying -blog comment traffic in & out of matrix - - -Examples --------- -.. NOTE:: - - User/Alias namespaces are subject to change depending on ID conventions. - -IRC -~~~ -Pre-conditions: - - Server admin stores the AS token "T_a" on the home server. - - Home server has a token "T_h". - - Home server has the domain "hsdomain.com" - -1. Application service registration - -:: - - AS -> HS: Registers itself with the home server - POST /register - { - url: "https://someapp.com/matrix", - as_token: "T_a", - namespaces: { - users: [ - { - "exclusive": true, - "regex": "@irc\.freenode\.net/.*" - } - ], - aliases: [ - { - "exclusive": true, - "regex": "#irc\.freenode\.net/.*" - } - ] - } - } - - Returns 200 OK: - { - hs_token: "T_h" - } - -2. IRC user "Bob" says "hello?" on "#matrix" at timestamp 1421416883133: - -:: - - - AS stores message as potential scrollback. - - Nothing happens as no Matrix users are in the room. - -3. Matrix user "@alice:hsdomain.com" wants to join "#matrix": - -:: - - User -> HS: Request to join "#irc.freenode.net/#matrix:hsdomain.com" - - HS -> AS: Room Query "#irc.freenode.net/#matrix:hsdomain.com" - GET /rooms/%23irc.freenode.net%2F%23matrix%3Ahsdomain.com?access_token=T_h - [Starts blocking] - AS -> HS: Creates room. Gets room ID "!aasaasasa:hsdomain.com". - AS -> HS: Sets room name to "#matrix". - AS -> HS: Sends message as ""@irc.freenode.net/Bob:hsdomain.com" - PUT /rooms/%21aasaasasa%3Ahsdomain.com/send/m.room.message - ?access_token=T_a - &user_id=%40irc.freenode.net%2FBob%3Ahsdomain.com - &ts=1421416883133 - { - body: "hello?" - msgtype: "m.text" - } - HS -> AS: User Query "@irc.freenode.net/Bob:hsdomain.com" - GET /users/%40irc.freenode.net%2FBob%3Ahsdomain.com?access_token=T_h - [Starts blocking] - AS -> HS: Creates user using CS API extension. - POST /register?access_token=T_a - { - type: "m.login.application_service", - user: "irc.freenode.net/Bob" - } - AS -> HS: Set user display name to "Bob". - [Finishes blocking] - [Finished blocking] - - - HS sends room information back to client. - -4. @alice:hsdomain.com says "hi!" in this room: - -:: - - User -> HS: Send message "hi!" in room !aasaasasa:hsdomain.com - - - HS sends message. - - HS sees the room ID is in the AS namespace and pushes it to the AS. - - HS -> AS: Push event - PUT /transactions/1?access_token=T_h - { - events: [ - { - content: { - body: "hi!", - msgtype: "m.text" - }, - origin_server_ts: , - user_id: "@alice:hsdomain.com", - room_id: "!aasaasasa:hsdomain.com", - type: "m.room.message" - } - ] - } - - - AS passes this through to IRC. - - -5. IRC user "Bob" says "what's up?" on "#matrix" at timestamp 1421418084816: - -:: - - IRC -> AS: "what's up?" - AS -> HS: Send message via CS API extension - PUT /rooms/%21aasaasasa%3Ahsdomain.com/send/m.room.message - ?access_token=T_a - &user_id=%40irc.freenode.net%2FBob%3Ahsdomain.com - &ts=1421418084816 - { - body: "what's up?" - msgtype: "m.text" - } - - - HS modifies the user_id and origin_server_ts on the event and sends it. diff --git a/drafts/definitions.rst b/drafts/definitions.rst deleted file mode 100644 index cfb56857bfb..00000000000 --- a/drafts/definitions.rst +++ /dev/null @@ -1,55 +0,0 @@ -Definitions -=========== - -(a relatively recent (Oct 2014) new list of definitions from Erik) - -# *Event* -- A JSON object that represents a piece of information to be -distributed to the the room. The object includes a payload and metadata, -including a ``type`` used to indicate what the payload is for and how to process -them. It also includes one or more references to previous events. - -# *Event graph* -- Events and their references to previous events form a -directed acyclic graph. All events must be a descendant of the first event in a -room, except for a few special circumstances. - -# *State event* -- A state event is an event that has a non-null string valued -`state_key` field. It may also include a ``prev_state`` key referencing exactly -one state event with the same type and state key, in the same event graph. - -# *State tree* -- A state tree is a tree formed by a collection of state events -that have the same type and state key (all in the same event graph. - -# *State resolution algorithm* -- An algorithm that takes a state tree as input -and selects a single leaf node. - -# *Current state event* -- The leaf node of a given state tree that has been -selected by the state resolution algorithm. - -# *Room state* / *state dictionary* / *current state* -- A mapping of the pair -(event type, state key) to the current state event for that pair. - -# *Room* -- An event graph and its associated state dictionary. An event is in -the room if it is part of the event graph. - -# *Topological ordering* -- The partial ordering that can be extracted from the -event graph due to it being a DAG. - -(The state definitions are purposely slightly ill-defined, since if we allow -deleting events we might end up with multiple state trees for a given event -type and state key pair.) - -Federation specific -------------------- -# *(Persistent data unit) PDU* -- An encoding of an event for distribution of -the server to server protocol. - -# *(Ephemeral data unit) EDU* -- A piece of information that is sent between -servers and doesn't encode an event. - -Client specific ---------------- -# *Child events* -- Events that reference a single event in the same room -independently of the event graph. - -# *Collapsed events* -- Events that have all child events that reference it -included in the JSON object. diff --git a/drafts/design_workflow.rst b/drafts/design_workflow.rst deleted file mode 100644 index 32a376551d9..00000000000 --- a/drafts/design_workflow.rst +++ /dev/null @@ -1,14 +0,0 @@ -Matrix Spec Design Workflow -=========================== - -1. Write use cases - -2. Design data flows for use cases - -3. Design generic API (factoring out commonalities where possible) - -4. Design transport-specific API with justifications - -5. Formalise transport-specific API as swagger or similar - -6. Evolve the generic API design doc and transport-specific API into the actual spec. \ No newline at end of file diff --git a/drafts/erikj_federation.rst b/drafts/erikj_federation.rst index 0644f27105b..ce089def7c5 100644 --- a/drafts/erikj_federation.rst +++ b/drafts/erikj_federation.rst @@ -237,7 +237,7 @@ Domain specific string ``room_id`` A domain specific string with prefix ``!`` that is static across all events in a graph and uniquely identifies it. The ``domain`` should be that of the - home server that created the room (i.e., the server that generated the + homeserver that created the room (i.e., the server that generated the first ``m.room.create`` event). ``sender`` @@ -246,7 +246,7 @@ Domain specific string User Id A domain specific string with prefix ``@`` representing a user account. The - ``domain`` is the home server of the user and is the server used to contact + ``domain`` is the homeserver of the user and is the server used to contact the user. Joining a room @@ -280,7 +280,7 @@ can then process the join event itself. Inviting a user --------------- -To invite a remote user to a room we need their home server to sign the invite +To invite a remote user to a room we need their homeserver to sign the invite event. This is done by sending the event to the remote server, which then signs the event, before distributing the invite to other servers. diff --git a/drafts/event_schema.yaml b/drafts/event_schema.yaml deleted file mode 100644 index 2a6e4ed4250..00000000000 --- a/drafts/event_schema.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# JSON Schema for Matrix events. http://json-schema.org/ -title: "Matrix Event" -type: object -properties: - auth_events: - description: The events needed to authenticate this event - $ref: "#/definitions/event_reference_list" - content: - type: object - description: The body of the event. - depth: - type: integer - description: A number one greater than that of any preceeding event. - minimum: 0 - event_id: - $ref: "#/definitions/event_id" - hashes: - $ref: "#/definitions/hashes" - origin: - $ref: "#/definitions/server_id" - origin_server_ts: - type: integer - description: Posix timestamp on the originating server - minimum: 0 - prev_events: - description: The event(s) this event came after. - $ref: "#/definitions/event_reference_list" - prev_state: - description: The state event(s) this event replaces. - $ref: "#/definitions/event_reference_list" - room_id: - $ref: "#/definitions/room_id" - sender: - oneOf: - - $ref: "#/definitions/user_id" - - $ref: "#/definitions/server_id" - state_key: - type: string - type: - type: string - unsigned: - type: object - -required: -- auth_events -- content -- depth -- event_id -- hashes -- origin -- origin_server_ts -- prev_events -- room_id -- sender -- type - -dependencies: - state_key: - - prev_state - prev_state: - - state_key - -definitions: - server_id: - type: string - description: Identifies a server. - pattern: "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*\ - ([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\ - (:[0-9]*[0-9])?$" - event_id: - type: string - description: Identifies an event. - pattern: "^\\$[^:]+:\ - (([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*\ - ([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\ - (:[0-9]*[0-9])?$" - room_id: - type: string - description: Identifies a room. - pattern: "^\\![^:]+:\ - (([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*\ - ([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\ - (:[0-9]*[0-9])?$" - user_id: - type: string - description: Identifies a user. - pattern: "^\\@[^:]+:\ - (([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*\ - ([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\ - (:[0-9]*[0-9])?$" - base64: - type: string - description: Base64 string without padding. - pattern: "^[a-zA-Z0-9/+]*$" - hashes: - type: object - description: Hashes - minProperties: 1 - additionalProperties: - $ref: "#/definitions/base64" - signatures: - type: object - description: Signatures - additionalProperties: - type: object - minProperties: 1 - additionalProperties: - $ref: "#/definitions/base64" - event_reference: - type: array - minItems: 2 - maxItems: 2 - items: - - type: string - description: Event id of the referenced event. - $ref: "#/definitions/event_id" - - type: object - description: Reference hashes of the referenced event. - $ref: "#/definitions/hashes" - event_reference_list: - type: array - items: - $ref: "#/definitions/event_reference" diff --git a/drafts/general_api.rst b/drafts/general_api.rst index ae2446d63d3..baad39017db 100644 --- a/drafts/general_api.rst +++ b/drafts/general_api.rst @@ -61,7 +61,7 @@ This version will change the path prefix for HTTP: - Version 2: ``/_matrix/client/v2`` Note the lack of the ``api`` segment. This is for consistency between other -home server path prefixes. +homeserver path prefixes. Terminology: - ``Chunk token`` : An opaque string which can be used to return another chunk @@ -169,16 +169,16 @@ Outputs: ``content`` key. Deleted message events are ``m.room.redaction`` events. - New position in the stream. (chunk token) State Events Ordering Notes: - - Home servers may receive state events over federation that are superceded by - state events previously sent to the client. The home server *cannot* send + - Homeservers may receive state events over federation that are superceded by + state events previously sent to the client. The homeserver *cannot* send these events to the client else they would end up erroneously clobbering the superceding state event. - - As a result, the home server reserves the right to omit sending state events + - As a result, the homeserver reserves the right to omit sending state events which are known to be superceded already. - This may result in missed *state* events. However, the state of the room will always be eventually consistent. Message Events Ordering Notes: - - Home servers may receive message events over federation that happened a long + - Homeservers may receive message events over federation that happened a long time ago. The client may or may not be interested in these message events. - For clients which do not store scrollback for a room (they discard events after processing them), this is not a problem as they only care about the @@ -191,11 +191,11 @@ Message Events Ordering Notes: - The event, when it comes down the stream, will indicate which event it comes after. Rejected events: - - A home server may find out via federation that it should not have accepted + - A homeserver may find out via federation that it should not have accepted an event (e.g. to send a message/state event in a room). For example, it may - send an event to another home server and receive an auth event stating + send an event to another homeserver and receive an auth event stating that the event should not have been sent. - - If this happens, the home server will send a ``m.room.redaction`` for the + - If this happens, the homeserver will send a ``m.room.redaction`` for the event in question. This will be a local server event (not shared with other servers). - If the event was a state event, it will synthesise a new state event to @@ -206,7 +206,7 @@ Unknown rooms: - You could receive events for rooms you are unaware of (e.g. you didn't do an initial sync, or your HS lost its database and is told from another HS that they are in this room). How do you handle this? - - The simplest option would be to redo the initial sync with a filter on the + - The simplest option would be to redo the initial sync with a filter on the room ID you're unaware of. This would retrieve the room state so you can display the room. What data flows does it address: @@ -291,7 +291,7 @@ Scrollback API ``[Draft]`` but as a purely informational display thing it would be nice. Additional Inputs: - - flag to say if the home server should do a backfill over federation + - flag to say if the homeserver should do a backfill over federation Additional Outputs: - whether there are more events on the local HS / over federation. What data flows does it address: @@ -313,7 +313,7 @@ Additional Outputs: Room Alias API ``[Draft]`` -------------------------- This provides mechanisms for creating and removing room aliases for a room on a -home server. Typically, any user in a room can make an alias for that room. The +homeserver. Typically, any user in a room can make an alias for that room. The alias creator (or anyone in the room?) can delete that alias. Server admins can also delete any alias on their server. @@ -323,7 +323,7 @@ Inputs: - Room Alias Output: - Room ID - - List of home servers to join via. + - List of homeservers to join via. Mapping a room to an alias: @@ -334,7 +334,7 @@ Inputs: Output: - Room alias Notes: - - The home server may add restrictions e.g. the user must be in the room. + - The homeserver may add restrictions e.g. the user must be in the room. Deleting a mapping: @@ -347,11 +347,11 @@ Output: Published room list API ``[Draft]`` ----------------------------------- -This provides mechanisms for searching for published rooms on a home server. +This provides mechanisms for searching for published rooms on a homeserver. Inputs: - Search text (e.g. room alias/name/topic to search on) - - Home server to search on (this may just be the URL hit for HTTP) + - Homeserver to search on (this may just be the URL hit for HTTP) - Any existing pagination token, can be missing if this is the first hit. - Limit for pagination Output: @@ -378,7 +378,7 @@ Notes: User Profile API ``[Draft]`` ---------------------------- -Every user on a home server has a profile. This profile is effectively a +Every user on a homeserver has a profile. This profile is effectively a key-value store scoped to a user ID. It can include an ``avatar_url``, ``displayname`` and other metadata. Updates to a profile should propagate to other interested users. @@ -435,7 +435,7 @@ This had a number of problems associated with it: flicker. - Name/avatar changes created more ``m.room.member`` events which meant they needed to be included in the auth chains for federation. This - created long auth chains which is suboptimal since home servers need + created long auth chains which is suboptimal since homeservers need to store the auth chains forever. These problems can be resolved by creating an ``m.room.member.profile`` @@ -448,7 +448,7 @@ However, this introduces its own set of problems, namely flicker. The client would receive the ``m.room.member`` event first, followed by the ``m.room.member.profile`` event, which could cause a flicker. In addition, federation may not send both events in a single transaction, -resulting in missing information on the receiving home server. +resulting in missing information on the receiving homeserver. For federation, these problems can be resolved by sending the ``m.room.member`` event as they are in v1 (with ``displayname`` and @@ -457,7 +457,7 @@ they cannot be in the ``unsigned`` part of the event. The receiving home server will then extract these keys and create a server-generated ``m.room.member.profile`` event. To avoid confusion with duplicate information, the ``avatar_url`` and ``displayname`` keys should be -removed from the ``m.room.member`` event by the receiving home server. +removed from the ``m.room.member`` event by the receiving homeserver. When a client requests these events (either from the event stream or from an initial sync), the server will send the generated ``m.room.member.profile`` event under the ``unsigned.profile`` key of the @@ -840,17 +840,17 @@ information per device to all other users just redirects the union problem to the client, which will commonly be presenting this information as an icon alongside the user. -When a client hits the event stream, the home server can treat the user as +When a client hits the event stream, the homeserver can treat the user as "online". This behaviour should be able to be overridden to avoid flicker during connection losses when the client is appear offline (e.g. device is appear offline > goes into a tunnel > server times out > device regains connection and hits the event stream forcing the device online before the "appear offline" state can be set). When the client has not hit the event -stream for a certain period of time, the home server can treat the user as +stream for a certain period of time, the homeserver can treat the user as "offline". The user can also set a global *per-user* appear offline flag. The user should also be able to set their presence state via a direct API, -without having to hit the event stream. The home server will set a timer when +without having to hit the event stream. The homeserver will set a timer when the connection ends, after which it will set that device to offline. As the idle flag and online state is determined per device, there needs to be a @@ -970,12 +970,12 @@ the hashes the same is the best as that means clients do not need to request the capabilities for the given hash. On first signup, the client will attempt to send the hash and be most likely -refused by the home server as it does not know the full capability set for that +refused by the homeserver as it does not know the full capability set for that hash. The client will then have to upload the full capability set to the home server. The client will then be able to send the hash as normal. When a client receives a hash, the client will either recognise the hash or -will have to request the capability set from their home server: +will have to request the capability set from their homeserver: Inputs: - Hash @@ -1070,7 +1070,7 @@ Main use cases for ``updates``: - Call signalling (child events are ICE candidates, answer to the offer, and termination) - *Local* Delivery/Read receipts : "Local" means they are not shared with other - users on the same home server or via federation but *are* shared between + users on the same homeserver or via federation but *are* shared between clients for the same user; useful for push notifications, read count markers, etc. This is done to avoid the ``n^2`` problem for sending receipts, where the vast majority of traffic tends towards sending more receipts. @@ -1168,11 +1168,11 @@ Events (breaking changes; event version 2) ``[Draft]`` when dealing with custom event types. E.g. ``_custom.event`` would allow anything in the state key, ``_@custom.event`` would only allow user IDs in the state key, etc. -- s/user_id/sender/g given that home servers can send events, not just users. +- s/user_id/sender/g given that homeservers can send events, not just users. Server-generated events ``[Draft]`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Home servers may want to send events to their local clients or to other home +Homeservers may want to send events to their local clients or to other home servers e.g. for server status notifications. These events look like regular events but have a server domain name as the diff --git a/drafts/human-id-rules.rst b/drafts/human-id-rules.rst index 3eb1cbee09f..b77d310d728 100644 --- a/drafts/human-id-rules.rst +++ b/drafts/human-id-rules.rst @@ -32,7 +32,7 @@ These checks are: User ID Localparts: - MUST NOT contain a ``:`` or start with a ``@`` or ``.`` - - MUST NOT contain one of the 107 blacklisted characters on this list: + - MUST NOT contain one of the 107 blacklisted characters on this list: http://kb.mozillazine.org/Network.IDN.blacklist_chars - After stripping " 0-9, +, -, [, ], _, and the space character it MUST NOT contain characters from >1 language, defined by the `exemplar characters`_ @@ -42,7 +42,7 @@ User ID Localparts: Room Alias Localparts: - MUST NOT contain a ``:`` - - MUST NOT contain one of the 107 blacklisted characters on this list: + - MUST NOT contain one of the 107 blacklisted characters on this list: http://kb.mozillazine.org/Network.IDN.blacklist_chars - After stripping " 0-9, +, -, [, ], _, and the space character it MUST NOT contain characters from >1 language, defined by the `exemplar characters`_ @@ -74,14 +74,14 @@ http://cldr.unicode.org/ This check SHOULD be applied when the user ID is created, in order to prevent registration with the same name and different capitalisations, e.g. -``@foo:bar`` vs ``@Foo:bar`` vs ``@FOO:bar``. Home servers MAY canonicalise +``@foo:bar`` vs ``@Foo:bar`` vs ``@FOO:bar``. Homeservers MAY canonicalise the user ID to be completely lower-case if desired. Rationale ========= -Each ID is split into segments (localpart/domain) around the ``:``. For -this reason, ``:`` is a reserved character and cannot be a localpart character. +Each ID is split into segments (localpart/domain) around the ``:``. For +this reason, ``:`` is a reserved character and cannot be a localpart character. The 107 blacklisted characters are used to prevent non-printable characters and spaces from being used. The decision to ban characters from more than 1 language matches the behaviour of `Google Chrome for IDN handling`_. This is to protect @@ -98,7 +98,7 @@ fail the ID checks. A failed ID could look like ``@@xn--c1yn36f:domain.com``. If a user ID fails the check, the user ID on the event is renamed. This doesn't require extra work for clients, and users will see an odd user ID rather than a -spoofed name. Renaming is done in order to protect users of a given HS, so if a +spoofed name. Renaming is done in order to protect users of a given HS, so if a malicious HS doesn't rename their IDs, it doesn't affect any other HS. Room aliases cannot be rewritten as punycode and sent to the HS the alias is @@ -115,7 +115,7 @@ Other rejected solutions for failed checks - Reject event: Outright rejection of the ID at the point of creation / receiving event. Point of creation rejection is preferable to avoid the ID entering the system in the first place. However, malicious HSes can just - allow the ID. Hence, other home servers must reject them if they see them in + allow the ID. Hence, other homeservers must reject them if they see them in events. Client never sees the problem ID, provided the HS is correctly implemented. However, it is difficult to ensure that ALL HSes will come to the same conclusion (given the CLDR dataset does come out with new versions). diff --git a/drafts/macaroons_caveats.rst b/drafts/macaroons_caveats.rst deleted file mode 100644 index 93622c3d400..00000000000 --- a/drafts/macaroons_caveats.rst +++ /dev/null @@ -1,34 +0,0 @@ -Macaroon Caveats -================ - -`Macaroons`_ are issued by Matrix servers as authorization tokens. Macaroons may be restricted by adding caveats to them. - -.. _Macaroons: http://theory.stanford.edu/~ataly/Papers/macaroons.pdf - -Caveats can only be used for reducing the scope of a token, never for increasing it. Servers are required to reject any macroon with a caveat that they do not understand. - -Some caveats are specified in this specification, and must be understood by all servers. The use of non-standard caveats is allowed. - -All caveats must take the form: - -`key` `operator` `value` -where `key` is a non-empty string drawn from the character set [A-Za-z0-9_] -`operator` is a non-empty string which does not contain whitespace -`value` is a non-empty string -And these are joined by single space characters. - -Specified caveats: - -+-------------+--------------------------------------------------+------------------------------------------------------------------------------------------------+ -| Caveat name | Description | Legal Values | -+-------------+--------------------------------------------------+------------------------------------------------------------------------------------------------+ -| gen | Generation of the macaroon caveat spec. | 1 | -| user_id | ID of the user for which this macaroon is valid. | Pure equality check. Operator must be =. | -| type | The purpose of this macaroon. | access - used to authorize any action except token refresh | -| refresh - only used to authorize a token refresh | -| time | Time before/after which this macaroon is valid. | A POSIX timestamp in milliseconds (in UTC). | -| Operator < means the macaroon is valid before the timestamp, as interpreted by the server. | -| Operator > means the macaroon is valid after the timestamp, as interpreted by the server. | -| Operator == means the macaroon is valid at exactly the timestamp, as interpreted by the server.| -| Note that exact equality of time is largely meaningless. | -+-------------+--------------------------------------------------+------------------------------------------------------------------------------------------------+ diff --git a/drafts/model/presence.rst b/drafts/model/presence.rst index bd60cba0fcd..c2c88737bf7 100644 --- a/drafts/model/presence.rst +++ b/drafts/model/presence.rst @@ -5,14 +5,14 @@ A simple implementation of presence messaging has the ability to cause a large amount of Internet traffic relating to presence updates. In order to minimise the impact of such a feature, the following observations can be made: - * There is no point in a Home Server polling status for peers in a user's + * There is no point in a homeserver polling status for peers in a user's presence list if the user has no clients connected that care about it. * It is highly likely that most presence subscriptions will be symmetric - a given user watching another is likely to in turn be watched by that user. * It is likely that most subscription pairings will be between users who share - at least one Room in common, and so their Home Servers are actively + at least one Room in common, and so their homeservers are actively exchanging message PDUs or transactions relating to that Room. * Presence update messages do not need realtime guarantees. It is acceptable to @@ -25,7 +25,7 @@ promise to send them when required. Rather than actively polling for the current state all the time, HSes can rely on their relative stability to only push updates when required. -A Home Server should not rely on the longterm validity of this presence +A homeserver should not rely on the longterm validity of this presence information, however, as this would not cover such cases as a user's server crashing and thus failing to inform their peers that users it used to host are no longer available online. Therefore, each promise of future updates should @@ -33,7 +33,7 @@ carry with a timeout value (whether explicit in the message, or implicit as some defined default in the protocol), after which the receiving HS should consider the information potentially stale and request it again. -However, because of the likelihood that two home servers are exchanging messages +However, because of the likelihood that two homeservers are exchanging messages relating to chat traffic in a room common to both of them, the ongoing receipt of these messages can be taken by each server as an implicit notification that the sending server is still up and running, and therefore that no status changes @@ -98,7 +98,7 @@ The data model presented here puts the following requirements on the APIs: Client-Server ------------- -Requests that a client can make to its Home Server +Requests that a client can make to its homeserver * get/set current presence state Basic enumeration + ability to set a custom piece of text @@ -128,7 +128,7 @@ Requests that a client can make to its Home Server Server-Server ------------- -Requests that Home Servers make to others +Requests that homeservers make to others * request permission to add a user to presence list diff --git a/drafts/model/profiles.rst b/drafts/model/profiles.rst index 15f65feb962..f81e115eb2c 100644 --- a/drafts/model/profiles.rst +++ b/drafts/model/profiles.rst @@ -9,7 +9,7 @@ Overview ======== Internally within Synapse users are referred to by an opaque ID, which consists -of some opaque localpart combined with the domain name of their home server. +of some opaque localpart combined with the domain name of their homeserver. Obviously this does not yield a very nice user experience; users would like to see readable names for other users that are in some way meaningful to them. Additionally, users like to be able to publish "profile" details to inform other @@ -59,7 +59,7 @@ servers should be accounted for here.]] Visibility Permissions ====================== -A home server implementation could offer the ability to set permissions on +A homeserver implementation could offer the ability to set permissions on limited visibility of those fields. When another user requests access to the target user's profile, their own identity should form part of that request. The HS implementation can then decide which fields to make available to the @@ -130,12 +130,12 @@ namespace to allocate names into. It would also be nice from a user experience perspective if the profile that a given name links to can also declare that name as part of its metadata. Furthermore as a security and consistency perspective it would be nice if each -end (the directory server and the user's home server) check the validity of the +end (the directory server and the user's homeserver) check the validity of the mapping in some way. This needs investigation from a security perspective to ensure against spoofing. One such model may be that the user starts by declaring their intent to use a -given user name link to their home server, which then contacts the directory +given user name link to their homeserver, which then contacts the directory service. At some point later (maybe immediately for "public open FCFS servers", maybe after some kind of human intervention for verification) the DS decides to honour this link, and includes it in its served output. It should also tell the @@ -170,7 +170,7 @@ balancing choice on behalf of the user who would choose, or not, to make use of such a feature to publish their information. Additionally, unless some form of strong end-to-end user-based encryption is -used, a user of ACLs for information privacy has to trust other home servers not +used, a user of ACLs for information privacy has to trust other homeservers not to lie about the identify of the user requesting access to the Profile. @@ -182,7 +182,7 @@ The data model presented here puts the following requirements on the APIs: Client-Server ------------- -Requests that a client can make to its Home Server +Requests that a client can make to its homeserver * get/set my Display Name This should return/take a simple "text/plain" field @@ -207,7 +207,7 @@ TODO(paul): At some later stage we should consider the API for: Server-Server ------------- -Requests that Home Servers make to others +Requests that homeservers make to others * get a user's Display Name / Avatar @@ -221,7 +221,7 @@ Requests that Home Servers make to others Room Event PDU Types -------------------- -Events that are pushed from Home Servers to other Home Servers or clients. +Events that are pushed from homeservers to other homeservers or clients. * user Display Name change diff --git a/drafts/model/room-join-workflow.rst b/drafts/model/room-join-workflow.rst index c321a64fabf..ff469740c45 100644 --- a/drafts/model/room-join-workflow.rst +++ b/drafts/model/room-join-workflow.rst @@ -8,7 +8,7 @@ Discovery ========= To join a room, a user has to discover the room by some mechanism in order to -obtain the (opaque) Room ID and a candidate list of likely home servers that +obtain the (opaque) Room ID and a candidate list of likely homeservers that contain it. Sending an Invitation @@ -21,7 +21,7 @@ The inviter's HS sets the membership status of the invitee to "invited" in the "m.members" state key by sending a state update PDU. The HS then broadcasts this PDU among the existing members in the usual way. An invitation message is also sent to the invited user, containing the Room ID and the PDU ID of this -invitation state change and potentially a list of some other home servers to use +invitation state change and potentially a list of some other homeservers to use to accept the invite. The user's client can then choose to display it in some way to alert the user. @@ -34,7 +34,7 @@ Directory Service Alternatively, the user may discover the channel via a directory service; either by performing a name lookup, or some kind of browse or search acitivty. However -this is performed, the end result is that the user's home server requests the +this is performed, the end result is that the user's homeserver requests the Room ID and candidate list from the directory service. [[TODO(paul): At present, no API has been designed or described for this @@ -44,14 +44,14 @@ directory service]] Joining ======= -Once the ID and home servers are obtained, the user can then actually join the +Once the ID and homeservers are obtained, the user can then actually join the room. Accepting an Invite ------------------- If a user has received and accepted an invitation to join a room, the invitee's -home server can now send an invite acceptance message to a chosen candidate +homeserver can now send an invite acceptance message to a chosen candidate server from the list given in the invitation, citing also the PDU ID of the invitation as "proof" of their invite. (This is required as due to late message propagation it could be the case that the acceptance is received before the @@ -85,7 +85,7 @@ can instead post a "knock" message, which informs other members of the room that the would-be joiner wishes to become a member and sets their membership value to "knocked". If any of them wish to accept this, they can then send an invitation in the usual way described above. Knowing that the user has already knocked and -expressed an interest in joining, the invited user's home server should +expressed an interest in joining, the invited user's homeserver should immediately accept that invitation on the user's behalf, and go on to join the room in the usual way. diff --git a/drafts/model/rooms.rst b/drafts/model/rooms.rst index 07c5e71f91a..2f57f2bc4bf 100644 --- a/drafts/model/rooms.rst +++ b/drafts/model/rooms.rst @@ -18,19 +18,19 @@ users, and other management and miscellaneous metadata), and a message history. Room Identity and Naming ======================== -Rooms can be arbitrarily created by any user on any home server; at which point -the home server will sign the message that creates the channel, and the +Rooms can be arbitrarily created by any user on any homeserver; at which point +the homeserver will sign the message that creates the channel, and the fingerprint of this signature becomes the strong persistent identify of the -room. This now identifies the room to any home server in the network regardless +room. This now identifies the room to any homeserver in the network regardless of its original origin. This allows the identify of the room to outlive any particular server. Subject to appropriate permissions [to be discussed later], any current member of a room can invite others to join it, can post messages that become part of its history, and can change the persistent state of the room (including its current set of permissions). -Home servers can provide a directory service, allowing a lookup from a +Homeservers can provide a directory service, allowing a lookup from a convenient human-readable form of room label to a room ID. This mapping is -scoped to the particular home server domain and so simply represents that server +scoped to the particular homeserver domain and so simply represents that server administrator's opinion of what room should take that label; it does not have to be globally replicated and does not form part of the stored state of that room. @@ -156,7 +156,7 @@ m.public_history to be a member of the room. m.archive_servers - For "public" rooms with public history, gives a list of home servers that + For "public" rooms with public history, gives a list of homeservers that should be included in message distribution to the room, even if no users on that server are present. These ensure that a public room can still persist even if no users are currently members of it. This list should be consulted by @@ -179,7 +179,7 @@ m.topic Room Creation Templates ======================= -A client (or maybe home server?) could offer a few templates for the creation of +A client (or maybe homeserver?) could offer a few templates for the creation of new rooms. For example, for a simple private one-to-one chat the channel could assign the creator a power-level of 1, requiring a level of 1 to invite, and needing an invite before members can join. An invite is then sent to the other @@ -215,7 +215,7 @@ permissions to allow this direct messaging. Between any given pair of user IDs that wish to exchange private messages, there will exist a single shared Room, created lazily by either side. These rooms will -need a certain amount of special handling in both home servers and display on +need a certain amount of special handling in both homeservers and display on clients, but as much as possible should be treated by the lower layers of code the same as other rooms. @@ -226,7 +226,7 @@ clients should display these in a special way too as the room name is not important; instead it should distinguish them on the Display Name of the other party. -Home Servers will need a client-API option to request setting up a new user-user +Homeservers will need a client-API option to request setting up a new user-user chat room, which will then need special handling within the server. It will create a new room with the following @@ -260,7 +260,7 @@ history with each other simultaneously create a room and invite the other to it. This is called a "glare" situation. There are two possible ideas for how to resolve this: - * Each Home Server should persist the mapping of (user ID pair) to room ID, so + * Each homeserver should persist the mapping of (user ID pair) to room ID, so that duplicate requests can be suppressed. On receipt of a room creation request that the HS thinks there already exists a room for, the invitation to join can be rejected if: diff --git a/drafts/model/third-party-id.rst b/drafts/model/third-party-id.rst index 1f8138ddf7f..838a6799388 100644 --- a/drafts/model/third-party-id.rst +++ b/drafts/model/third-party-id.rst @@ -66,7 +66,7 @@ Privacy A User may publish the association between their phone number and Matrix User ID on the Identity Server without publishing the number in their Profile hosted on -their Home Server. +their homeserver. Identity Servers should refrain from publishing reverse mappings and should take steps, such as rate limiting, to prevent attackers enumerating the space of diff --git a/drafts/reputation_thoughts.rst b/drafts/reputation_thoughts.rst new file mode 100644 index 00000000000..ae427f2d47a --- /dev/null +++ b/drafts/reputation_thoughts.rst @@ -0,0 +1,76 @@ +(a stream of incoherent consciousness from matthew which should go somewhere) + +Invite-graph based reputation data: + +* Users need a reputation score to issue invites or join public rooms. +* A user can have many reputation scores in different audiences (and perhaps a global average?) +* A room (degenerate case: user) can align itself with a given audience in order to consume the reputation data for that audience. +* The people that a user invites inherits a proportion of their reputation. +* If your reputation in an audience is ever reduced, it similarly reduces the reputation you have ever conveyed to anyone else (which propagates through the invite graph). +* Users increase reputation by: + * Inviting someone. + * Upvoting their messages in a room (i.e. for the suitability of that audience) +* Users decrease reputation by: + * Blocking them. + * Downvoting their messages in a room (i.e. for the suitability of that audience) + +Need to ensure the accounts are of a decent quality - making it harder to create sockpuppet accounts and associating them with real people is more important than the actual reputation problem. + +Build a war game simulation to test? + + +Problems: + * How are audiences defined? Just a given unique set of users? Which then makes inheriting reputation easy between audiences - if the overlap is significant, the chances are the reputation rules are the same. + * But is it possible to have the same set of users in two different rooms have different rules for reputation? Probably yes, as the potential audience may include future invitees or indeed the general public, so history visibility rules should probably also contribute to this. But given privacy rules can change over time, each room should effectively define its own audience. So in the end, an audience === a room. + * Create a large network of fake users, and go and have them all vote up each other's score for a given audience. + * This can be solved if the root inviter is penalised, which then destroys all the reputation they conveyed to their graph. + +Could Reputation == Power Level (!?!?!) + +Inheritence semantics for reputation between different audiences is hard. + * You should base the reputation of a stranger on their reputation in other communities that you or your communities have some overlap with. + * Do you consider 2nd hand reputation data at all from private rooms? Or do you look only at the public reputation data? + +How do you do these calculations in a byzantine world? + +How do you do these calculations whilst preserving privacy? + * Only consider reputation data from rooms you are actually in? + * Store reputation data in room state? + * Have a function (HS? client? AS? spider?) that aggregates reputation data (and proves that the aggregation is accurate, almost like blockchain mining?)? + * Or have a separate reputation global db seperate from room state that people contribute metrics into (which gathers the aggregate data into a single place, and makes it easier to query reputation data for strangers) + +How do you avoid backstabbing? (People maliciously ganging up on someone to downvote them)? + +How do you avoid a voting war? (Community fragments; different factions turn up and try to downvote the other)? + * This is effectively two different audiences emerging in a single room. + * Perhaps this means we should model audiences separately from rooms. + * Perhaps audiences are literally ACL groups? And eventually, one might change the ACLs of a room to eject one of the groups? + * Or do you just synthesise audiences based on cliques of people who support each other? The act of upvoting someone is effectively aligning yourself as being part of the same audience? + + +So: + * Gather all public upvote/downvotes/invites/blocks in a global DB. + * Partition this into audiences based on who votes on who. Stuff which is read and not complained about could provide a small implicit approval? Although this makes it easy to flood content to boost your reputation, so bad idea. + * Partitioning algorithm could be quite subtle. + * You could end up with lots of small audiences (including invalid ones), and it's fairly unclear how they get aggregated into a single view. How should you treat a stranger who you have no audience-overlap with at all? Treat them as effectively having zero reputation from your perspective? + +Problem: + * If the douchebag who invites spammers never says anything, how do you go vote on their reputation? Should there be some kind of back-propagation? Or is there explicitly a "this person invited a douchebag" downvote? Or hang on - how can they ever get reputation in the first place to invite their sockpuppets if they don't say anything (beyond the initial invite)? + * What if users simply don't talk in public? Is it right that we prevent them issuing invites just because they stick to private rooms? What about inviting people into those private rooms? I guess the point is that if these are public invites, then they need to have some kind of public reputation, or rely on out-of-band private invitation to establish trust? + * Are we rewarding people who don't change their habits? There's no time component considered here, and we punish people's entire history of invites and rep if they misbehave. The only way to escape is to create a new identity atm. Is this a feature or a bug? + * How does this handle people's accounts getting 0wn3d and doing things which wipe out their reputation? => This is always a risk; ignore it. + * Do you need a particular level of reputation to be able to vote on people? + +Summary? + * Partition the global population into multiple overlapping clusters called 'audiences' based on mutual(?) upvote/downvote relationships in public rooms. + * Clusters of the same people but in different rooms could be modelled as separate (but overlapping) clusters. + * Each audience builds up a reputation score for the global population, blending in damped scores from overlapping audiences. + * Anyone can upvote/downvote, but the votes will not contribute to your personal opinion unless the voter overlaps with your audience's scoresheet. + * A room could adopt a given audience (that of the moderators'?) for considering the reputation of who can join, invite people, etc. + * A user uses their own 'audience of one' scoresheet to put a threshold on filtering out contact from other users (invites, messages, etc). + * Their personal scoresheet is presumably a blend of all the audiences they are already in. + * The act of inviting someone gives them some reputation, within your audiences, proportional to your own. Similarly blocking reduces reputation. + * If you are downvoted, it retrospectively reduces the weight of all of your upvote/downvotes (at least for audiences that the downvoter's opinion contributes to). Similarly for upvoting. + * This penalisation process is transitive. + + Do we even need the penalisation stuff if audience partitioning works? diff --git a/drafts/websockets.rst b/drafts/websockets.rst new file mode 100644 index 00000000000..0fe8d968d55 --- /dev/null +++ b/drafts/websockets.rst @@ -0,0 +1,288 @@ +WebSockets API +============== + +Introduction +------------ +This document is a proposal for a WebSockets-based client-server API. It is not +intended to replace the REST API, but rather to complement it and provide an +alternative interface for certain operations. + +The primary goal is to offer a more efficient interface than the REST API: by +using a bidirectional protocol such as WebSockets we can avoid the overheads +involved in long-polling (SSL negotiation, HTTP headers, etc). In doing so we +will reduce the latency between server and client by allowing the server to +send events as soon as they arrive, rather than having to wait for a poll from +the client. + +Note: This proposal got continued in a google document you can find here: +https://docs.google.com/document/d/104ClehFBgqLQbf4s-AKX2ijr8sOAxcizfcRs_atsB0g + +Handshake +--------- +1. Instead of calling ``/sync``, the client makes a websocket request to + ``/_matrix/client/rN/stream``, passing the query parameters ``access_token`` + and ``since``, and optionally ``filter`` - all of which have the same + meaning as for ``/sync``. + + * The client sets the ``Sec-WebSocket-Protocol`` to ``m.json``. (Servers may + offer alternative encodings; at present only the JSON encoding is + specified but in future we will specify alternative encodings.) + +#. The server returns the websocket handshake; the socket is then connected. + +If the server does not return a valid websocket handshake, this indicates that +the server or an intermediate proxy does not support WebSockets. In this case, +the client should fall back to polling the ``/sync`` REST endpoint. + +Example +~~~~~~~ + +Client request: + +.. code:: http + + GET /_matrix/client/v2_alpha/stream?access_token=123456&since=s72594_4483_1934 HTTP/1.1 + Host: matrix.org + Upgrade: websocket + Connection: Upgrade + Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== + Sec-WebSocket-Protocol: m.json + Sec-WebSocket-Version: 13 + Origin: https://matrix.org + +Server response: + +.. code:: http + + HTTP/1.1 101 Switching Protocols + Upgrade: websocket + Connection: Upgrade + Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= + Sec-WebSocket-Protocol: m.json + + +Update Notifications +-------------------- +Once the socket is connected, the server begins streaming updates over the +websocket. The server sends Update notifications about new messages or state +changes. To make it easy for clients to parse, Update notifications have the +same structure as the response to ``/sync``: an object with the following +members: + +============= ========== =================================================== +Key Type Description +============= ========== =================================================== +next_batch string The batch token to supply in the ``since`` param of + the next /sync request. This is not required for + streaming of events over the WebSocket, but is + provided so that clients can reconnect if the + socket is disconnected. +presence Presence The updates to the presence status of other users. +rooms Rooms Updates to rooms. +============= ========== =================================================== + +Example +~~~~~~~ +Message from the server: + +.. code:: json + + { + "next_batch": "s72595_4483_1934", + "presence": { + "events": [] + }, + "rooms": { + "join": {}, + "invite": {}, + "leave": {} + } + } + + +Client-initiated operations +--------------------------- + +The client can perform certain operations by sending a websocket message to +the server. Such a "Request" message should be a JSON-encoded object with +the following members: + +============= ========== =================================================== +Key Type Description +============= ========== =================================================== +id string A unique identifier for this request +method string Specifies the name of the operation to be + performed; see below for available operations +param object The parameters for the requested operation. +============= ========== =================================================== + +The server responds to a client Request with a Response message. This is a +JSON-encoded object with the following members: + +============= ========== =================================================== +Key Type Description +============= ========== =================================================== +id string The same as the value in the corresponding Request + object. The presence of the ``id`` field + distinguishes a Response message from an Update + notification. +result object On success, the results of the request. +error object On error, an object giving the resons for the + error. This has the same structure as the "standard + error response" for the Matrix API: an object with + the fields ``errcode`` and ``error``. +============= ========== =================================================== + +Request methods +~~~~~~~~~~~~~~~ +It is not intended that all operations which are available via the REST API +will be available via the WebSockets API, but a few simple, common operations +will be exposed. The initial operations will be as follows. + +``ping`` +^^^^^^^^ +This is a no-op which clients may use to keep their connection alive. + +The request ``params`` and the response ``result`` should be empty. + +``send`` +^^^^^^^^ +Send a message event to a room. The parameters are as follows: + +============= ========== =================================================== +Parameter Type Description +============= ========== =================================================== +room_id string **Required.** The room to send the event to +event_type string **Required.** The type of event to send. +content object **Required.** The content of the event. +============= ========== =================================================== + +The result is as follows: + +============= ========== =================================================== +Key Type Description +============= ========== =================================================== +event_id string A unique identifier for the event. +============= ========== =================================================== + +The ``id`` from the Request message is used as the transaction ID by the +server. + +``state`` +^^^^^^^^^ +Update the state on a room. + +============= ========== =================================================== +Parameter Type Description +============= ========== =================================================== +room_id string **Required.** The room to set the state in +event_type string **Required.** The type of event to send. +state_key string **Required.** The state_key for the state to send. +content object **Required.** The content of the event. +============= ========== =================================================== + +The result is as follows: + +============= ========== =================================================== +Key Type Description +============= ========== =================================================== +event_id string A unique identifier for the event. +============= ========== =================================================== + + +Example +~~~~~~~ +Client request: + +.. code:: json + + { + "id": "12345", + "method": "send", + "params": { + "room_id": "!d41d8cd:matrix.org", + "event_type": "m.room.message", + "content": { + "msgtype": "m.text", + "body": "hello" + } + } + } + +Server response: + +.. code:: json + + { + "id": "12345", + "result": { + "event_id": "$66697273743031:matrix.org" + } + } + +Alternative server response, in case of error: + +.. code:: json + + { + "id": "12345", + "error": { + "errcode": "M_MISSING_PARAM", + "error": "Missing parameter: event_type" + } + } + + +Rationale +--------- +Alternatives to WebSockets include HTTP/2, CoAP, and simply rolling our own +protocol over raw TCP sockets. However, the need to implement browser-based +clients essentially reduces our choice to WebSockets. HTTP/2 streams will +probably provide an interesting alternative in the future, but current browsers +do not appear to give javascript applications low-level access to the protocol. + +Concerning the continued use of the JSON encoding: we prefer to focus on the +transition to WebSockets initially. Replacing JSON with a compact +representation such as CBOR, MessagePack, or even just compressed JSON will be +a likely extension for the future. The support for negotiation of subprotocols +within WebSockets should make this a simple transition once time permits. + +The number of methods available for client requests is deliberately limited, as +each method requires code to be written to map it onto the equivalent REST +implementation. Some REST methods - for instance, user registration and login - +would be pointless to expose via WebSockets. It is likely, however, that we +will increate the number of methods available via the WebSockets API as it +becomes clear which would be most useful. + +Open questions +-------------- + +Throttling +~~~~~~~~~~ +At least in v2 sync, clients are inherently self-throttling - if they do not +poll quickly enough, events will be dropped from the next result. This proposal +raises the possibility that events will be produced more quickly than they can +be sent to the client; backlogs will build up on the server and/or in the +intermediate network, which will not only lead to high latency on events being +delivered, but will lead to responses to client requests also being delayed. + +We may need to implement some sort of throttling mechanism by which the server +can start to drop events. The difficulty is in knowing when to start dropping +events. A few ideas: + +* Use websocket pings to measure the RTT; if it starts to increase, start + dropping events. But this requires knowledge of the base RTT, and a useful + model of what constitutes an excessive increase. + +* Have the client acknowledge each batch of events, and use a window to ensure + the number of outstanding batches is limited. This is annoying as it requires + the client to have to acknowledge batches - and it's not clear what the right + window size is: we want a big window for long fat networks (think of mobile + clients), but a small one for one with lower latency. + +* Start dropping events if the server's TCP buffer starts filling up. This has + the advantage of delegating the congestion-detection to TCP (which already + has a number of algorithms to deal with it, to greater or lesser + effectiveness), but relies on homeservers being hosted on OSes which use + sensible TCP congestion-avoidance algorithms, and more critically, an ability + to read the fill level of the TCP send buffer. diff --git a/event-schemas/README.md b/event-schemas/README.md index 1030a04c531..c833b9c3e51 100644 --- a/event-schemas/README.md +++ b/event-schemas/README.md @@ -1,13 +1,6 @@ -Testing a schema ----------------- -There are [many](http://json-schema.org/implementations.html) JSON Schema -validators you can use to validate incoming events. Not all of them support -JSON Schema v4, and some of them have bugs which prevent ``$ref`` from being -resolved correctly. For basic CLI testing, we recommend and have verified they -work with the Node.js package [z-schema](https://github.com/zaggino/z-schema): -``` - $ npm install -g z-schema - $ z-schema schema/v1/m.room.message examples/v1/m.room.message_m.text - schema validation passed - json #1 validation passed -``` +Checking the event schemas +========================== + +To validate the event schemas, and check the example events, run + + ./check-examples.py diff --git a/event-schemas/check.sh b/event-schemas/check.sh deleted file mode 100755 index fac36cf85be..00000000000 --- a/event-schemas/check.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -e -# Runs z-schema over all of the schema files (looking for matching examples) -find schema/v1/m.* | while read line -do - split_path=(${line///// }) - event_type=(${split_path[2]}) - echo "Checking $event_type" - echo "--------------------" - # match exact name or exact name with a # - find examples/v1 -name $event_type -o -name "$event_type#*" | while read exline - do - echo " against $exline" - # run z-schema: because of bash -e if this fails we bail with exit code 1 - z-schema schema/v1/$event_type $exline - done -done diff --git a/event-schemas/check_examples.py b/event-schemas/check_examples.py index e54d3a1cae6..f2456d97e4a 100755 --- a/event-schemas/check_examples.py +++ b/event-schemas/check_examples.py @@ -1,4 +1,18 @@ -#! /usr/bin/env python +#!/usr/bin/env python +# +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import sys import json @@ -38,13 +52,12 @@ def check_example_file(examplepath, schemapath): schema = yaml.load(f) fileurl = "file://" + os.path.abspath(schemapath) + schema["id"] = fileurl + resolver = jsonschema.RefResolver(schemapath, schema, handlers={"file": load_yaml}) print ("Checking schema for: %r %r" % (examplepath, schemapath)) - # Setting the 'id' tells jsonschema where the file is so that it - # can correctly resolve relative $ref references in the schema - schema['id'] = fileurl try: - jsonschema.validate(example, schema) + jsonschema.validate(example, schema, resolver=resolver) except Exception as e: raise ValueError("Error validating JSON schema for %r %r" % ( examplepath, schemapath @@ -60,6 +73,8 @@ def check_example_dir(exampledir, schemadir): continue examplepath = os.path.join(root, filename) schemapath = examplepath.replace(exampledir, schemadir) + if schemapath.find("#") >= 0: + schemapath = schemapath[:schemapath.find("#")] try: check_example_file(examplepath, schemapath) except Exception as e: @@ -69,6 +84,15 @@ def check_example_dir(exampledir, schemadir): if errors: raise ValueError("Error validating examples") + +def load_yaml(path): + if not path.startswith("file:///"): + raise Exception("Bad ref: %s" % (path,)) + path = path[len("file://"):] + with open(path, "r") as f: + return yaml.load(f) + + if __name__ == '__main__': try: check_example_dir("examples", "schema") diff --git a/event-schemas/examples/v1/m.call.answer b/event-schemas/examples/m.call.answer similarity index 91% rename from event-schemas/examples/v1/m.call.answer rename to event-schemas/examples/m.call.answer index 0301fb3b5f3..f7d1443942a 100644 --- a/event-schemas/examples/v1/m.call.answer +++ b/event-schemas/examples/m.call.answer @@ -13,5 +13,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.call.answer", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.call.candidates b/event-schemas/examples/m.call.candidates similarity index 92% rename from event-schemas/examples/v1/m.call.candidates rename to event-schemas/examples/m.call.candidates index 389d227181b..8e6849bb1c9 100644 --- a/event-schemas/examples/v1/m.call.candidates +++ b/event-schemas/examples/m.call.candidates @@ -15,5 +15,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.call.candidates", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.call.hangup b/event-schemas/examples/m.call.hangup similarity index 86% rename from event-schemas/examples/v1/m.call.hangup rename to event-schemas/examples/m.call.hangup index 52ddad1620e..42e1f346ccb 100644 --- a/event-schemas/examples/v1/m.call.hangup +++ b/event-schemas/examples/m.call.hangup @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.call.hangup", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.call.invite b/event-schemas/examples/m.call.invite similarity index 91% rename from event-schemas/examples/v1/m.call.invite rename to event-schemas/examples/m.call.invite index 2df2592951c..974a5b4cdbe 100644 --- a/event-schemas/examples/v1/m.call.invite +++ b/event-schemas/examples/m.call.invite @@ -13,5 +13,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.call.invite", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/m.direct b/event-schemas/examples/m.direct new file mode 100644 index 00000000000..92f13daa27b --- /dev/null +++ b/event-schemas/examples/m.direct @@ -0,0 +1,9 @@ +{ + "type": "m.direct", + "content": { + "@bob:example.com": [ + "!abcdefgh:example.com", + "!hgfedcba:example.com" + ] + } +} diff --git a/event-schemas/examples/v1/m.presence b/event-schemas/examples/m.presence similarity index 88% rename from event-schemas/examples/v1/m.presence rename to event-schemas/examples/m.presence index 695bd99b2fc..ead92ccdb0f 100644 --- a/event-schemas/examples/v1/m.presence +++ b/event-schemas/examples/m.presence @@ -3,6 +3,7 @@ "avatar_url": "mxc://localhost:wefuiwegh8742w", "last_active_ago": 2478593, "presence": "online", + "currently_active": false, "user_id": "@example:localhost" }, "event_id": "$WLGTSEFSEF:localhost", diff --git a/event-schemas/examples/v1/m.receipt b/event-schemas/examples/m.receipt similarity index 100% rename from event-schemas/examples/v1/m.receipt rename to event-schemas/examples/m.receipt diff --git a/event-schemas/examples/v1/m.room.aliases b/event-schemas/examples/m.room.aliases similarity index 88% rename from event-schemas/examples/v1/m.room.aliases rename to event-schemas/examples/m.room.aliases index 07b4b330634..ca87510e3ae 100644 --- a/event-schemas/examples/v1/m.room.aliases +++ b/event-schemas/examples/m.room.aliases @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.aliases", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/m.room.avatar b/event-schemas/examples/m.room.avatar new file mode 100644 index 00000000000..2080d96ef40 --- /dev/null +++ b/event-schemas/examples/m.room.avatar @@ -0,0 +1,18 @@ +{ + "age": 242352, + "content": { + "info": { + "h": 398, + "w": 394, + "mimetype": "image/jpeg", + "size": 31037 + }, + "url": "mxc://localhost/JWEIFJgwEIhweiWJE" + }, + "origin_server_ts": 1431961217939, + "event_id": "$WLGTSEFSEF:localhost", + "type": "m.room.avatar", + "state_key": "", + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.canonical_alias b/event-schemas/examples/m.room.canonical_alias similarity index 87% rename from event-schemas/examples/v1/m.room.canonical_alias rename to event-schemas/examples/m.room.canonical_alias index 0203a851edd..59df586d9d3 100644 --- a/event-schemas/examples/v1/m.room.canonical_alias +++ b/event-schemas/examples/m.room.canonical_alias @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.canonical_alias", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.create b/event-schemas/examples/m.room.create similarity index 87% rename from event-schemas/examples/v1/m.room.create rename to event-schemas/examples/m.room.create index a3598853881..34dabb53678 100644 --- a/event-schemas/examples/v1/m.room.create +++ b/event-schemas/examples/m.room.create @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.create", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/m.room.guest_access b/event-schemas/examples/m.room.guest_access new file mode 100644 index 00000000000..c636ff395ed --- /dev/null +++ b/event-schemas/examples/m.room.guest_access @@ -0,0 +1,12 @@ +{ + "age": 242353, + "content": { + "guest_access": "can_join" + }, + "state_key": "", + "origin_server_ts": 1431961217938, + "event_id": "$WLGTSEFSEG:localhost", + "type": "m.room.guest_access", + "room_id": "!Cuyf34gef24u:localhost", + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.history_visibility b/event-schemas/examples/m.room.history_visibility similarity index 88% rename from event-schemas/examples/v1/m.room.history_visibility rename to event-schemas/examples/m.room.history_visibility index fcc3f881b29..6fedc5dc6a5 100644 --- a/event-schemas/examples/v1/m.room.history_visibility +++ b/event-schemas/examples/m.room.history_visibility @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.history_visibility", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.join_rules b/event-schemas/examples/m.room.join_rules similarity index 87% rename from event-schemas/examples/v1/m.room.join_rules rename to event-schemas/examples/m.room.join_rules index f22ad97e9e5..39e14fc5d30 100644 --- a/event-schemas/examples/v1/m.room.join_rules +++ b/event-schemas/examples/m.room.join_rules @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.join_rules", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/m.room.member b/event-schemas/examples/m.room.member new file mode 100644 index 00000000000..2495145baab --- /dev/null +++ b/event-schemas/examples/m.room.member @@ -0,0 +1,14 @@ +{ + "age": 242352, + "content": { + "membership": "join", + "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto", + "displayname": "Alice Margatroid" + }, + "state_key": "@alice:localhost", + "origin_server_ts": 1431961217939, + "event_id": "$WLGTSEFSEF:localhost", + "type": "m.room.member", + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/m.room.member#invite_room_state b/event-schemas/examples/m.room.member#invite_room_state new file mode 100644 index 00000000000..1a93b395b2d --- /dev/null +++ b/event-schemas/examples/m.room.member#invite_room_state @@ -0,0 +1,30 @@ +{ + "age": 242352, + "content": { + "membership": "invite", + "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto", + "displayname": "Alice Margatroid" + }, + "invite_room_state": [ + { + "type": "m.room.name", + "state_key": "", + "content": { + "name": "Forest of Magic" + } + }, + { + "type": "m.room.join_rules", + "state_key": "", + "content": { + "join_rule": "invite" + } + } + ], + "state_key": "@alice:localhost", + "origin_server_ts": 1431961217939, + "event_id": "$WLGTSEFSEF:localhost", + "type": "m.room.member", + "room_id": "!Cuyf34gef24t:localhost", + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.member b/event-schemas/examples/m.room.member#third_party_invite similarity index 52% rename from event-schemas/examples/v1/m.room.member rename to event-schemas/examples/m.room.member#third_party_invite index a5ab79b5cb3..61718ba1a40 100644 --- a/event-schemas/examples/v1/m.room.member +++ b/event-schemas/examples/m.room.member#third_party_invite @@ -5,11 +5,16 @@ "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto", "displayname": "Alice Margatroid", "third_party_invite": { - "token": "pc98", - "public_key": "abc123", - "key_validity_url": "https://magic.forest/verifykey", - "signature": "q1w2e3", - "sender": "@zun:zun.soft" + "display_name": "alice", + "signed": { + "mxid": "@alice:localhost", + "signatures": { + "magic.forest": { + "ed25519:3": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg" + } + }, + "token": "abc123" + } } }, "state_key": "@alice:localhost", @@ -17,5 +22,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.member", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.message#m.audio b/event-schemas/examples/m.room.message#m.audio similarity index 92% rename from event-schemas/examples/v1/m.room.message#m.audio rename to event-schemas/examples/m.room.message#m.audio index b57adc9a2d9..367eb954061 100644 --- a/event-schemas/examples/v1/m.room.message#m.audio +++ b/event-schemas/examples/m.room.message#m.audio @@ -14,5 +14,5 @@ "origin_server_ts": 1432735824653, "room_id": "!jEsUZKDJdhlrceRyVU:localhost", "type": "m.room.message", - "user_id": "@example:localhost" -} \ No newline at end of file + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.message#m.emote b/event-schemas/examples/m.room.message#m.emote similarity index 88% rename from event-schemas/examples/v1/m.room.message#m.emote rename to event-schemas/examples/m.room.message#m.emote index bd9848315d8..4280928ee42 100644 --- a/event-schemas/examples/v1/m.room.message#m.emote +++ b/event-schemas/examples/m.room.message#m.emote @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.message", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.message#m.file b/event-schemas/examples/m.room.message#m.file similarity index 92% rename from event-schemas/examples/v1/m.room.message#m.file rename to event-schemas/examples/m.room.message#m.file index 7ac36c9036d..e52c3a9479f 100644 --- a/event-schemas/examples/v1/m.room.message#m.file +++ b/event-schemas/examples/m.room.message#m.file @@ -14,5 +14,5 @@ "origin_server_ts": 1432735824653, "room_id": "!jEsUZKDJdhlrceRyVU:localhost", "type": "m.room.message", - "user_id": "@example:localhost" -} \ No newline at end of file + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.message#m.image b/event-schemas/examples/m.room.message#m.image similarity index 92% rename from event-schemas/examples/v1/m.room.message#m.image rename to event-schemas/examples/m.room.message#m.image index ba405a1ae62..91e72be264f 100644 --- a/event-schemas/examples/v1/m.room.message#m.image +++ b/event-schemas/examples/m.room.message#m.image @@ -15,5 +15,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.message", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.message#m.location b/event-schemas/examples/m.room.message#m.location similarity index 55% rename from event-schemas/examples/v1/m.room.message#m.location rename to event-schemas/examples/m.room.message#m.location index 54198acbf4d..75363f6fd6e 100644 --- a/event-schemas/examples/v1/m.room.message#m.location +++ b/event-schemas/examples/m.room.message#m.location @@ -3,12 +3,14 @@ "content": { "body": "Big Ben, London, UK", "geo_uri": "geo:51.5008,0.1247", - "thumbnail_url": "mxc://localhost/FHyPlCeYUSFFxlgbQYZmoEoe", - "thumbnail_info": { - "mimetype": "image/jpeg", - "size": 46144, - "w": 300, - "h": 300 + "info": { + "thumbnail_url": "mxc://localhost/FHyPlCeYUSFFxlgbQYZmoEoe", + "thumbnail_info": { + "mimetype": "image/jpeg", + "size": 46144, + "w": 300, + "h": 300 + } }, "msgtype": "m.location" }, @@ -16,5 +18,5 @@ "origin_server_ts": 1432735824653, "room_id": "!jEsUZKDJdhlrceRyVU:localhost", "type": "m.room.message", - "user_id": "@example:localhost" -} + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.message#m.notice b/event-schemas/examples/m.room.message#m.notice similarity index 88% rename from event-schemas/examples/v1/m.room.message#m.notice rename to event-schemas/examples/m.room.message#m.notice index 2e5fff97f2e..978c67e6b0e 100644 --- a/event-schemas/examples/v1/m.room.message#m.notice +++ b/event-schemas/examples/m.room.message#m.notice @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.message", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.message#m.text b/event-schemas/examples/m.room.message#m.text similarity index 88% rename from event-schemas/examples/v1/m.room.message#m.text rename to event-schemas/examples/m.room.message#m.text index d877ee6416f..e00c7aa5663 100644 --- a/event-schemas/examples/v1/m.room.message#m.text +++ b/event-schemas/examples/m.room.message#m.text @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.message", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.message#m.video b/event-schemas/examples/m.room.message#m.video similarity index 94% rename from event-schemas/examples/v1/m.room.message#m.video rename to event-schemas/examples/m.room.message#m.video index e3da9077021..576d80de4ed 100644 --- a/event-schemas/examples/v1/m.room.message#m.video +++ b/event-schemas/examples/m.room.message#m.video @@ -23,5 +23,5 @@ "origin_server_ts": 1432735824653, "room_id": "!jEsUZKDJdhlrceRyVU:localhost", "type": "m.room.message", - "user_id": "@example:localhost" -} \ No newline at end of file + "sender": "@example:localhost" +} diff --git a/event-schemas/examples/v1/m.room.message.feedback b/event-schemas/examples/m.room.message.feedback similarity index 88% rename from event-schemas/examples/v1/m.room.message.feedback rename to event-schemas/examples/m.room.message.feedback index 6282305b0c4..16fe0ee0975 100644 --- a/event-schemas/examples/v1/m.room.message.feedback +++ b/event-schemas/examples/m.room.message.feedback @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.message.feedback", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.name b/event-schemas/examples/m.room.name similarity index 87% rename from event-schemas/examples/v1/m.room.name rename to event-schemas/examples/m.room.name index 636119b8b69..87db2008ed1 100644 --- a/event-schemas/examples/v1/m.room.name +++ b/event-schemas/examples/m.room.name @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.name", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.power_levels b/event-schemas/examples/m.room.power_levels similarity index 90% rename from event-schemas/examples/v1/m.room.power_levels rename to event-schemas/examples/m.room.power_levels index 8278597af59..0c8f8bc532c 100644 --- a/event-schemas/examples/v1/m.room.power_levels +++ b/event-schemas/examples/m.room.power_levels @@ -7,6 +7,7 @@ "m.room.power_levels": 100 }, "events_default": 0, + "invite": 50, "kick": 50, "redact": 50, "state_default": 50, @@ -20,5 +21,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.power_levels", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.redaction b/event-schemas/examples/m.room.redaction similarity index 63% rename from event-schemas/examples/v1/m.room.redaction rename to event-schemas/examples/m.room.redaction index 2e8260eabf8..e24a8cdbeaa 100644 --- a/event-schemas/examples/v1/m.room.redaction +++ b/event-schemas/examples/m.room.redaction @@ -1,5 +1,7 @@ { - "age": 242352, + "unsigned": { + "age": 242352 + }, "content": { "reason": "Spamming" }, @@ -7,6 +9,6 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.redaction", "room_id": "!Cuyf34gef24t:localhost", - "redacts": "!fukweghifu23:localhost", - "user_id": "@example:localhost" + "redacts": "$fukweghifu23:localhost", + "sender": "@example:localhost" } diff --git a/event-schemas/examples/v1/m.room.third_party_invite b/event-schemas/examples/m.room.third_party_invite similarity index 70% rename from event-schemas/examples/v1/m.room.third_party_invite rename to event-schemas/examples/m.room.third_party_invite index 82ac524873e..3f9d48feff0 100644 --- a/event-schemas/examples/v1/m.room.third_party_invite +++ b/event-schemas/examples/m.room.third_party_invite @@ -3,7 +3,11 @@ "content": { "display_name": "Alice Margatroid", "key_validity_url": "https://magic.forest/verifykey", - "public_key": "abc123" + "public_key": "abc123", + "public_keys": [{ + "public_key": "def456", + "key_validity_url": "https://magic.forest/verifykey" + }] }, "state_key": "pc98", "origin_server_ts": 1431961217939, diff --git a/event-schemas/examples/v1/m.room.topic b/event-schemas/examples/m.room.topic similarity index 87% rename from event-schemas/examples/v1/m.room.topic rename to event-schemas/examples/m.room.topic index 8a469122c13..65daa987219 100644 --- a/event-schemas/examples/v1/m.room.topic +++ b/event-schemas/examples/m.room.topic @@ -8,5 +8,5 @@ "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.topic", "room_id": "!Cuyf34gef24t:localhost", - "user_id": "@example:localhost" + "sender": "@example:localhost" } diff --git a/event-schemas/examples/m.tag b/event-schemas/examples/m.tag new file mode 100644 index 00000000000..00e81060b07 --- /dev/null +++ b/event-schemas/examples/m.tag @@ -0,0 +1,8 @@ +{ + "type": "m.tag", + "content": { + "tags": { + "work": {"order": 1} + } + } +} diff --git a/event-schemas/examples/v1/m.typing b/event-schemas/examples/m.typing similarity index 98% rename from event-schemas/examples/v1/m.typing rename to event-schemas/examples/m.typing index bd53f6fb6b3..1d2c517b912 100644 --- a/event-schemas/examples/v1/m.typing +++ b/event-schemas/examples/m.typing @@ -4,4 +4,4 @@ "content": { "user_ids": ["@alice:matrix.org", "@bob:example.com"] } -} \ No newline at end of file +} diff --git a/event-schemas/schema/core-event-schema/event.yaml b/event-schemas/schema/core-event-schema/event.yaml new file mode 100644 index 00000000000..ebf67aa36ef --- /dev/null +++ b/event-schemas/schema/core-event-schema/event.yaml @@ -0,0 +1,13 @@ +description: The basic set of fields all events must have. +properties: + content: + description: The fields in this object will vary depending on the type of event. + When interacting with the REST API, this is the HTTP body. + title: EventContent + type: object + type: + description: The type of event. This SHOULD be namespaced similar to Java package + naming conventions e.g. 'com.example.subdomain.event.type' + type: string +title: Event +type: object diff --git a/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml b/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml new file mode 100644 index 00000000000..b4eab413a2c --- /dev/null +++ b/event-schemas/schema/core-event-schema/msgtype_infos/image_info.yaml @@ -0,0 +1,24 @@ +$schema: http://json-schema.org/draft-04/schema# +description: Metadata about an image. +properties: + h: + description: The height of the image in pixels. + type: integer + w: + description: The width of the image in pixels. + type: integer + mimetype: + description: The mimetype of the image, e.g. ``image/jpeg``. + type: string + size: + description: Size of the image in bytes. + type: integer + thumbnail_url: + description: The URL to a thumbnail of the image. + type: string + thumbnail_info: + allOf: + - $ref: thumbnail_info.yaml + description: Metadata about the image referred to in ``thumbnail_url``. +title: ImageInfo +type: object diff --git a/event-schemas/schema/core-event-schema/msgtype_infos/thumbnail_info.yaml b/event-schemas/schema/core-event-schema/msgtype_infos/thumbnail_info.yaml new file mode 100644 index 00000000000..5a233b241a3 --- /dev/null +++ b/event-schemas/schema/core-event-schema/msgtype_infos/thumbnail_info.yaml @@ -0,0 +1,17 @@ +$schema: http://json-schema.org/draft-04/schema# +description: Metadata about a thumbnail image. +properties: + h: + description: The height of the image in pixels. + type: integer + w: + description: The width of the image in pixels. + type: integer + mimetype: + description: The mimetype of the image, e.g. ``image/jpeg``. + type: string + size: + description: Size of the image in bytes. + type: integer +title: ThumbnailInfo +type: object diff --git a/event-schemas/schema/core-event-schema/room_event.yaml b/event-schemas/schema/core-event-schema/room_event.yaml new file mode 100644 index 00000000000..de72f570925 --- /dev/null +++ b/event-schemas/schema/core-event-schema/room_event.yaml @@ -0,0 +1,45 @@ +allOf: +- $ref: event.yaml +description: In addition to the Event fields, Room Events have the following additional + fields. +properties: + event_id: + description: Required. The globally unique event identifier. + type: string + room_id: + description: Required. The ID of the room associated with this event. + type: string + sender: + description: Required. Contains the fully-qualified ID of the user who *sent* + this event. + type: string + origin_server_ts: + description: Required. Timestamp in milliseconds on originating homeserver + when this event was sent. + type: number + unsigned: + description: Contains optional extra information about the event. + properties: + age: + description: The time in milliseconds that has elapsed since the event was + sent. This field is generated by the local homeserver, and may be incorrect + if the local time on at least one of the two servers is out of sync, which can + cause the age to either be negative or greater than it actually is. + type: integer + redacted_because: + description: Optional. The event that redacted this event, if any. + title: Event + type: object + transaction_id: + description: The client-supplied transaction ID, if the client being given + the event is the same one which sent it. + type: string + title: UnsignedData + type: object +required: +- event_id +- room_id +- sender +- origin_server_ts +title: Room Event +type: object diff --git a/event-schemas/schema/core-event-schema/state_event.yaml b/event-schemas/schema/core-event-schema/state_event.yaml new file mode 100644 index 00000000000..020e9087a60 --- /dev/null +++ b/event-schemas/schema/core-event-schema/state_event.yaml @@ -0,0 +1,19 @@ +allOf: +- $ref: room_event.yaml +description: In addition to the Room Event fields, State Events have the following + additional fields. +properties: + prev_content: + description: Optional. The previous ``content`` for this event. If there is no + previous content, this key will be missing. + title: EventContent + type: object + state_key: + description: A unique key which defines the overwriting semantics for this piece + of room state. This value is often a zero-length string. The presence of this + key makes this event a State Event. The key MUST NOT start with '_'. + type: string +required: +- state_key +title: State Event +type: object diff --git a/event-schemas/schema/v1/m.call.answer b/event-schemas/schema/m.call.answer similarity index 96% rename from event-schemas/schema/v1/m.call.answer rename to event-schemas/schema/m.call.answer index f11a61ba5b1..9d98dd7d2a0 100644 --- a/event-schemas/schema/v1/m.call.answer +++ b/event-schemas/schema/m.call.answer @@ -2,7 +2,7 @@ "type": "object", "description": "This event is sent by the callee when they wish to answer the call.", "allOf": [{ - "$ref": "core-event-schema/room_event.json" + "$ref": "core-event-schema/room_event.yaml" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.call.candidates b/event-schemas/schema/m.call.candidates similarity index 97% rename from event-schemas/schema/v1/m.call.candidates rename to event-schemas/schema/m.call.candidates index 52e06705318..7426717c670 100644 --- a/event-schemas/schema/v1/m.call.candidates +++ b/event-schemas/schema/m.call.candidates @@ -2,7 +2,7 @@ "type": "object", "description": "This event is sent by callers after sending an invite and by the callee after answering. Its purpose is to give the other party additional ICE candidates to try using to communicate.", "allOf": [{ - "$ref": "core-event-schema/room_event.json" + "$ref": "core-event-schema/room_event.yaml" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.call.hangup b/event-schemas/schema/m.call.hangup similarity index 94% rename from event-schemas/schema/v1/m.call.hangup rename to event-schemas/schema/m.call.hangup index 026676352cc..9d45d179ae7 100644 --- a/event-schemas/schema/v1/m.call.hangup +++ b/event-schemas/schema/m.call.hangup @@ -2,7 +2,7 @@ "type": "object", "description": "Sent by either party to signal their termination of the call. This can be sent either once the call has has been established or before to abort the call.", "allOf": [{ - "$ref": "core-event-schema/room_event.json" + "$ref": "core-event-schema/room_event.yaml" }], "properties": { "content": { diff --git a/event-schemas/schema/v1/m.call.invite b/event-schemas/schema/m.call.invite similarity index 97% rename from event-schemas/schema/v1/m.call.invite rename to event-schemas/schema/m.call.invite index 585403d05b9..ebf09267bb6 100644 --- a/event-schemas/schema/v1/m.call.invite +++ b/event-schemas/schema/m.call.invite @@ -2,7 +2,7 @@ "type": "object", "description": "This event is sent by the caller when they wish to establish a call.", "allOf": [{ - "$ref": "core-event-schema/room_event.json" + "$ref": "core-event-schema/room_event.yaml" }], "properties": { "content": { diff --git a/event-schemas/schema/m.direct b/event-schemas/schema/m.direct new file mode 100644 index 00000000000..8cbbf38f0b7 --- /dev/null +++ b/event-schemas/schema/m.direct @@ -0,0 +1,23 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml +description: |- + A map of which rooms are considered 'direct' rooms for specific users + is kept in ``account_data`` in an event of type ``m.direct``. The + content of this event is an object where the keys are the user IDs + and values are lists of room ID strings of the 'direct' rooms for + that user ID. +properties: + content: + additionalProperties: + type: array + title: User ID + items: + type: string + type: object + type: + enum: + - m.direct + type: string +title: Direct Chat Mapping +type: object diff --git a/event-schemas/schema/v1/m.presence b/event-schemas/schema/m.presence similarity index 81% rename from event-schemas/schema/v1/m.presence rename to event-schemas/schema/m.presence index 79852ac696e..36108db6f21 100644 --- a/event-schemas/schema/v1/m.presence +++ b/event-schemas/schema/m.presence @@ -2,6 +2,9 @@ "type": "object", "title": "Presence Event", "description": "Informs the client of a user's presence state change.", + "allOf": [{ + "$ref": "core-event-schema/event.yaml" + }], "properties": { "content": { "type": "object", @@ -21,7 +24,11 @@ "presence": { "type": "string", "description": "The presence state for this user.", - "enum": ["online", "offline", "unavailable", "free_for_chat", "hidden"] + "enum": ["online", "offline", "unavailable"] + }, + "currently_active": { + "type": boolean, + "description": "Whether the user is currently active" }, "user_id": { "type": "string", diff --git a/event-schemas/schema/v1/m.receipt b/event-schemas/schema/m.receipt similarity index 96% rename from event-schemas/schema/v1/m.receipt rename to event-schemas/schema/m.receipt index d0f79ac4a1e..8594dd7c147 100644 --- a/event-schemas/schema/v1/m.receipt +++ b/event-schemas/schema/m.receipt @@ -2,6 +2,9 @@ "type": "object", "title": "Receipt Event", "description": "Informs the client of new receipts.", + "allOf": [{ + "$ref": "core-event-schema/event.yaml" + }], "properties": { "content": { "type": "object", diff --git a/event-schemas/schema/m.room.aliases b/event-schemas/schema/m.room.aliases new file mode 100644 index 00000000000..348d490d41d --- /dev/null +++ b/event-schemas/schema/m.room.aliases @@ -0,0 +1,24 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: 'This event is sent by a homeserver directly to inform of changes to the list of aliases it knows about for that room. The ``state_key`` for this event is set to the homeserver which owns the room alias. The entire set of known aliases for the room is the union of all the ``m.room.aliases`` events, one for each homeserver. Clients **should** check the validity of any room alias given in this list before presenting it to the user as trusted fact. The lists given by this event should be considered simply as advice on which aliases might exist, for which the client can perform the lookup to confirm whether it receives the correct room ID.' +properties: + content: + properties: + aliases: + description: A list of room aliases. + items: + type: string + type: array + required: + - aliases + type: object + state_key: + description: The homeserver domain which owns these room aliases. + type: string + type: + enum: + - m.room.aliases + type: string +title: Informs the room about what room aliases it has been given. +type: object diff --git a/event-schemas/schema/m.room.avatar b/event-schemas/schema/m.room.avatar new file mode 100644 index 00000000000..a0ecb21c2de --- /dev/null +++ b/event-schemas/schema/m.room.avatar @@ -0,0 +1,27 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: A picture that is associated with the room. This can be displayed alongside the room information. +properties: + content: + properties: + info: + allOf: + - $ref: core-event-schema/msgtype_infos/image_info.yaml + description: Metadata about the image referred to in ``url``. + url: + description: The URL to the image. + type: string + required: + - url + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.avatar + type: string +title: RoomAvatar +type: object diff --git a/event-schemas/schema/m.room.canonical_alias b/event-schemas/schema/m.room.canonical_alias new file mode 100644 index 00000000000..5e8e0524840 --- /dev/null +++ b/event-schemas/schema/m.room.canonical_alias @@ -0,0 +1,30 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: |- + This event is used to inform the room about which alias should be + considered the canonical one. This could be for display purposes or as + suggestion to users which alias to use to advertise the room. + + A room with an ``m.room.canonical_alias`` event with an absent, null, or + empty ``alias`` field should be treated the same as a room with no + ``m.room.canonical_alias`` event. +properties: + content: + properties: + alias: + description: The canonical alias. + type: string + type: object + required: + - alias + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.canonical_alias + type: string +title: Informs the room as to which alias is the canonical one. +type: object diff --git a/event-schemas/schema/m.room.create b/event-schemas/schema/m.room.create new file mode 100644 index 00000000000..a07ab90fcd4 --- /dev/null +++ b/event-schemas/schema/m.room.create @@ -0,0 +1,26 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: This is the first event in a room and cannot be changed. It acts as the root of all other events. +properties: + content: + properties: + creator: + description: The ``user_id`` of the room creator. This is set by the homeserver. + type: string + m.federate: + description: Whether users on other servers can join this room. Defaults to ``true`` if key does not exist. + type: boolean + required: + - creator + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.create + type: string +title: The first event in the room. +type: object diff --git a/event-schemas/schema/m.room.guest_access b/event-schemas/schema/m.room.guest_access new file mode 100644 index 00000000000..f886dfe5936 --- /dev/null +++ b/event-schemas/schema/m.room.guest_access @@ -0,0 +1,26 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: 'This event controls whether guest users are allowed to join rooms. If this event is absent, servers should act as if it is present and has the guest_access value "forbidden".' +properties: + content: + properties: + guest_access: + description: Whether guests can join the room. + enum: + - can_join + - forbidden + type: string + required: + - guest_access + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.guest_access + type: string +title: Controls whether guest users are allowed to join rooms. +type: object diff --git a/event-schemas/schema/m.room.history_visibility b/event-schemas/schema/m.room.history_visibility new file mode 100644 index 00000000000..27ec67c7806 --- /dev/null +++ b/event-schemas/schema/m.room.history_visibility @@ -0,0 +1,28 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: This event controls whether a user can see the events that happened in a room from before they joined. +properties: + content: + properties: + history_visibility: + description: Who can see the room history. + enum: + - invited + - joined + - shared + - world_readable + type: string + required: + - history_visibility + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.history_visibility + type: string +title: Controls visibility of history. +type: object diff --git a/event-schemas/schema/m.room.join_rules b/event-schemas/schema/m.room.join_rules new file mode 100644 index 00000000000..b8e8501c53d --- /dev/null +++ b/event-schemas/schema/m.room.join_rules @@ -0,0 +1,28 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: 'A room may be ``public`` meaning anyone can join the room without any prior action. Alternatively, it can be ``invite`` meaning that a user who wishes to join the room must first receive an invite to the room from someone already inside of the room. Currently, ``knock`` and ``private`` are reserved keywords which are not implemented.' +properties: + content: + properties: + join_rule: + description: The type of rules used for users wishing to join this room. + enum: + - public + - knock + - invite + - private + type: string + required: + - join_rule + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.join_rules + type: string +title: Describes how users are allowed to join the room. +type: object diff --git a/event-schemas/schema/m.room.member b/event-schemas/schema/m.room.member new file mode 100644 index 00000000000..4f4077a7603 --- /dev/null +++ b/event-schemas/schema/m.room.member @@ -0,0 +1,108 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: |- + Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms//invite`` etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail. + + The following membership states are specified: + + - ``invite`` - The user has been invited to join a room, but has not yet joined it. They may not participate in the room until they join. + + - ``join`` - The user has joined the room (possibly after accepting an invite), and may participate in it. + + - ``leave`` - The user was once joined to the room, but has since left (possibly by choice, or possibly by being kicked). + + - ``ban`` - The user has been banned from the room, and is no longer allowed to join it until they are un-banned from the room (by having their membership state set to a value other than ``ban``). + + - ``knock`` - This is a reserved word, which currently has no meaning. + + The ``third_party_invite`` property will be set if this invite is an ``invite`` event and is the successor of an ``m.room.third_party_invite`` event, and absent otherwise. + + This event may also include an ``invite_room_state`` key **outside the** ``content`` **key**. If present, this contains an array of ``StrippedState`` Events. These events provide information on a subset of state events such as the room name. +properties: + content: + properties: + avatar_url: + description: 'The avatar URL for this user, if any. This is added by the homeserver.' + type: string + displayname: + description: 'The display name for this user, if any. This is added by the homeserver.' + type: + - "string" + - "null" + membership: + description: The membership state of the user. + enum: + - invite + - join + - knock + - leave + - ban + type: string + is_direct: + description: Flag indicating if the room containing this event was created with the intention of being a direct chat. See `Direct Messaging`_. + type: boolean + third_party_invite: + properties: + display_name: + description: A name which can be displayed to represent the user instead of their third party identifier + type: string + signed: + description: 'A block of content which has been signed, which servers can use to verify the event. Clients should ignore this.' + properties: + mxid: + description: The invited matrix user ID. Must be equal to the user_id property of the event. + type: string + signatures: + description: 'A single signature from the verifying server, in the format specified by the Signing Events section of the server-server API.' + title: Signatures + type: object + token: + description: The token property of the containing third_party_invite object. + type: string + required: + - mxid + - signatures + - token + title: signed + type: object + required: + - display_name + - signed + title: Invite + type: object + required: + - membership + title: EventContent + type: object + invite_room_state: + description: 'A subset of the state of the room at the time of the invite, if ``membership`` is ``invite``. Note that this state is informational, and SHOULD NOT be trusted; once the client has joined the room, it SHOULD fetch the live state from the server and discard the invite_room_state. Also, clients must not rely on any particular state being present here; they SHOULD behave properly (with possibly a degraded but not a broken experience) in the absence of any particular events here. If they are set on the room, at least the state for ``m.room.avatar``, ``m.room.canonical_alias``, ``m.room.join_rules``, and ``m.room.name`` SHOULD be included.' + items: + description: 'A stripped down state event, with only the ``type``, ``state_key`` and ``content`` keys.' + properties: + content: + description: The ``content`` for the event. + title: EventContent + type: object + state_key: + description: The ``state_key`` for the event. + type: string + type: + description: The ``type`` for the event. + type: string + required: + - type + - state_key + - content + title: StrippedState + type: object + type: array + state_key: + description: The ``user_id`` this membership event relates to. + type: string + type: + enum: + - m.room.member + type: string +title: The current membership state of a user in the room. +type: object diff --git a/event-schemas/schema/m.room.message b/event-schemas/schema/m.room.message new file mode 100644 index 00000000000..45025c99326 --- /dev/null +++ b/event-schemas/schema/m.room.message @@ -0,0 +1,23 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: 'This event is used when sending messages in a room. Messages are not limited to be text. The ``msgtype`` key outlines the type of message, e.g. text, audio, image, video, etc. The ``body`` key is text and MUST be used with every kind of ``msgtype`` as a fallback mechanism for when a client cannot render a message. This allows clients to display *something* even if it is just plain text.' +properties: + content: + properties: + body: + description: The textual representation of this message. + type: string + msgtype: + description: 'The type of message, e.g. ``m.image``, ``m.text``' + type: string + required: + - msgtype + - body + type: object + type: + enum: + - m.room.message + type: string +title: Message +type: object diff --git a/event-schemas/schema/m.room.message#m.audio b/event-schemas/schema/m.room.message#m.audio new file mode 100644 index 00000000000..f15c71a3d2c --- /dev/null +++ b/event-schemas/schema/m.room.message#m.audio @@ -0,0 +1,42 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: This message represents a single audio clip. +properties: + content: + properties: + body: + description: "A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'." + type: string + info: + description: Metadata for the audio clip referred to in ``url``. + properties: + duration: + description: The duration of the audio in milliseconds. + type: integer + mimetype: + description: The mimetype of the audio e.g. ``audio/aac``. + type: string + size: + description: The size of the audio clip in bytes. + type: integer + title: AudioInfo + type: object + msgtype: + enum: + - m.audio + type: string + url: + description: The URL to the audio clip. + type: string + required: + - msgtype + - body + - url + type: object + type: + enum: + - m.room.message + type: string +title: AudioMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.emote b/event-schemas/schema/m.room.message#m.emote new file mode 100644 index 00000000000..88860cb210f --- /dev/null +++ b/event-schemas/schema/m.room.message#m.emote @@ -0,0 +1,24 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: "This message is similar to ``m.text`` except that the sender is 'performing' the action contained in the ``body`` key, similar to ``/me`` in IRC. This message should be prefixed by the name of the sender. This message could also be represented in a different colour to distinguish it from regular ``m.text`` messages." +properties: + content: + properties: + body: + description: The emote action to perform. + type: string + msgtype: + enum: + - m.emote + type: string + required: + - msgtype + - body + type: object + type: + enum: + - m.room.message + type: string +title: EmoteMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.file b/event-schemas/schema/m.room.message#m.file new file mode 100644 index 00000000000..76e59e09c0b --- /dev/null +++ b/event-schemas/schema/m.room.message#m.file @@ -0,0 +1,50 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: This message represents a generic file. +properties: + content: + properties: + body: + description: A human-readable description of the file. This is recommended to be the filename of the original upload. + type: string + filename: + description: The original filename of the uploaded file. + type: string + info: + description: Information about the file referred to in ``url``. + properties: + mimetype: + description: The mimetype of the file e.g. ``application/msword``. + type: string + size: + description: The size of the file in bytes. + type: integer + thumbnail_url: + description: The URL to the thumbnail of the file. + type: string + thumbnail_info: + allOf: + - $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml + description: Metadata about the image referred to in ``thumbnail_url``. + title: FileInfo + type: object + msgtype: + enum: + - m.file + type: string + url: + description: The URL to the file. + type: string + required: + - msgtype + - body + - url + - filename + type: object + type: + enum: + - m.room.message + type: string +title: FileMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.image b/event-schemas/schema/m.room.message#m.image new file mode 100644 index 00000000000..1237b8f8004 --- /dev/null +++ b/event-schemas/schema/m.room.message#m.image @@ -0,0 +1,32 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: This message represents a single image and an optional thumbnail. +properties: + content: + properties: + body: + description: "A textual representation of the image. This could be the alt text of the image, the filename of the image, or some kind of content description for accessibility e.g. 'image attachment'." + type: string + info: + allOf: + - $ref: core-event-schema/msgtype_infos/image_info.yaml + description: Metadata about the image referred to in ``url``. + msgtype: + enum: + - m.image + type: string + url: + description: The URL to the image. + type: string + required: + - msgtype + - body + - url + type: object + type: + enum: + - m.room.message + type: string +title: ImageMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.location b/event-schemas/schema/m.room.message#m.location new file mode 100644 index 00000000000..e8d55769f29 --- /dev/null +++ b/event-schemas/schema/m.room.message#m.location @@ -0,0 +1,39 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: This message represents a real-world location. +properties: + content: + properties: + body: + description: "A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'." + type: string + geo_uri: + description: A geo URI representing this location. + type: string + msgtype: + enum: + - m.location + type: string + info: + type: object + properties: + thumbnail_url: + description: The URL to a thumbnail of the location being represented. + type: string + thumbnail_info: + allOf: + - $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml + description: Metadata about the image referred to in ``thumbnail_url``. + title: LocationInfo + required: + - msgtype + - body + - geo_uri + type: object + type: + enum: + - m.room.message + type: string +title: LocationMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.notice b/event-schemas/schema/m.room.message#m.notice new file mode 100644 index 00000000000..ef97e28ade6 --- /dev/null +++ b/event-schemas/schema/m.room.message#m.notice @@ -0,0 +1,24 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: 'The ``m.notice`` type is primarily intended for responses from automated clients. An ``m.notice`` message must be treated the same way as a regular ``m.text`` message with two exceptions. Firstly, clients should present ``m.notice`` messages to users in a distinct manner, and secondly, ``m.notice`` messages must never be automatically responded to. This helps to prevent infinite-loop situations where two automated clients continuously exchange messages.' +properties: + content: + properties: + body: + description: The notice text to send. + type: string + msgtype: + enum: + - m.notice + type: string + required: + - msgtype + - body + type: object + type: + enum: + - m.room.message + type: string +title: NoticeMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.text b/event-schemas/schema/m.room.message#m.text new file mode 100644 index 00000000000..2720172dd69 --- /dev/null +++ b/event-schemas/schema/m.room.message#m.text @@ -0,0 +1,24 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: This message is the most basic message and is used to represent text. +properties: + content: + properties: + body: + description: The body of the message. + type: string + msgtype: + enum: + - m.text + type: string + required: + - msgtype + - body + type: object + type: + enum: + - m.room.message + type: string +title: TextMessage +type: object diff --git a/event-schemas/schema/m.room.message#m.video b/event-schemas/schema/m.room.message#m.video new file mode 100644 index 00000000000..a0240b5486e --- /dev/null +++ b/event-schemas/schema/m.room.message#m.video @@ -0,0 +1,55 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: This message represents a single video clip. +properties: + content: + properties: + body: + description: "A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'." + type: string + info: + description: Metadata about the video clip referred to in ``url``. + properties: + duration: + description: The duration of the video in milliseconds. + type: integer + h: + description: The height of the video in pixels. + type: integer + w: + description: The width of the video in pixels. + type: integer + mimetype: + description: The mimetype of the video e.g. ``video/mp4``. + type: string + size: + description: The size of the video in bytes. + type: integer + thumbnail_url: + description: The URL to an image thumbnail of the video clip. + type: string + thumbnail_info: + allOf: + - $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml + description: Metadata about the image referred to in ``thumbnail_url``. + title: VideoInfo + type: object + msgtype: + enum: + - m.video + type: string + url: + description: The URL to the video clip. + type: string + required: + - msgtype + - body + - url + type: object + type: + enum: + - m.room.message + type: string +title: VideoMessage +type: object diff --git a/event-schemas/schema/m.room.message.feedback b/event-schemas/schema/m.room.message.feedback new file mode 100644 index 00000000000..fa3390fa776 --- /dev/null +++ b/event-schemas/schema/m.room.message.feedback @@ -0,0 +1,26 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: '**NB: Usage of this event is discouraged in favour of the** `receipts module`_. **Most clients will not recognise this event.** Feedback events are events sent to acknowledge a message in some way. There are two supported acknowledgements: ``delivered`` (sent when the event has been received) and ``read`` (sent when the event has been observed by the end-user). The ``target_event_id`` should reference the ``m.room.message`` event being acknowledged.' +properties: + content: + properties: + target_event_id: + description: The event that this feedback is related to. + type: string + type: + description: The type of feedback. + enum: + - delivered + - read + type: string + required: + - type + - target_event_id + type: object + type: + enum: + - m.room.message.feedback + type: string +title: MessageFeedback +type: object diff --git a/event-schemas/schema/m.room.name b/event-schemas/schema/m.room.name new file mode 100644 index 00000000000..3e3d15ace0e --- /dev/null +++ b/event-schemas/schema/m.room.name @@ -0,0 +1,34 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: |- + A room has an opaque room ID which is not human-friendly to read. A room + alias is human-friendly, but not all rooms have room aliases. The room name + is a human-friendly string designed to be displayed to the end-user. The + room name is not unique, as multiple rooms can have the same room name set. + + A room with an ``m.room.name`` event with an absent, null, or empty + ``name`` field should be treated the same as a room with no ``m.room.name`` + event. + + An event of this type is automatically created when creating a room using + ``/createRoom`` with the ``name`` key. +properties: + content: + properties: + name: + description: The name of the room. This MUST NOT exceed 255 bytes. + type: string + required: + - name + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.name + type: string +title: RoomName +type: object diff --git a/event-schemas/schema/m.room.power_levels b/event-schemas/schema/m.room.power_levels new file mode 100644 index 00000000000..0353b166a7e --- /dev/null +++ b/event-schemas/schema/m.room.power_levels @@ -0,0 +1,86 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: |- + This event specifies the minimum level a user must have in order to perform a + certain action. It also specifies the levels of each user in the room. + + If a ``user_id`` is in the ``users`` list, then that ``user_id`` has the + associated power level. Otherwise they have the default level + ``users_default``. If ``users_default`` is not supplied, it is assumed to be + 0. + + The level required to send a certain event is governed by ``events``, + ``state_default`` and ``events_default``. If an event type is specified in + ``events``, then the user must have at least the level specified in order to + send that event. If the event type is not supplied, it defaults to + ``events_default`` for Message Events and ``state_default`` for State + Events. + + If there is no ``state_default`` in the ``m.room.power_levels`` event, the + ``state_default`` is 50. If there is no ``events_default`` in the + ``m.room.power_levels`` event, the ``events_default`` is 0. If the room + contains no ``m.room.power_levels`` event, *both* the ``state_default`` and + ``events_default`` are 0. + + The power level required to invite a user to the room, kick a user from the + room, ban a user from the room, or redact an event, is defined by ``invite``, + ``kick``, ``ban``, and ``redact``, respectively. Each of these levels defaults + to 50 if they are not specified in the ``m.room.power_levels`` event, or if + the room contains no ``m.room.power_levels`` event. + +properties: + content: + properties: + ban: + description: The level required to ban a user. Defaults to 50 if unspecified. + type: number + events: + additionalProperties: + type: number + description: The level required to send specific event types. This is a mapping from event type to power level required. + title: Event power levels + type: object + events_default: + description: |- + The default level required to send message events. Can be + overridden by the ``events`` key. Defaults to 0 if unspecified. + type: number + invite: + description: The level required to invite a user. Defaults to 50 if unspecified. + type: number + kick: + description: The level required to kick a user. Defaults to 50 if unspecified. + type: number + redact: + description: The level required to redact an event. Defaults to 50 if unspecified. + type: number + state_default: + description: |- + The default level required to send state events. Can be overridden + by the ``events`` key. Defaults to 50 if unspecified, but 0 if + there is no ``m.room.power_levels`` event at all. + type: number + users: + additionalProperties: + type: number + description: The power levels for specific users. This is a mapping from ``user_id`` to power level for that user. + title: User power levels + type: object + users_default: + description: |- + The default power level for every user in the room, unless their + ``user_id`` is mentioned in the ``users`` key. Defaults to 0 if + unspecified. + type: number + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.power_levels + type: string +title: Defines the power levels (privileges) of users in the room. +type: object diff --git a/event-schemas/schema/m.room.redaction b/event-schemas/schema/m.room.redaction new file mode 100644 index 00000000000..b3bc418e9c6 --- /dev/null +++ b/event-schemas/schema/m.room.redaction @@ -0,0 +1,22 @@ +--- +allOf: + - $ref: core-event-schema/room_event.yaml +description: 'Events can be redacted by either room or server admins. Redacting an event means that all keys not required by the protocol are stripped off, allowing admins to remove offensive or illegal content that may have been attached to any event. This cannot be undone, allowing server owners to physically delete the offending data. There is also a concept of a moderator hiding a message event, which can be undone, but cannot be applied to state events. The event that has been redacted is specified in the ``redacts`` event level key.' +properties: + content: + properties: + reason: + description: 'The reason for the redaction, if any.' + type: string + type: object + redacts: + description: The event ID that was redacted. + type: string + type: + enum: + - m.room.redaction + type: string +required: + - redacts +title: Redaction +type: object diff --git a/event-schemas/schema/m.room.third_party_invite b/event-schemas/schema/m.room.third_party_invite new file mode 100644 index 00000000000..794bd232a74 --- /dev/null +++ b/event-schemas/schema/m.room.third_party_invite @@ -0,0 +1,46 @@ +--- +$schema: http://json-schema.org/draft-04/schema# +allOf: + - $ref: core-event-schema/state_event.yaml +description: "Acts as an ``m.room.member`` invite event, where there isn't a target user_id to invite. This event contains a token and a public key whose private key must be used to sign the token. Any user who can present that signature may use this invitation to join the target room." +properties: + content: + properties: + display_name: + description: "A user-readable string which represents the user who has been invited. This should not contain the user's third party ID, as otherwise when the invite is accepted it would leak the association between the matrix ID and the third party ID." + type: string + key_validity_url: + description: "A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'." + type: string + public_key: + description: A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in public_keys is also sufficient). This exists for backwards compatibility. + type: string + public_keys: + description: Keys with which the token may be signed. + items: + properties: + key_validity_url: + description: "An optional URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'. If this URL is absent, the key must be considered valid indefinitely." + type: string + public_key: + description: A base-64 encoded ed25519 key with which token may be signed. + type: string + required: + - public_key + title: PublicKeys + type: object + type: array + required: + - display_name + - key_validity_url + - public_key + type: object + state_key: + description: 'The token, of which a signature must be produced in order to join the room.' + type: string + type: + enum: + - m.room.third_party_invite + type: string +title: 'An invitation to a room issued to a third party identifier, rather than a matrix user ID.' +type: object diff --git a/event-schemas/schema/m.room.topic b/event-schemas/schema/m.room.topic new file mode 100644 index 00000000000..ad2a3ba251e --- /dev/null +++ b/event-schemas/schema/m.room.topic @@ -0,0 +1,23 @@ +--- +allOf: + - $ref: core-event-schema/state_event.yaml +description: 'A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using ``/createRoom`` with the ``topic`` key.' +properties: + content: + properties: + topic: + description: The topic text. + type: string + required: + - topic + type: object + state_key: + description: A zero-length string. + pattern: '^$' + type: string + type: + enum: + - m.room.topic + type: string +title: Topic +type: object diff --git a/event-schemas/schema/m.tag b/event-schemas/schema/m.tag new file mode 100644 index 00000000000..80d3f9dd362 --- /dev/null +++ b/event-schemas/schema/m.tag @@ -0,0 +1,28 @@ +{ + "type": "object", + "title": "Tag Event", + "description": "Informs the client of tags on a room.", + "allOf": [{ + "$ref": "core-event-schema/event.yaml" + }], + "properties": { + "type": { + "type": "string", + "enum": ["m.tag"] + }, + "content": { + "type": "object", + "properties": { + "tags": { + "type": "object", + "description": "The tags on the room and their contents.", + "additionalProperties": { + "title": "Tag", + "type": "object" + } + } + } + } + }, + "required": ["type", "content"] +} diff --git a/event-schemas/schema/v1/m.typing b/event-schemas/schema/m.typing similarity index 91% rename from event-schemas/schema/v1/m.typing rename to event-schemas/schema/m.typing index b712f6ecb83..705b3b6c7af 100644 --- a/event-schemas/schema/v1/m.typing +++ b/event-schemas/schema/m.typing @@ -2,6 +2,9 @@ "type": "object", "title": "Typing Event", "description": "Informs the client of the list of users currently typing.", + "allOf": [{ + "$ref": "core-event-schema/event.yaml" + }], "properties": { "content": { "type": "object", diff --git a/event-schemas/schema/v1/core-event-schema/core-event-schema b/event-schemas/schema/v1/core-event-schema/core-event-schema deleted file mode 120000 index 945c9b46d68..00000000000 --- a/event-schemas/schema/v1/core-event-schema/core-event-schema +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/event-schemas/schema/v1/core-event-schema/event.json b/event-schemas/schema/v1/core-event-schema/event.json deleted file mode 100644 index e73aec809cc..00000000000 --- a/event-schemas/schema/v1/core-event-schema/event.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "object", - "title": "Event", - "description": "The basic set of fields all events must have.", - "properties": { - "content": { - "type": "object", - "description": "The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body." - }, - "type": { - "type": "string", - "description": "The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type'" - } - } -} diff --git a/event-schemas/schema/v1/core-event-schema/msgtype_infos/image_info.json b/event-schemas/schema/v1/core-event-schema/msgtype_infos/image_info.json deleted file mode 100644 index ee75745eabf..00000000000 --- a/event-schemas/schema/v1/core-event-schema/msgtype_infos/image_info.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "ImageInfo", - "description": "Metadata about an image.", - "properties": { - "size": { - "type": "integer", - "description": "Size of the image in bytes." - }, - "w": { - "type": "integer", - "description": "The width of the image in pixels." - }, - "h": { - "type": "integer", - "description": "The height of the image in pixels." - }, - "mimetype": { - "type": "string", - "description": "The mimetype of the image, e.g. ``image/jpeg``." - } - } -} diff --git a/event-schemas/schema/v1/core-event-schema/room_event.json b/event-schemas/schema/v1/core-event-schema/room_event.json deleted file mode 100644 index d5413f8a0e5..00000000000 --- a/event-schemas/schema/v1/core-event-schema/room_event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "type": "object", - "title": "Room Event", - "description": "In addition to the Event fields, Room Events MUST have the following additional field.", - "allOf":[{ - "$ref": "core-event-schema/event.json" - }], - "properties": { - "room_id": { - "type": "string", - "description": "The ID of the room associated with this event." - }, - "event_id": { - "type": "string", - "description": "The globally unique event identifier." - }, - "user_id": { - "type": "string", - "description": "Contains the fully-qualified ID of the user who *sent* this event." - } - }, - "required": ["room_id"] -} diff --git a/event-schemas/schema/v1/core-event-schema/state_event.json b/event-schemas/schema/v1/core-event-schema/state_event.json deleted file mode 100644 index f70118f49c7..00000000000 --- a/event-schemas/schema/v1/core-event-schema/state_event.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "title": "State Event", - "description": "In addition to the Room Event fields, State Events have the following additional fields.", - "allOf":[{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "state_key": { - "type": "string", - "description": "A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event." - }, - "prev_content": { - "type": "object", - "description": "Optional. The previous ``content`` for this event. If there is no previous content, this key will be missing." - } - }, - "required": ["state_key"] -} diff --git a/event-schemas/schema/v1/m.room.aliases b/event-schemas/schema/v1/m.room.aliases deleted file mode 100644 index 43cc9dbcded..00000000000 --- a/event-schemas/schema/v1/m.room.aliases +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "object", - "title": "Informs the room about what room aliases it has been given.", - "description": "This event is sent by a homeserver directly to inform of changes to the list of aliases it knows about for that room. The ``state_key`` for this event is set to the homeserver which owns the room alias. The entire set of known aliases for the room is the union of all the ``m.room.aliases`` events, one for each homeserver. Clients **should** check the validity of any room alias given in this list before presenting it to the user as trusted fact. The lists given by this event should be considered simply as advice on which aliases might exist, for which the client can perform the lookup to confirm whether it receives the correct room ID.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "aliases": { - "type": "array", - "description": "A list of room aliases.", - "items": { - "type": "string" - } - } - }, - "required": ["aliases"] - }, - "state_key": { - "type": "string", - "description": "The homeserver domain which owns these room aliases." - }, - "type": { - "type": "string", - "enum": ["m.room.aliases"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.canonical_alias b/event-schemas/schema/v1/m.room.canonical_alias deleted file mode 100644 index 25cd00c0868..00000000000 --- a/event-schemas/schema/v1/m.room.canonical_alias +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "object", - "title": "Informs the room as to which alias is the canonical one.", - "description": "This event is used to inform the room about which alias should be considered the canonical one. This could be for display purposes or as suggestion to users which alias to use to advertise the room.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "alias": { - "type": "string", - "description": "The canonical alias." - } - }, - "required": ["alias"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.canonical_alias"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.create b/event-schemas/schema/v1/m.room.create deleted file mode 100644 index 348b6d86eeb..00000000000 --- a/event-schemas/schema/v1/m.room.create +++ /dev/null @@ -1,33 +0,0 @@ -{ - "type": "object", - "title": "The first event in the room.", - "description": "This is the first event in a room and cannot be changed. It acts as the root of all other events.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "creator": { - "type": "string", - "description": "The ``user_id`` of the room creator. This is set by the homeserver." - }, - "m.federate": { - "type": "boolean", - "description": "Whether users on other servers can join this room. Defaults to ``true`` if key does not exist." - } - }, - "required": ["creator"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.create"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.history_visibility b/event-schemas/schema/v1/m.room.history_visibility deleted file mode 100644 index bc33f4b3164..00000000000 --- a/event-schemas/schema/v1/m.room.history_visibility +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "Controls visibility of history.", - "description": "This event controls whether a member of a room can see the events that happened in a room from before they joined.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "history_visibility": { - "type": "string", - "description": "Who can see the room history.", - "enum": ["invited","joined","shared"] - } - }, - "required": ["history_visibility"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.history_visibility"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.join_rules b/event-schemas/schema/v1/m.room.join_rules deleted file mode 100644 index 70c36ccb156..00000000000 --- a/event-schemas/schema/v1/m.room.join_rules +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "Describes how users are allowed to join the room.", - "description": "A room may be ``public`` meaning anyone can join the room without any prior action. Alternatively, it can be ``invite`` meaning that a user who wishes to join the room must first receive an invite to the room from someone already inside of the room. Currently, ``knock`` and ``private`` are reserved keywords which are not implemented.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "join_rule": { - "type": "string", - "description": "The type of rules used for users wishing to join this room.", - "enum": ["public","knock","invite","private"] - } - }, - "required": ["join_rule"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.join_rules"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.member b/event-schemas/schema/v1/m.room.member deleted file mode 100644 index 912f6cf30c1..00000000000 --- a/event-schemas/schema/v1/m.room.member +++ /dev/null @@ -1,84 +0,0 @@ -{ - "type": "object", - "title": "The current membership state of a user in the room.", - "description": "Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms//invite`` etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail. The ``third_party_invite`` property will be set if the invite was an ``m.room.third_party_invite`` event, and absent if the invite was an ``m.room.member`` event.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "membership": { - "type": "string", - "description": "The membership state of the user.", - "enum": ["invite","join","knock","leave","ban"] - }, - "avatar_url": { - "type": "string", - "description": "The avatar URL for this user, if any. This is added by the homeserver." - }, - "displayname": { - "type": ["string", "null"], - "description": "The display name for this user, if any. This is added by the homeserver." - }, - "third_party_invite": { - "type": "object", - "title": "invite", - "properties": { - "token": { - "type": "string", - "description": "A token which must be correctly signed, in order to join the room." - }, - "key_validity_url": { - "type": "string", - "description": "A URL which can be fetched, with querystring ``public_key=public_key``, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'." - }, - "public_key": { - "type": "string", - "description": "A base64-encoded ed25519 key with which token must be signed." - }, - "signature": { - "type": "string", - "description": "A base64-encoded signature of token with public_key." - }, - "sender": { - "type": "string", - "description": "The matrix user ID of the user who send the invite which is being used." - } - }, - "required": ["token", "key_validity_url", "public_key", "signature", "sender"] - } - }, - "required": ["membership"] - }, - "state_key": { - "type": "string", - "description": "The ``user_id`` this membership event relates to." - }, - "type": { - "type": "string", - "enum": ["m.room.member"] - }, - "invite_room_state": { - "type": "array", - "description": "A subset of the state of the room at the time of the invite, if ``membership`` is ``invite``", - "items": { - "type": "object", - "title": "StateEvent", - "description": "A stripped down state event, with only the ``type``, ``state_key`` and ``content`` keys.", - "properties": { - "type": { - "type": "string" - }, - "state_key": { - "type": "string" - }, - "content": { - "type": "object" - } - } - } - } - } -} diff --git a/event-schemas/schema/v1/m.room.message b/event-schemas/schema/v1/m.room.message deleted file mode 100644 index 27b7e925e30..00000000000 --- a/event-schemas/schema/v1/m.room.message +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "title": "Message", - "description": "This event is used when sending messages in a room. Messages are not limited to be text. The ``msgtype`` key outlines the type of message, e.g. text, audio, image, video, etc. The ``body`` key is text and MUST be used with every kind of ``msgtype`` as a fallback mechanism for when a client cannot render a message.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "description": "The type of message, e.g. ``m.image``, ``m.text``" - }, - "body": { - "type": "string", - "description": "The textual representation of this message." - } - }, - "required": ["msgtype", "body"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.audio b/event-schemas/schema/v1/m.room.message#m.audio deleted file mode 100644 index 0a6625f5417..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.audio +++ /dev/null @@ -1,51 +0,0 @@ -{ - "type": "object", - "title": "AudioMessage", - "description": "This message represents a single audio clip.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.audio"] - }, - "body": { - "type": "string", - "description": "A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'." - }, - "url": { - "type": "string", - "description": "The URL to the audio clip." - }, - "info": { - "type": "object", - "title": "AudioInfo", - "description": "Metadata for the audio clip referred to in ``url``.", - "properties": { - "mimetype": { - "type": "string", - "description": "The mimetype of the audio e.g. ``audio/aac``." - }, - "size": { - "type": "integer", - "description": "The size of the audio clip in bytes." - }, - "duration": { - "type": "integer", - "description": "The duration of the audio in milliseconds." - } - } - } - }, - "required": ["msgtype", "body", "url"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.emote b/event-schemas/schema/v1/m.room.message#m.emote deleted file mode 100644 index 690600f4621..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.emote +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "title": "EmoteMessage", - "description": "This message is similar to ``m.text`` except that the sender is 'performing' the action contained in the ``body`` key, similar to ``/me`` in IRC. This message should be prefixed by the name of the sender. This message could also be represented in a different colour to distinguish it from regular ``m.text`` messages.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.emote"] - }, - "body": { - "type": "string", - "description": "The emote action to perform." - } - }, - "required": ["msgtype", "body"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.file b/event-schemas/schema/v1/m.room.message#m.file deleted file mode 100644 index c97480ef808..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.file +++ /dev/null @@ -1,63 +0,0 @@ -{ - "type": "object", - "title": "FileMessage", - "description": "This message represents a generic file.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.file"] - }, - "filename": { - "type": "string", - "description": "The original filename of the uploaded file." - }, - "body": { - "type": "string", - "description": "A human-readable description of the file. This is recommended to be the filename of the original upload." - }, - "url": { - "type": "string", - "description": "The URL to the file." - }, - "info": { - "type": "object", - "title": "FileInfo", - "description": "Information about the file referred to in ``url``.", - "properties": { - "size": { - "type": "integer", - "description": "The size of the file in bytes." - }, - "mimetype": { - "type": "string", - "description": "The mimetype of the file e.g. ``application/msword``." - } - } - }, - "thumbnail_url": { - "type": "string", - "description": "The URL to the thumbnail of the file." - }, - "thumbnail_info": { - "type": "object", - "title": "ImageInfo", - "description": "Metadata about the image referred to in ``thumbnail_url``.", - "allOf": [{ - "$ref": "core-event-schema/msgtype_infos/image_info.json" - }] - } - }, - "required": ["msgtype", "body", "url", "filename"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.image b/event-schemas/schema/v1/m.room.message#m.image deleted file mode 100644 index af78096d67a..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.image +++ /dev/null @@ -1,67 +0,0 @@ -{ - "type": "object", - "title": "ImageMessage", - "description": "This message represents a single image and an optional thumbnail.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.image"] - }, - "body": { - "type": "string", - "description": "A textual representation of the image. This could be the alt text of the image, the filename of the image, or some kind of content description for accessibility e.g. 'image attachment'." - }, - "url": { - "type": "string", - "description": "The URL to the image." - }, - "thumbnail_url": { - "type": "string", - "description": "The URL to the thumbnail of the image." - }, - "thumbnail_info": { - "type": "object", - "title": "ImageInfo", - "description": "Metadata about the image referred to in ``thumbnail_url``.", - "allOf": [{ - "$ref": "core-event-schema/msgtype_infos/image_info.json" - }] - }, - "info": { - "type": "object", - "title": "ImageInfo", - "description": "Metadata about the image referred to in ``url``.", - "properties": { - "size": { - "type": "integer", - "description": "Size of the image in bytes." - }, - "w": { - "type": "integer", - "description": "The width of the image in pixels." - }, - "h": { - "type": "integer", - "description": "The height of the image in pixels." - }, - "mimetype": { - "type": "string", - "description": "The mimetype of the image, e.g. ``image/jpeg``." - } - } - } - }, - "required": ["msgtype", "body", "url"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.location b/event-schemas/schema/v1/m.room.message#m.location deleted file mode 100644 index ef4b5e815a1..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.location +++ /dev/null @@ -1,43 +0,0 @@ -{ - "type": "object", - "title": "LocationMessage", - "description": "This message represents a real-world location.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.location"] - }, - "body": { - "type": "string", - "description": "A description of the location e.g. 'Big Ben, London, UK', or some kind of content description for accessibility e.g. 'location attachment'." - }, - "geo_uri": { - "type": "string", - "description": "A geo URI representing this location." - }, - "thumbnail_url": { - "type": "string", - "description": "The URL to a thumbnail of the location being represented." - }, - "thumbnail_info": { - "type": "object", - "title": "ImageInfo", - "allOf": [{ - "$ref": "core-event-schema/msgtype_infos/image_info.json" - }] - } - }, - "required": ["msgtype", "body", "geo_uri"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.notice b/event-schemas/schema/v1/m.room.message#m.notice deleted file mode 100644 index 195c56a3b71..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.notice +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "title": "NoticeMessage", - "description": "A m.notice message should be considered similar to a plain m.text message except that clients should visually distinguish it in some way. It is intended to be used by automated clients, such as bots, bridges, and other entities, rather than humans. Additionally, such automated agents which watch a room for messages and respond to them ought to ignore m.notice messages. This helps to prevent infinite-loop situations where two automated clients continuously exchange messages, as each responds to the other.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.notice"] - }, - "body": { - "type": "string", - "description": "The notice text to send." - } - }, - "required": ["msgtype", "body"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.text b/event-schemas/schema/v1/m.room.message#m.text deleted file mode 100644 index de59cf0eaf2..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.text +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "title": "TextMessage", - "description": "This message is the most basic message and is used to represent text.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.text"] - }, - "body": { - "type": "string", - "description": "The body of the message." - } - }, - "required": ["msgtype", "body"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message#m.video b/event-schemas/schema/v1/m.room.message#m.video deleted file mode 100644 index ad68e72a1f7..00000000000 --- a/event-schemas/schema/v1/m.room.message#m.video +++ /dev/null @@ -1,70 +0,0 @@ -{ - "type": "object", - "title": "VideoMessage", - "description": "This message represents a single video clip.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "msgtype": { - "type": "string", - "enum": ["m.video"] - }, - "body": { - "type": "string", - "description": "A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'." - }, - "url": { - "type": "string", - "description": "The URL to the video clip." - }, - "info": { - "type": "object", - "title": "VideoInfo", - "description": "Metadata about the video clip referred to in ``url``.", - "properties": { - "mimetype": { - "type": "string", - "description": "The mimetype of the video e.g. ``video/mp4``." - }, - "size": { - "type": "integer", - "description": "The size of the video in bytes." - }, - "duration": { - "type": "integer", - "description": "The duration of the video in milliseconds." - }, - "w": { - "type": "integer", - "description": "The width of the video in pixels." - }, - "h": { - "type": "integer", - "description": "The height of the video in pixels." - }, - "thumbnail_url": { - "type": "string", - "description": "The URL to a thumbnail of the video clip." - }, - "thumbnail_info": { - "type": "object", - "title": "ImageInfo", - "allOf": [{ - "$ref": "core-event-schema/msgtype_infos/image_info.json" - }] - } - } - } - }, - "required": ["msgtype", "body", "url"] - }, - "type": { - "type": "string", - "enum": ["m.room.message"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.message.feedback b/event-schemas/schema/v1/m.room.message.feedback deleted file mode 100644 index 1bbfc1ba962..00000000000 --- a/event-schemas/schema/v1/m.room.message.feedback +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "object", - "title": "MessageFeedback", - "description": "Feedback events are events sent to acknowledge a message in some way. There are two supported acknowledgements: ``delivered`` (sent when the event has been received) and ``read`` (sent when the event has been observed by the end-user). The ``target_event_id`` should reference the ``m.room.message`` event being acknowledged. N.B. not implemented in Synapse, and superceded in v2 CS API by the ``relates_to`` event field.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "The type of feedback.", - "enum": ["delivered", "read"] - }, - "target_event_id": { - "type": "string", - "description": "The event that this feedback is related to." - } - }, - "required": ["type", "target_event_id"] - }, - "type": { - "type": "string", - "enum": ["m.room.message.feedback"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.name b/event-schemas/schema/v1/m.room.name deleted file mode 100644 index b43f02ccfc8..00000000000 --- a/event-schemas/schema/v1/m.room.name +++ /dev/null @@ -1,29 +0,0 @@ -{ - "title": "RoomName", - "description": "A room has an opaque room ID which is not human-friendly to read. A room alias is human-friendly, but not all rooms have room aliases. The room name is a human-friendly string designed to be displayed to the end-user. The room name is not unique, as multiple rooms can have the same room name set. The room name can also be set when creating a room using ``/createRoom`` with the ``name`` key.", - "type": "object", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the room." - } - }, - "required": ["name"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.name"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.power_levels b/event-schemas/schema/v1/m.room.power_levels deleted file mode 100644 index 2ef3bfa3b9c..00000000000 --- a/event-schemas/schema/v1/m.room.power_levels +++ /dev/null @@ -1,66 +0,0 @@ -{ - "type": "object", - "title": "Defines the power levels (privileges) of users in the room.", - "description": "This event specifies the minimum level a user must have in order to perform a certain action. It also specifies the levels of each user in the room. If a ``user_id`` is in the ``users`` list, then that ``user_id`` has the associated power level. Otherwise they have the default level ``users_default``. If ``users_default`` is not supplied, it is assumed to be 0. The level required to send a certain event is governed by ``events``, ``state_default`` and ``events_default``. If an event type is specified in ``events``, then the user must have at least the level specified in order to send that event. If the event type is not supplied, it defaults to ``events_default`` for Message Events and ``state_default`` for State Events.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "ban": { - "type": "number", - "description": "The level required to ban a user." - }, - "events_default": { - "type": "number", - "description": "The default level required to send message events. Can be overridden by the ``events`` key." - }, - "kick": { - "type": "number", - "description": "The level required to kick a user." - }, - "redact": { - "type": "number", - "description": "The level required to redact an event." - }, - "state_default": { - "type": "number", - "description": "The default level required to send state events. Can be overridden by the ``events`` key." - }, - "users_default": { - "type": "number", - "description": "The default power level for every user in the room, unless their ``user_id`` is mentioned in the ``users`` key." - }, - "events": { - "type": "object", - "title": "Event power levels", - "description": "The level required to send specific event types. This is a mapping from event type to power level required.", - "additionalProperties": { - "type": "number" - } - }, - "users": { - "type": "object", - "title": "User power levels", - "description": "The power levels for specific users. This is a mapping from ``user_id`` to power level for that user.", - "additionalProperties": { - "type": "number" - } - } - }, - "required": ["ban","events","events_default","kick","redact", - "state_default","users"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.power_levels"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.redaction b/event-schemas/schema/v1/m.room.redaction deleted file mode 100644 index 5c3ef391617..00000000000 --- a/event-schemas/schema/v1/m.room.redaction +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "title": "Redaction", - "description": "Events can be redacted by either room or server admins. Redacting an event means that all keys not required by the protocol are stripped off, allowing admins to remove offensive or illegal content that may have been attached to any event. This cannot be undone, allowing server owners to physically delete the offending data. There is also a concept of a moderator hiding a message event, which can be undone, but cannot be applied to state events. The event that has been redacted is specified in the ``redacts`` event level key.", - "allOf": [{ - "$ref": "core-event-schema/room_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "The reason for the redaction, if any." - } - } - }, - "redacts": { - "type": "string", - "description": "The event ID that was redacted." - }, - "type": { - "type": "string", - "enum": ["m.room.redaction"] - } - }, - "required": ["redacts"] -} diff --git a/event-schemas/schema/v1/m.room.third_party_invite b/event-schemas/schema/v1/m.room.third_party_invite deleted file mode 100644 index ba66100f15a..00000000000 --- a/event-schemas/schema/v1/m.room.third_party_invite +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "title": "An invitation to a room issued to a third party identifier, rather than a matrix user ID.", - "description": "Acts as an ``m.room.member`` invite event, where there isn't a target user_id to invite. This event contains a token and a public key whose private key must be used to sign the token. Any user who can present that signature may use this invitation to join the target room.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "display_name": { - "type": "string", - "description": "A user-readable string which represents the user who has been invited. This should not contain the user's third party ID, as otherwise when the invite is accepted it would leak the association between the matrix ID and the third party ID." - }, - "key_validity_url": { - "type": "string", - "description": "A URL which can be fetched, with querystring public_key=public_key, to validate whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'." - }, - "public_key": { - "type": "string", - "description": "A base64-encoded ed25519 key with which token must be signed." - } - }, - "required": ["display_name", "key_validity_url", "public_key"] - }, - "state_key": { - "type": "string", - "description": "The token, of which a signature must be produced in order to join the room." - }, - "type": { - "type": "string", - "enum": ["m.room.third_party_invite"] - } - } -} diff --git a/event-schemas/schema/v1/m.room.topic b/event-schemas/schema/v1/m.room.topic deleted file mode 100644 index d13f0c239e7..00000000000 --- a/event-schemas/schema/v1/m.room.topic +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "object", - "title": "Topic", - "description": "A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using ``/createRoom`` with the ``topic`` key.", - "allOf": [{ - "$ref": "core-event-schema/state_event.json" - }], - "properties": { - "content": { - "type": "object", - "properties": { - "topic": { - "type": "string", - "description": "The topic text." - } - }, - "required": ["topic"] - }, - "state_key": { - "type": "string", - "description": "A zero-length string.", - "pattern": "^$" - }, - "type": { - "type": "string", - "enum": ["m.room.topic"] - } - } -} diff --git a/event-schemas/schema/v1/v1-event-schema b/event-schemas/schema/v1/v1-event-schema deleted file mode 120000 index 945c9b46d68..00000000000 --- a/event-schemas/schema/v1/v1-event-schema +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/jenkins.sh b/jenkins.sh index 0b217e58a87..7cc0badd83e 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -2,8 +2,29 @@ set -ex +virtualenv env +. env/bin/activate +pip install \ + docutils \ + pygments \ + Jinja2 \ + jsonschema \ + PyYAML + +# do sanity checks on the examples and swagger (cd event-schemas/ && ./check_examples.py) (cd api && ./check_examples.py) -(cd scripts && ./gendoc.py -v) -(cd api && npm install && node validator.js -s "client-server/v1" && node validator.js -s "client-server/v2_alpha") -(cd event-schemas/ && ./check.sh) +(cd api && npm install && node validator.js -s "client-server") + +: ${GOPATH:=${WORKSPACE}/.gopath} +mkdir -p "${GOPATH}" +export GOPATH +go get github.com/hashicorp/golang-lru +go get gopkg.in/fsnotify.v1 + +# make sure that the scripts build +(cd scripts/continuserv && go build) +(cd scripts/speculator && go build) + +# build the spec and collect the supporting docs for matrix.org +./scripts/generate-matrix-org-assets diff --git a/meta/documentation_style.rst b/meta/documentation_style.rst index 5cc5ce82cc4..a6cfad51b52 100644 --- a/meta/documentation_style.rst +++ b/meta/documentation_style.rst @@ -19,11 +19,17 @@ RST support lots of different punctuation characters for underlines on sections. Content in the specification MUST use the same characters in order for the complete specification to be merged correctly. These characters are: -- ``=`` : Top-level sections -- ``-`` : Second-level sections -- ``~`` : Third-level sections -- ``+`` : Fourth-level sections -- You should rethink your document layout if you require a fifth level. +- ``=`` +- ``-`` +- ``~`` +- ``+`` +- ``^`` +- ````` +- ``@`` +- ``:`` + +If you find yourself using ``^`` or beyond, you should rethink your document +layout if possible. TODOs ----- diff --git a/scripts/README b/scripts/README deleted file mode 100644 index a65b8577e07..00000000000 --- a/scripts/README +++ /dev/null @@ -1,11 +0,0 @@ -Requirements: - - docutils (for converting RST to HTML) - - Jinja2 (for templating) - -To generate the complete specification along with supporting documentation, run: - python gendoc.py - -The output of this will be inside the "gen" folder. - -Matrix.org only ("gen" folder has matrix.org tweaked pages): - ./matrix-org-gendoc.sh /path/to/matrix.org/includes/nav.html diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000000..5178d5b75c2 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,45 @@ +Generating the HTML for the specification +========================================= + +Requirements: + - docutils (for converting RST to HTML) + - Jinja2 (for templating) + - PyYAML (for reading YAML files) + +Nix[2] users can enter an environment with the appropriate tools and +dependencies available by invoking `nix-shell contrib/shell.nix` in this +directory. + +To generate the complete specification along with supporting documentation, run: + python gendoc.py + +The output of this will be inside the "scripts/gen" folder. + +Matrix.org only ("gen" folder has matrix.org tweaked pages): + ./matrix-org-gendoc.sh /path/to/matrix.org/includes/nav.html + + +Generating the Swagger documentation +==================================== +Swagger[1] is a framework for representing RESTful APIs. We use it to generate +interactive documentation for our APIs. + +Swagger UI reads a JSON description of the API. To generate this file from the +YAML files in the `api` folder, run: + ./dump-swagger.py + +By default, `dump-swagger` will write to `scripts/swagger/api-docs.json`. + +To make use of the generated file, there are a number of options: + * It can be uploaded from your filesystem to an online editor/viewer such as + http://editor.swagger.io/ + * You can run a local HTTP server by running `./swagger-http-server.py`, and + then view the documentation via an online viewer; for example, at + http://petstore.swagger.io/?url=http://localhost:8000/api-docs.json + * You can host the swagger UI yourself: + * download the latest release from https://github.com/swagger-api/swagger-ui + * copy the contents of the 'dist' directory alongside `api-docs.json` + * View the UI via your browser at http://\?url=api-docs.json + +[1] http://swagger.io/ +[2] https://nixos.org/nix/ diff --git a/scripts/codehighlight.css b/scripts/codehighlight.css deleted file mode 100644 index 5c9b0c36f7d..00000000000 --- a/scripts/codehighlight.css +++ /dev/null @@ -1,6 +0,0 @@ -pre.code .comment, code .comment { color: green } -pre.code .keyword, code .keyword { color: darkred; font-weight: bold } -pre.code .name.builtin, code .name.builtin { color: darkred; font-weight: bold } -pre.code .literal.number, code .literal.number { color: blue } -pre.code .name.tag, code .name.tag { color: darkgreen } -pre.code .literal.string, code .literal.string { color: darkblue } diff --git a/scripts/continuserv/index.html b/scripts/continuserv/index.html new file mode 100644 index 00000000000..f698c5b3d26 --- /dev/null +++ b/scripts/continuserv/index.html @@ -0,0 +1,15 @@ + + + + + diff --git a/scripts/continuserv/main.go b/scripts/continuserv/main.go index 573c2c95529..b489e06abea 100644 --- a/scripts/continuserv/main.go +++ b/scripts/continuserv/main.go @@ -25,9 +25,11 @@ import ( var ( port = flag.Int("port", 8000, "Port on which to serve HTTP") - toServe atomic.Value // Always contains valid []byte to serve. May be stale unless wg is zero. - wg sync.WaitGroup // Indicates how many updates are pending. - mu sync.Mutex // Prevent multiple updates in parallel. + mu sync.Mutex // Prevent multiple updates in parallel. + toServe atomic.Value // Always contains a bytesOrErr. May be stale unless wg is zero. + + wgMu sync.Mutex // Prevent multiple calls to wg.Wait() or wg.Add(positive number) in parallel. + wg sync.WaitGroup // Indicates how many updates are pending. ) func main() { @@ -48,7 +50,13 @@ func main() { } } - filepath.Walk(dir, makeWalker(w)) + walker := makeWalker(dir, w) + paths := []string{"api", "changelogs", "event-schemas", "scripts", + "specification", "templating"} + + for _, p := range paths { + filepath.Walk(path.Join(dir, p), walker) + } wg.Add(1) populateOnce(dir) @@ -75,13 +83,31 @@ func watchFS(ch chan struct{}, w *fsnotify.Watcher) { } } -func makeWalker(w *fsnotify.Watcher) filepath.WalkFunc { - return func(path string, _ os.FileInfo, err error) error { +func makeWalker(base string, w *fsnotify.Watcher) filepath.WalkFunc { + return func(path string, i os.FileInfo, err error) error { if err != nil { log.Fatalf("Error walking: %v", err) } + if !i.IsDir() { + // we set watches on directories, not files + return nil + } + + rel, err := filepath.Rel(base, path) + if err != nil { + log.Fatalf("Failed to get relative path of %s: %v", path, err) + } + + // skip a few things that we know don't form part of the spec + if rel == "api/node_modules" || + rel == "scripts/gen" || + rel == "scripts/tmp" { + return filepath.SkipDir + } + + // log.Printf("Adding watch on %s", path) if err := w.Add(path); err != nil { - log.Fatalf("Failed to add watch: %v", err) + log.Fatalf("Failed to add watch on %s: %v", path, err) } return nil } @@ -93,48 +119,114 @@ func filter(e fsnotify.Event) bool { if e.Op != fsnotify.Write { return false } + // Avoid some temp files that vim writes if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(e.Name, ".") { return false } - // Ignore the .git directory - It's very noisy - if strings.Contains(e.Name, "/.git/") { - return false - } - - // Avoid infinite cycles being caused by writing actual output - if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") { - return false - } return true } func serve(w http.ResponseWriter, req *http.Request) { + wgMu.Lock() wg.Wait() - b := toServe.Load().([]byte) - w.Write(b) + wgMu.Unlock() + + m := toServe.Load().(bytesOrErr) + if m.err != nil { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte(m.err.Error())) + return + } + + ok := true + var b []byte + + file := req.URL.Path + if file[0] == '/' { + file = file[1:] + } + b, ok = m.bytes[file] + + if ok && file == "api-docs.json" { + w.Header().Set("Access-Control-Allow-Origin", "*") + } + + if ok { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(b)) + return + } + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(404) + w.Write([]byte("Not found")) } -func populateOnce(dir string) { - defer wg.Done() - mu.Lock() - defer mu.Unlock() +func generate(dir string) (map[string][]byte, error) { cmd := exec.Command("python", "gendoc.py") cmd.Dir = path.Join(dir, "scripts") var b bytes.Buffer cmd.Stderr = &b err := cmd.Run() if err != nil { - toServe.Store([]byte(fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()).Error())) - return + return nil, fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()) + } + + // cheekily dump the swagger docs into the gen directory so that it is + // easy to serve + cmd = exec.Command("python", "dump-swagger.py", "gen/api-docs.json") + cmd.Dir = path.Join(dir, "scripts") + cmd.Stderr = &b + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("error generating api docs: %v\nOutput from dump-swagger:\n%v", err, b.String()) } - specBytes, err := ioutil.ReadFile(path.Join(dir, "scripts", "gen", "specification.html")) + + files := make(map[string][]byte) + base := path.Join(dir, "scripts", "gen") + walker := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + rel, err := filepath.Rel(base, path) + if err != nil { + return fmt.Errorf("Failed to get relative path of %s: %v", path, err) + } + + bytes, err := ioutil.ReadFile(path) + if err != nil { + return err + } + files[rel] = bytes + return nil + } + + if err := filepath.Walk(base, walker); err != nil { + return nil, fmt.Errorf("error reading spec: %v", err) + } + + // load the special index + indexpath := path.Join(dir, "scripts", "continuserv", "index.html") + bytes, err := ioutil.ReadFile(indexpath) if err != nil { - toServe.Store([]byte(fmt.Errorf("error reading spec: %v", err).Error())) - return + return nil, fmt.Errorf("error reading index: %v", err) } - toServe.Store(specBytes) + files[""] = bytes + + return files, nil +} + +func populateOnce(dir string) { + defer wg.Done() + mu.Lock() + defer mu.Unlock() + + files, err := generate(dir) + toServe.Store(bytesOrErr{files, err}) } func doPopulate(ch chan struct{}, dir string) { @@ -143,7 +235,9 @@ func doPopulate(ch chan struct{}, dir string) { select { case <-ch: if pending == 0 { + wgMu.Lock() wg.Add(1) + wgMu.Unlock() } pending++ case <-time.After(10 * time.Millisecond): @@ -159,3 +253,8 @@ func exists(path string) bool { _, err := os.Stat(path) return !os.IsNotExist(err) } + +type bytesOrErr struct { + bytes map[string][]byte // filename -> contents + err error +} diff --git a/scripts/contrib/shell.nix b/scripts/contrib/shell.nix new file mode 100644 index 00000000000..7bdcdffabc3 --- /dev/null +++ b/scripts/contrib/shell.nix @@ -0,0 +1,6 @@ +with import {}; + +(python.buildEnv.override { + extraLibs = with pythonPackages; + [ docutils pyyaml jinja2 pygments ]; +}).env diff --git a/scripts/basic.css b/scripts/css/basic.css similarity index 100% rename from scripts/basic.css rename to scripts/css/basic.css diff --git a/scripts/css/blockquote.css b/scripts/css/blockquote.css new file mode 100644 index 00000000000..151d3bce082 --- /dev/null +++ b/scripts/css/blockquote.css @@ -0,0 +1,5 @@ +blockquote { + margin: 20px 0 30px; + border-left: 5px solid; + padding-left: 20px; +} diff --git a/scripts/css/codehighlight.css b/scripts/css/codehighlight.css new file mode 100644 index 00000000000..fafc43f4758 --- /dev/null +++ b/scripts/css/codehighlight.css @@ -0,0 +1,16 @@ +pre.code .comment, code .comment { color: green } +pre.code .keyword, code .keyword { color: darkred; font-weight: bold } +pre.code .name.builtin, code .name.builtin { color: darkred; font-weight: bold } +pre.code .name.tag, code .name.tag { color: darkgreen } +pre.code .literal, code .literal { color: darkblue } +pre.code .literal.number, code .literal.number { color: blue } + + +/* HTTP Methods have class "name function" */ +pre.code.http .name.function, code.http .name.function { color: black; font-weight: bold } +/* HTTP Paths have class "name namespace" */ +pre.code.http .name.namespace, code.http .name.namespace { color: darkgreen } +/* HTTP "HTTP" strings have class "keyword reserved" */ +pre.code.http .keyword.reserved, code.http .keyword.reserved { color: black; font-weight: bold } +/* HTTP Header names have class "name attribute" */ +pre.code.http .name.attribute, code.http .name.attribute { color: black; font-weight: bold } diff --git a/scripts/nature.css b/scripts/css/nature.css similarity index 100% rename from scripts/nature.css rename to scripts/css/nature.css diff --git a/scripts/dump-swagger.py b/scripts/dump-swagger.py new file mode 100755 index 00000000000..58ec2b84455 --- /dev/null +++ b/scripts/dump-swagger.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python2 + +# dump-swagger reads all of the swagger API docs used in spec generation and +# outputs a JSON file which merges them all, for use as input to a swagger UI +# viewer. +# See https://github.com/swagger-api/swagger-ui for details of swagger-ui. + +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import errno +import json +import logging +import os.path +import re +import shutil +import sys +import yaml + +scripts_dir = os.path.dirname(os.path.abspath(__file__)) +templating_dir = os.path.join(os.path.dirname(scripts_dir), "templating") +api_dir = os.path.join(os.path.dirname(scripts_dir), "api") + +sys.path.insert(0, templating_dir) + +from matrix_templates.units import resolve_references, MatrixUnits + +if len(sys.argv) > 3: + sys.stderr.write("usage: %s [output_file] [client_release_label]\n" % (sys.argv[0],)) + sys.exit(1) + +if len(sys.argv) > 1: + output_file = os.path.abspath(sys.argv[1]) +else: + output_file = os.path.join(scripts_dir, "swagger", "api-docs.json") + +release_label = sys.argv[2] if len(sys.argv) > 2 else "unstable" + +major_version = release_label +match = re.match("^(r\d+)(\.\d+)*$", major_version) +if match: + major_version = match.group(1) + + +logging.basicConfig() + +os.chdir(templating_dir) +apis = MatrixUnits().load_swagger_apis() + +output = { + "basePath": "/", + "consumes": ["application/json"], + "produces": ["application/json"], + "host": "matrix.org:8448", + "schemes": ["https"], + "info": { + "title": "Matrix Client-Server API", + "version": release_label, + }, + "securityDefinitions": {}, + "paths": {}, + "swagger": "2.0", +} + +with open(os.path.join(api_dir, 'client-server', 'definitions', + 'security.yaml')) as f: + output['securityDefinitions'] = yaml.load(f) + +for file, contents in apis.items(): + basePath = contents['basePath'] + for path, methods in contents["paths"].items(): + path = (basePath + path).replace('%CLIENT_MAJOR_VERSION%', + major_version) + for method, spec in methods.items(): + if "tags" in spec.keys(): + if path not in output["paths"]: + output["paths"][path] = {} + output["paths"][path][method] = spec + + +print "Generating %s" % output_file + +try: + os.makedirs(os.path.dirname(output_file)) +except OSError as e: + if e.errno != errno.EEXIST: + raise + +with open(output_file, "w") as f: + text = json.dumps(output, sort_keys=True, indent=4) + text = text.replace("%CLIENT_RELEASE_LABEL%", release_label) + text = text.replace("%CLIENT_MAJOR_VERSION%", major_version) + f.write(text) diff --git a/scripts/gendoc.py b/scripts/gendoc.py index 8db604c4e2e..05d7908ee4d 100755 --- a/scripts/gendoc.py +++ b/scripts/gendoc.py @@ -1,22 +1,36 @@ #! /usr/bin/env python +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from argparse import ArgumentParser from docutils.core import publish_file import copy import fileinput import glob import os +import os.path import re import shutil import subprocess import sys import yaml -os.chdir(os.path.dirname(os.path.abspath(__file__))) - -stylesheets = { - "stylesheet_path": ["basic.css", "nature.css", "codehighlight.css"] -} +script_dir = os.path.dirname(os.path.abspath(__file__)) +docs_dir = os.path.dirname(script_dir) +spec_dir = os.path.join(docs_dir, "specification") +tmp_dir = os.path.join(script_dir, "tmp") VERBOSE = False @@ -112,7 +126,7 @@ def load_with_adjusted_titles(filename, file_stream, title_level, title_styles): line_title_style, title_styles[adjusted_level] )) - + return "".join(rst_lines) @@ -128,11 +142,8 @@ def get_rst(file_info, title_level, title_styles, spec_dir, adjust_titles): ) else: rst = f.read() - if rst[-2:] != "\n\n": - raise Exception( - ("File %s should end with TWO new-line characters to ensure " + - "file concatenation works correctly.") % (file_info,) - ) + + rst += "\n\n" return rst # dicts look like {0: filepath, 1: filepath} where the key is the title level elif isinstance(file_info, dict): @@ -154,13 +165,14 @@ def get_rst(file_info, title_level, title_styles, spec_dir, adjust_titles): def build_spec(target, out_filename): + log("Building templated file %s" % out_filename) with open(out_filename, "wb") as outfile: for file_info in target["files"]: section = get_rst( file_info=file_info, title_level=0, title_styles=target["title_styles"], - spec_dir="../specification/", + spec_dir=spec_dir, adjust_titles=True ) outfile.write(section) @@ -177,6 +189,7 @@ def build_spec(target, out_filename): array in targets.yaml. """ def fix_relative_titles(target, filename, out_filename): + log("Fix relative titles, %s -> %s" % (filename, out_filename)) title_styles = target["title_styles"] relative_title_chars = [ target["relative_title_styles"]["subtitle"], @@ -228,7 +241,8 @@ def fix_relative_titles(target, filename, out_filename): -def rst2html(i, o): +def rst2html(i, o, stylesheets): + log("rst2html %s -> %s" % (i, o)) with open(i, "r") as in_file: with open(o, "w") as out_file: publish_file( @@ -237,40 +251,52 @@ def rst2html(i, o): reader_name="standalone", parser_name="restructuredtext", writer_name="html", - settings_overrides=stylesheets + settings_overrides={ + "stylesheet_path": stylesheets, + }, ) -def run_through_template(input, set_verbose): - tmpfile = './tmp/output' - try: - with open(tmpfile, 'w') as out: - args = [ - 'python', 'build.py', - "-i", "matrix_templates", - "-o", "../scripts/tmp", - "../scripts/"+input - ] - if set_verbose: - args.insert(2, "-v") - log("EXEC: %s" % " ".join(args)) - log(" ==== build.py output ==== ") - print subprocess.check_output( - args, - stderr=out, - cwd="../templating" - ) - except subprocess.CalledProcessError as e: - print e.output - with open(tmpfile, 'r') as f: - sys.stderr.write(f.read() + "\n") - raise +def addAnchors(path): + log("add anchors %s" % path) + + with open(path, "r") as f: + lines = f.readlines() + + replacement = replacement = r'

\n\1' + with open(path, "w") as f: + for line in lines: + line = re.sub(r'()', replacement, line.rstrip()) + line = re.sub(r'(
)', replacement, line.rstrip()) + f.write(line + "\n") + + +def run_through_template(input_files, set_verbose, substitutions): + args = [ + 'python', 'build.py', + "-o", tmp_dir, + "-i", "matrix_templates", + ] + + for k, v in substitutions.items(): + args.append("--substitution=%s=%s" % (k, v)) + if set_verbose: + args.insert(2, "-v") + + args.extend(input_files) + + log("EXEC: %s" % " ".join(args)) + log(" ==== build.py output ==== ") + subprocess.check_call( + args, + cwd=os.path.join(docs_dir, "templating"), + ) """ Extract and resolve groups for the given target in the given targets listing. Args: - targets_listing (str): The path to a YAML file containing a list of targets + all_targets (dict): The parsed YAML file containing a list of targets target_name (str): The name of the target to extract from the listings. Returns: dict: Containing "filees" (a list of file paths), "relative_title_styles" @@ -278,14 +304,12 @@ def run_through_template(input, set_verbose): (a list of characters which represent the global title style to follow, with the top section title first, the second section second, and so on.) """ -def get_build_target(targets_listing, target_name): +def get_build_target(all_targets, target_name): build_target = { "title_styles": [], "relative_title_styles": {}, "files": [] } - with open(targets_listing, "r") as targ_file: - all_targets = yaml.load(targ_file.read()) build_target["title_styles"] = all_targets["title_styles"] build_target["relative_title_styles"] = all_targets["relative_title_styles"] @@ -362,58 +386,144 @@ def logv(line): print "gendoc:V: %s" % line -def prepare_env(): +def cleanup_env(): + shutil.rmtree(tmp_dir) + + +def mkdirp(d) : + if not os.path.exists(d): + os.makedirs(d) + + +def main(targets, dest_dir, keep_intermediates, substitutions): try: - os.makedirs("./gen") - except OSError: - pass + mkdirp(dest_dir) + except Exception as e: + log("Error creating destination directory '%s': %s" % (dest_dir, str(e))) + return 1 try: - os.makedirs("./tmp") - except OSError: - pass + mkdirp(tmp_dir) + except Exception as e: + log("Error creating temporary directory '%s': %s" % (tmp_dir, str(e))) + return 1 + with open(os.path.join(spec_dir, "targets.yaml"), "r") as targ_file: + target_defs = yaml.load(targ_file.read()) + + if targets == ["all"]: + targets = target_defs["targets"].keys() + + log("Building spec [targets=%s]" % targets) + + templated_files = {} # map from target name to templated file + + for target_name in targets: + templated_file = os.path.join(tmp_dir, "templated_%s.rst" % (target_name,)) + + target = get_build_target(target_defs, target_name) + build_spec(target=target, out_filename=templated_file) + templated_files[target_name] = templated_file + + # we do all the templating at once, because it's slow + run_through_template(templated_files.values(), VERBOSE, substitutions) + + stylesheets = glob.glob(os.path.join(script_dir, "css", "*.css")) + + for target_name, templated_file in templated_files.iteritems(): + target = target_defs["targets"].get(target_name) + version_label = None + if target: + version_label = target.get("version_label") + if version_label: + for old, new in substitutions.items(): + version_label = version_label.replace(old, new) + + rst_file = os.path.join(tmp_dir, "spec_%s.rst" % (target_name,)) + if version_label: + d = os.path.join(dest_dir, target_name) + if not os.path.exists(d): + os.mkdir(d) + html_file = os.path.join(d, "%s.html" % version_label) + else: + html_file = os.path.join(dest_dir, "%s.html" % (target_name, )) + + fix_relative_titles( + target=target_defs, filename=templated_file, + out_filename=rst_file, + ) + rst2html(rst_file, html_file, stylesheets=stylesheets) + addAnchors(html_file) -def cleanup_env(): - shutil.rmtree("./tmp") - - -def main(target_name, keep_intermediates): - prepare_env() - log("Building spec [target=%s]" % target_name) - target = get_build_target("../specification/targets.yaml", target_name) - build_spec(target=target, out_filename="tmp/templated_spec.rst") - run_through_template("tmp/templated_spec.rst", VERBOSE) - fix_relative_titles( - target=target, filename="tmp/templated_spec.rst", - out_filename="tmp/full_spec.rst" - ) - shutil.copy("../supporting-docs/howtos/client-server.rst", "tmp/howto.rst") - run_through_template("tmp/howto.rst", False) # too spammy to mark -v on this - rst2html("tmp/full_spec.rst", "gen/specification.html") - rst2html("tmp/howto.rst", "gen/howtos.html") if not keep_intermediates: cleanup_env() + return 0 + + +def list_targets(): + with open(os.path.join(spec_dir, "targets.yaml"), "r") as targ_file: + target_defs = yaml.load(targ_file.read()) + targets = target_defs["targets"].keys() + print "\n".join(targets) + + +def extract_major(s): + major_version = s + match = re.match("^(r\d+)(\.\d+)*$", s) + if match: + major_version = match.group(1) + return major_version + if __name__ == '__main__': parser = ArgumentParser( - "gendoc.py - Generate the Matrix specification as HTML to the gen/ folder." + "gendoc.py - Generate the Matrix specification as HTML." ) parser.add_argument( "--nodelete", "-n", action="store_true", - help="Do not delete intermediate files. They will be found in tmp/" + help="Do not delete intermediate files. They will be found in scripts/tmp/" ) parser.add_argument( - "--target", "-t", default="main", - help="Specify the build target to build from specification/targets.yaml" + "--target", "-t", action="append", + help="Specify the build target to build from specification/targets.yaml. " + + "The value 'all' will build all of the targets therein." ) parser.add_argument( "--verbose", "-v", action="store_true", help="Turn on verbose mode." ) + parser.add_argument( + "--client_release", "-c", action="store", default="unstable", + help="The client-server release tag to generate, e.g. r1.2" + ) + parser.add_argument( + "--server_release", "-s", action="store", default="unstable", + help="The server-server release tag to generate, e.g. r1.2" + ) + parser.add_argument( + "--list_targets", action="store_true", + help="Do not update the specification. Instead print a list of targets.", + ) + parser.add_argument( + "--dest", "-d", default=os.path.join(script_dir, "gen"), + help="Set destination directory (default: scripts/gen)", + ) + args = parser.parse_args() - if not args.target: - parser.print_help() - sys.exit(1) VERBOSE = args.verbose - main(args.target, args.nodelete) + + if args.list_targets: + list_targets() + exit(0) + + substitutions = { + "%CLIENT_RELEASE_LABEL%": args.client_release, + # we hardcode a major version of r0. This ends up in the + # example API URLs. When we have released a new major version, + # we'll have to bump it. + "%CLIENT_MAJOR_VERSION%": "r0", + "%SERVER_RELEASE_LABEL%": args.server_release, + "%SERVER_MAJOR_VERSION%": extract_major(args.server_release), + } + + exit (main(args.target or ["all"], args.dest, args.nodelete, substitutions)) diff --git a/scripts/generate-matrix-org-assets b/scripts/generate-matrix-org-assets new file mode 100755 index 00000000000..d772caac66c --- /dev/null +++ b/scripts/generate-matrix-org-assets @@ -0,0 +1,18 @@ +#!/bin/sh +# +# generate a tarball of assets suitable for the matrix.org site + +cd `dirname $0`/.. + +mkdir -p assets + +# generate the spec docs +./scripts/gendoc.py -d assets/spec + +# also need the supporting-docs, which become the jekyll posts. +cp -rT supporting-docs assets/jekyll-posts + +# create a tarball of the assets. Exclude the spec index for now, as +# we want to leave it pointing at the release versions of the specs. +# (XXX: how to maintain this?) +tar -czf assets.tar.gz --exclude="assets/spec/index.html" assets diff --git a/scripts/matrix-org-gendoc.sh b/scripts/matrix-org-gendoc.sh deleted file mode 100755 index 5716786d2fe..00000000000 --- a/scripts/matrix-org-gendoc.sh +++ /dev/null @@ -1,63 +0,0 @@ -#! /bin/bash - -if [ -z "$1" ]; then - echo "Expected /includes/head.html file as 1st arg." - exit 1 -fi - -if [ -z "$2" ]; then - echo "Expected /includes/nav.html file as 2nd arg." - exit 1 -fi - -if [ -z "$3" ]; then - echo "Expected /includes/footer.html file as 3rd arg." - exit 1 -fi - - -HEADER=$1 -NAV_BAR=$2 -FOOTER=$3 - -if [ ! -f $HEADER ]; then - echo $HEADER " does not exist" - exit 1 -fi - -if [ ! -f $NAV_BAR ]; then - echo $NAV_BAR " does not exist" - exit 1 -fi - -if [ ! -f $FOOTER ]; then - echo $FOOTER " does not exist" - exit 1 -fi - -python gendoc.py - -perl -MFile::Slurp -pi -e 'BEGIN { $header = read_file("'$HEADER'") } s##$header - -#' gen/specification.html gen/howtos.html - -perl -MFile::Slurp -pi -e 'BEGIN { $nav = read_file("'$NAV_BAR'") } s## -
-
- $nav -
-
-
-#' gen/specification.html gen/howtos.html - -perl -MFile::Slurp -pi -e 'BEGIN { $footer = read_file("'$FOOTER'") } s## -
-
-
-
-
-
- $footer -
-
- #' gen/specification.html gen/howtos.html diff --git a/scripts/speculator/README b/scripts/speculator/README index 0a9f53fd2b8..82dc3d36e44 100644 --- a/scripts/speculator/README +++ b/scripts/speculator/README @@ -6,5 +6,13 @@ It serves the following HTTP endpoints: - /diff/rst/123 which gives a diff of the spec's rst at pull request 123. - /diff/html/123 which gives a diff of the spec's HTML at pull request 123. -To run it, you must install the `go` tool, and run: +The build or run, you need a working `go` installation. +Then fetch dependencies: + ` go get github.com/hashicorp/golang-lru` + +To run it, then run: `go run main.go` + +To build the binary (which is necessary for deployment to the matrix.org +servers), you must again install `go` and dependencies, and then run: + `go build` diff --git a/scripts/speculator/main.go b/scripts/speculator/main.go index d641d7da426..87ee4bf632f 100644 --- a/scripts/speculator/main.go +++ b/scripts/speculator/main.go @@ -21,10 +21,16 @@ import ( "os" "os/exec" "path" + "path/filepath" + "regexp" "strconv" "strings" + "sync" "syscall" + "text/template" "time" + + "github.com/hashicorp/golang-lru" ) type PullRequest struct { @@ -51,8 +57,12 @@ type User struct { } var ( - port = flag.Int("port", 9000, "Port on which to listen for HTTP") - allowedMembers map[string]bool + port = flag.Int("port", 9000, "Port on which to listen for HTTP") + includesDir = flag.String("includes_dir", "", "Directory containing include files for styling like matrix.org") + accessToken = flag.String("access_token", "", "github.com access token") + allowedMembers map[string]bool + specCache *lru.Cache // string -> map[string][]byte filename -> contents + styledSpecCache *lru.Cache // string -> map[string][]byte filename -> contents ) func (u *User) IsTrusted() bool { @@ -60,56 +70,56 @@ func (u *User) IsTrusted() bool { } const ( - pullsPrefix = "https://api.github.com/repos/matrix-org/matrix-doc/pulls" - matrixDocCloneURL = "https://github.com/matrix-org/matrix-doc.git" + pullsPrefix = "https://api.github.com/repos/matrix-org/matrix-doc/pulls" + matrixDocCloneURL = "https://github.com/matrix-org/matrix-doc.git" + permissionsOwnerFull = 0700 ) -func gitClone(url string, shared bool) (string, error) { - directory := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10)) - cmd := exec.Command("git", "clone", url, directory) - if shared { - cmd.Args = append(cmd.Args, "--shared") +var numericRegex = regexp.MustCompile(`^\d+$`) + +func accessTokenQuerystring() string { + if *accessToken == "" { + return "" } + return fmt.Sprintf("?access_token=%s", *accessToken) +} - err := cmd.Run() - if err != nil { - return "", fmt.Errorf("error cloning repo: %v", err) +func gitClone(url string, directory string, shared bool) error { + args := []string{"clone", url, directory} + if shared { + args = append(args, "--shared") } - return directory, nil + if err := runGitCommand(directory, args); err != nil { + return err + } + return nil } func gitCheckout(path, sha string) error { return runGitCommand(path, []string{"checkout", sha}) } -func gitFetch(path string) error { - return runGitCommand(path, []string{"fetch"}) -} - func runGitCommand(path string, args []string) error { cmd := exec.Command("git", args...) cmd.Dir = path - err := cmd.Run() - if err != nil { - return fmt.Errorf("error running %q: %v", strings.Join(cmd.Args, " "), err) + var b bytes.Buffer + cmd.Stderr = &b + if err := cmd.Run(); err != nil { + return fmt.Errorf("error running %q: %v (stderr: %s)", strings.Join(cmd.Args, " "), err, b.String()) } return nil } -func lookupPullRequest(url url.URL, pathPrefix string) (*PullRequest, error) { - if !strings.HasPrefix(url.Path, pathPrefix+"/") { - return nil, fmt.Errorf("invalid path passed: %s expect %s/123", url.Path, pathPrefix) - } - prNumber := url.Path[len(pathPrefix)+1:] - if strings.Contains(prNumber, "/") { - return nil, fmt.Errorf("invalid path passed: %s expect %s/123", url.Path, pathPrefix) - } - - resp, err := http.Get(fmt.Sprintf("%s/%s", pullsPrefix, prNumber)) +func lookupPullRequest(prNumber string) (*PullRequest, error) { + resp, err := http.Get(fmt.Sprintf("%s/%s%s", pullsPrefix, prNumber, accessTokenQuerystring())) defer resp.Body.Close() if err != nil { return nil, fmt.Errorf("error getting pulls: %v", err) } + if resp.StatusCode != 200 { + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("error getting pull request %s: %v", prNumber, string(body)) + } dec := json.NewDecoder(resp.Body) var pr PullRequest if err := dec.Decode(&pr); err != nil { @@ -118,35 +128,89 @@ func lookupPullRequest(url url.URL, pathPrefix string) (*PullRequest, error) { return &pr, nil } +func (s *server) lookupBranch(branch string) (string, error) { + err := s.updateBase() + if err != nil { + log.Printf("Error fetching: %v, will use cached branches") + } + + if strings.ToLower(branch) == "head" { + branch = "master" + } + branch = "origin/" + branch + sha, err := s.getSHAOf(branch) + if err != nil { + return "", fmt.Errorf("error getting branch %s: %v", branch, err) + } + if sha == "" { + return "", fmt.Errorf("Unable to get sha for %s", branch) + } + return sha, nil +} + func generate(dir string) error { cmd := exec.Command("python", "gendoc.py", "--nodelete") cmd.Dir = path.Join(dir, "scripts") var b bytes.Buffer cmd.Stderr = &b - err := cmd.Run() - if err != nil { + if err := cmd.Run(); err != nil { return fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()) } + + // cheekily dump the swagger docs into the gen directory so they can be + // served by serveSpec + cmd = exec.Command("python", "dump-swagger.py", "gen/api-docs.json") + cmd.Dir = path.Join(dir, "scripts") + cmd.Stderr = &b + if err := cmd.Run(); err != nil { + return fmt.Errorf("error generating api docs: %v\nOutput from dump-swagger:\n%v", err, b.String()) + } + return nil } func writeError(w http.ResponseWriter, code int, err error) { + w.Header().Set("Content-Type", "text/plain") w.WriteHeader(code) io.WriteString(w, fmt.Sprintf("%v\n", err)) } type server struct { + mu sync.Mutex // Must be locked around any git command on matrixDocCloneURL matrixDocCloneURL string } +func (s *server) updateBase() error { + s.mu.Lock() + defer s.mu.Unlock() + return runGitCommand(s.matrixDocCloneURL, []string{"fetch"}) +} + +// canCheckout returns whether a given sha can currently be checked out from s.matrixDocCloneURL. +func (s *server) canCheckout(sha string) bool { + s.mu.Lock() + defer s.mu.Unlock() + return runGitCommand(s.matrixDocCloneURL, []string{"cat-file", "-e", sha + "^{commit}"}) == nil +} + // generateAt generates spec from repo at sha. // Returns the path where the generation was done. func (s *server) generateAt(sha string) (dst string, err error) { - err = gitFetch(s.matrixDocCloneURL) + if !s.canCheckout(sha) { + err = s.updateBase() + if err != nil { + return + } + } + + dst, err = makeTempDir() if err != nil { return } - dst, err = gitClone(s.matrixDocCloneURL, true) + log.Printf("Generating %s in %s\n", sha, dst) + s.mu.Lock() + err = gitClone(s.matrixDocCloneURL, dst, true) + s.mu.Unlock() if err != nil { return } @@ -164,25 +228,100 @@ func (s *server) getSHAOf(ref string) (string, error) { cmd.Dir = path.Join(s.matrixDocCloneURL) var b bytes.Buffer cmd.Stdout = &b + s.mu.Lock() err := cmd.Run() + s.mu.Unlock() if err != nil { - return "", fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()) + return "", fmt.Errorf("error generating spec: %v\nOutput from git:\n%v", err, b.String()) } return strings.TrimSpace(b.String()), nil } +// extractPRNumber checks that the path begins with the given base, and returns +// the following component. +func extractPRNumber(path, base string) (string, error) { + if !strings.HasPrefix(path, base+"/") { + return "", fmt.Errorf("invalid path passed: %q expect %s/123", path, base) + } + return strings.Split(path[len(base)+1:], "/")[0], nil +} + +// extractPath extracts the file path within the gen directory which should be served for the request. +// Returns one of (file to serve, path to redirect to). +// path is the actual path being requested, e.g. "/spec/head/client_server.html". +// base is the base path of the handler, including a trailing slash, before the PR number, e.g. "/spec/". +func extractPath(path, base string) (string, string) { + // Assumes exactly one flat directory + + // Count slashes in /spec/head/client_server.html + // base is /spec/ + // +1 for the PR number - /spec/head + // +1 for the path-part after the slash after the PR number + max := strings.Count(base, "/") + 2 + parts := strings.SplitN(path, "/", max) + + if len(parts) < max { + // Path is base/pr - redirect to base/pr/index.html + return "", path + "/index.html" + } + if parts[max-1] == "" { + // Path is base/pr/ - serve index.html + return "index.html", "" + } + + // Path is base/pr/file.html - serve file + return parts[max-1], "" +} + func (s *server) serveSpec(w http.ResponseWriter, req *http.Request) { var sha string - if strings.ToLower(req.URL.Path) == "/spec/head" { - originHead, err := s.getSHAOf("origin/master") - if err != nil { - writeError(w, 500, err) - return + var styleLikeMatrixDotOrg = req.URL.Query().Get("matrixdotorgstyle") != "" + + if styleLikeMatrixDotOrg && *includesDir == "" { + writeError(w, 500, fmt.Errorf("Cannot style like matrix.org - no include dir specified")) + return + } + + // we use URL.EscapedPath() to get hold of the %-encoded version of the + // path, so that we can handle branch names with slashes in. + urlPath := req.URL.EscapedPath() + + if urlPath == "/spec" { + // special treatment for /spec - redirect to /spec/HEAD/ + s.redirectTo(w, req, "/spec/HEAD/") + return + } + + if !strings.HasPrefix(urlPath, "/spec/") { + writeError(w, 500, fmt.Errorf("invalid path passed: %q expect /spec/...", urlPath)) + } + + splits := strings.SplitN(urlPath[6:], "/", 2) + + if len(splits) == 1 { + // "/spec/foo" - redirect to "/spec/foo/" (so that relative links from the index work) + if splits[0] == "" { + s.redirectTo(w, req, "/spec/HEAD/") + } else { + s.redirectTo(w, req, urlPath+"/") } - sha = originHead - } else { - pr, err := lookupPullRequest(*req.URL, "/spec") + return + } + + // now we have: + // splits[0] is a PR#, or a branch name + // splits[1] is the file to serve + + branchName, _ := url.QueryUnescape(splits[0]) + requestedPath, _ := url.QueryUnescape(splits[1]) + if requestedPath == "" { + requestedPath = "index.html" + } + + if numericRegex.MatchString(branchName) { + // PR number + pr, err := lookupPullRequest(branchName) if err != nil { writeError(w, 400, err) return @@ -195,21 +334,108 @@ func (s *server) serveSpec(w http.ResponseWriter, req *http.Request) { return } sha = pr.Head.SHA + log.Printf("Serving pr %s (%s)\n", branchName, sha) + } else if strings.ToLower(branchName) == "head" || + branchName == "master" || + strings.HasPrefix(branchName, "drafts/") { + branchSHA, err := s.lookupBranch(branchName) + if err != nil { + writeError(w, 400, err) + return + } + sha = branchSHA + log.Printf("Serving branch %s (%s)\n", branchName, sha) + } else { + writeError(w, 404, fmt.Errorf("invalid branch name")) + return } - dst, err := s.generateAt(sha) - defer os.RemoveAll(dst) - if err != nil { - writeError(w, 500, err) - return + var cache = specCache + if styleLikeMatrixDotOrg { + cache = styledSpecCache } - b, err := ioutil.ReadFile(path.Join(dst, "scripts/gen/specification.html")) - if err != nil { - writeError(w, 500, fmt.Errorf("Error reading spec: %v", err)) + var pathToContent map[string][]byte + + if cached, ok := cache.Get(sha); ok { + pathToContent = cached.(map[string][]byte) + } else { + dst, err := s.generateAt(sha) + defer os.RemoveAll(dst) + if err != nil { + writeError(w, 500, err) + return + } + + pathToContent = make(map[string][]byte) + scriptsdir := path.Join(dst, "scripts") + base := path.Join(scriptsdir, "gen") + walker := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + rel, err := filepath.Rel(base, path) + if err != nil { + return fmt.Errorf("Failed to get relative path of %s: %v", path, err) + } + + if styleLikeMatrixDotOrg { + cmd := exec.Command("./add-matrix-org-stylings.pl", *includesDir, path) + cmd.Dir = scriptsdir + var b bytes.Buffer + cmd.Stderr = &b + if err := cmd.Run(); err != nil { + return fmt.Errorf("error styling spec: %v\nOutput:\n%v", err, b.String()) + } + } + + bytes, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("Error reading spec: %v", err) + } + pathToContent[rel] = bytes + return nil + } + + err = filepath.Walk(base, walker) + if err != nil { + writeError(w, 500, err) + return + } + cache.Add(sha, pathToContent) + } + + if requestedPath == "api-docs.json" { + // allow other swagger UIs access to our swagger + w.Header().Set("Access-Control-Allow-Origin", "*") + } + + if b, ok := pathToContent[requestedPath]; ok { + w.Write(b) return } - w.Write(b) + if requestedPath == "index.html" { + // Fall back to single-page spec for old PRs + if b, ok := pathToContent["specification.html"]; ok { + w.Write(b) + return + } + } + w.WriteHeader(404) + w.Write([]byte("Not found")) +} + +func (s *server) redirectTo(w http.ResponseWriter, req *http.Request, path string) { + u := *req.URL + u.Scheme = "http" + u.Host = req.Host + u.Path = path + w.Header().Set("Location", u.String()) + w.WriteHeader(302) } func checkAuth(pr *PullRequest) error { @@ -220,7 +446,12 @@ func checkAuth(pr *PullRequest) error { } func (s *server) serveRSTDiff(w http.ResponseWriter, req *http.Request) { - pr, err := lookupPullRequest(*req.URL, "/diff/rst") + prNumber, err := extractPRNumber(req.URL.Path, "/diff/rst") + if err != nil { + writeError(w, 400, err) + return + } + pr, err := lookupPullRequest(prNumber) if err != nil { writeError(w, 400, err) return @@ -247,7 +478,7 @@ func (s *server) serveRSTDiff(w http.ResponseWriter, req *http.Request) { return } - diffCmd := exec.Command("diff", "-u", path.Join(base, "scripts", "tmp", "full_spec.rst"), path.Join(head, "scripts", "tmp", "full_spec.rst")) + diffCmd := exec.Command("diff", "-r", "-u", path.Join(base, "scripts", "tmp"), path.Join(head, "scripts", "tmp")) var diff bytes.Buffer diffCmd.Stdout = &diff if err := ignoreExitCodeOne(diffCmd.Run()); err != nil { @@ -258,7 +489,12 @@ func (s *server) serveRSTDiff(w http.ResponseWriter, req *http.Request) { } func (s *server) serveHTMLDiff(w http.ResponseWriter, req *http.Request) { - pr, err := lookupPullRequest(*req.URL, "/diff/html") + prNumber, err := extractPRNumber(req.URL.Path, "/diff/html") + if err != nil { + writeError(w, 400, err) + return + } + pr, err := lookupPullRequest(prNumber) if err != nil { writeError(w, 400, err) return @@ -291,14 +527,21 @@ func (s *server) serveHTMLDiff(w http.ResponseWriter, req *http.Request) { return } - cmd := exec.Command(htmlDiffer, path.Join(base, "scripts", "gen", "specification.html"), path.Join(head, "scripts", "gen", "specification.html")) - var b bytes.Buffer - cmd.Stdout = &b + requestedPath, redirect := extractPath(req.URL.Path, "/diff/spec/") + if redirect != "" { + s.redirectTo(w, req, redirect) + return + } + cmd := exec.Command(htmlDiffer, path.Join(base, "scripts", "gen", requestedPath), path.Join(head, "scripts", "gen", requestedPath)) + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr if err := cmd.Run(); err != nil { - writeError(w, 500, fmt.Errorf("error running HTML differ: %v", err)) + writeError(w, 500, fmt.Errorf("error running HTML differ: %v\nOutput:\n%v", err, stderr.String())) return } - w.Write(b.Bytes()) + w.Write(stdout.Bytes()) } func findHTMLDiffer() (string, error) { @@ -313,30 +556,122 @@ func findHTMLDiffer() (string, error) { return "", fmt.Errorf("unable to find htmldiff.pl") } -func listPulls(w http.ResponseWriter, req *http.Request) { - resp, err := http.Get(pullsPrefix) +func getPulls() ([]PullRequest, error) { + resp, err := http.Get(fmt.Sprintf("%s%s", pullsPrefix, accessTokenQuerystring())) if err != nil { - writeError(w, 500, err) - return + return nil, err } defer resp.Body.Close() + if resp.StatusCode != 200 { + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("error getting pull requests: %v", string(body)) + } dec := json.NewDecoder(resp.Body) var pulls []PullRequest - if err := dec.Decode(&pulls); err != nil { + err = dec.Decode(&pulls) + return pulls, err +} + +// getBranches returns a list of the upstream branch names. +// It attempts to `git fetch` before doing so. +func (s *server) getBranches() ([]string, error) { + err := s.updateBase() + if err != nil { + log.Printf("Error fetching: %v, will use cached branches") + } + + cmd := exec.Command("git", "branch", "-r") + cmd.Dir = path.Join(s.matrixDocCloneURL) + var b bytes.Buffer + cmd.Stdout = &b + s.mu.Lock() + err = cmd.Run() + s.mu.Unlock() + if err != nil { + return nil, fmt.Errorf("Error reading branch names: %v. Output from git:\n%v", err, b.String()) + } + branches := []string{} + for _, b := range strings.Split(b.String(), "\n") { + b = strings.TrimSpace(b) + if strings.HasPrefix(b, "origin/") { + branches = append(branches, b[7:]) + } + } + return branches, nil +} + +func (srv *server) makeIndex(w http.ResponseWriter, req *http.Request) { + pulls, err := getPulls() + if err != nil { writeError(w, 500, err) return } - if len(pulls) == 0 { - io.WriteString(w, "No pull requests found") + + branches, err := srv.getBranches() + if err != nil { + writeError(w, 500, err) return } - s := "
    " + + // write our stuff into a buffer so that we can change our minds + // and write a 500 if it all goes wrong. + var b bytes.Buffer + b.Write([]byte(` + + + +
      +`)) + + tmpl, err := template.New("pr entry").Parse(` +
    • {{.Number}}: + {{.User.Login}}: + {{.Title}}: + spec + api docs + spec diff + rst diff +
    • +`) + if err != nil { + log.Fatal(err) + } + for _, pull := range pulls { - s += fmt.Sprintf(`
    • %d: %s: %s: spec spec diff rst diff
    • `, - pull.Number, pull.User.HTMLURL, pull.User.Login, pull.HTMLURL, pull.Title, pull.Number, pull.Number, pull.Number) + err = tmpl.Execute(&b, pull) + if err != nil { + writeError(w, 500, err) + return + } + } + b.Write([]byte(` +
    +
    View the spec at:
      +`)) + branchNames := []string{} + for _, branch := range branches { + if strings.HasPrefix(branch, "drafts/") { + branchNames = append(branchNames, branch) + } + } + branchNames = append(branchNames, "HEAD") + for _, branch := range branchNames { + href := "spec/" + url.QueryEscape(branch) + "/" + fmt.Fprintf(&b, `
    • %s
    • `, href, branch) + if *includesDir != "" { + fmt.Fprintf(&b, `
    • %s, styled like matrix.org
    • `, + href, branch) + } } - s += `
    ` - io.WriteString(w, s) + b.Write([]byte("
")) + b.WriteTo(w) } func ignoreExitCodeOne(err error) error { @@ -364,25 +699,59 @@ func main() { "illicitonion": true, "Kegsay": true, "NegativeMjark": true, + "richvdh": true, + "ara4n": true, + "leonerd": true, + } + if err := initCache(); err != nil { + log.Fatal(err) } rand.Seed(time.Now().Unix()) - masterCloneDir, err := gitClone(matrixDocCloneURL, false) + masterCloneDir, err := makeTempDir() if err != nil { log.Fatal(err) } - s := server{masterCloneDir} - http.HandleFunc("/spec/", s.serveSpec) + log.Printf("Creating master clone dir %s\n", masterCloneDir) + if err = gitClone(matrixDocCloneURL, masterCloneDir, false); err != nil { + log.Fatal(err) + } + s := server{matrixDocCloneURL: masterCloneDir} + http.HandleFunc("/spec/", forceHTML(s.serveSpec)) http.HandleFunc("/diff/rst/", s.serveRSTDiff) - http.HandleFunc("/diff/html/", s.serveHTMLDiff) + http.HandleFunc("/diff/html/", forceHTML(s.serveHTMLDiff)) http.HandleFunc("/healthz", serveText("ok")) - http.HandleFunc("/", listPulls) + http.HandleFunc("/", forceHTML(s.makeIndex)) fmt.Printf("Listening on port %d\n", *port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) } +func forceHTML(h func(w http.ResponseWriter, req *http.Request)) func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "text/html") + h(w, req) + } +} + func serveText(s string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { io.WriteString(w, s) } } + +func initCache() error { + c1, err := lru.New(50) // Evict after 50 entries (i.e. 50 sha1s) + specCache = c1 + + c2, err := lru.New(50) // Evict after 50 entries (i.e. 50 sha1s) + styledSpecCache = c2 + return err +} + +func makeTempDir() (string, error) { + directory := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10)) + if err := os.MkdirAll(directory, permissionsOwnerFull); err != nil { + return "", fmt.Errorf("error making directory %s: %v", directory, err) + } + return directory, nil +} diff --git a/scripts/swagger-http-server.py b/scripts/swagger-http-server.py new file mode 100755 index 00000000000..5ec001019f7 --- /dev/null +++ b/scripts/swagger-http-server.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +# Runs an HTTP server on localhost:8000 which will serve the generated swagger +# JSON so that it can be viewed in an online swagger UI. + +# Copyright 2016 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import SimpleHTTPServer +import SocketServer + +# Thanks to http://stackoverflow.com/a/13354482 +class MyHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def end_headers(self): + self.send_my_headers() + SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) + + def send_my_headers(self): + self.send_header("Access-Control-Allow-Origin", "*") + + +if __name__ == '__main__': + scripts_dir = os.path.dirname(os.path.abspath(__file__)) + parser = argparse.ArgumentParser() + parser.add_argument( + '--port', '-p', + type=int, default=8000, + help='TCP port to listen on (default: %(default)s)', + ) + parser.add_argument( + 'swagger_dir', nargs='?', + default=os.path.join(scripts_dir, 'swagger'), + help='directory to serve (default: %(default)s)', + ) + args = parser.parse_args() + + os.chdir(args.swagger_dir) + + httpd = SocketServer.TCPServer(("localhost", args.port), + MyHTTPRequestHandler) + print "Serving at http://localhost:%i/api-docs.json" % args.port + httpd.serve_forever() diff --git a/specification/0-event_signing.rst b/specification/0-event_signing.rst deleted file mode 100644 index bde58f0cd7f..00000000000 --- a/specification/0-event_signing.rst +++ /dev/null @@ -1,237 +0,0 @@ -Signing Events --------------- - -Canonical JSON -~~~~~~~~~~~~~~ - -Matrix events are represented using JSON objects. If we want to sign JSON -events we need to encode the JSON as a binary string. Unfortunately the same -JSON can be encoded in different ways by changing how much white space is used -or by changing the order of keys within objects. Therefore we have to define an -encoding which can be reproduced byte for byte by any JSON library. - -We define the canonical JSON encoding for a value to be the shortest UTF-8 JSON -encoding with dictionary keys lexicographically sorted by unicode codepoint. -Numbers in the JSON must be integers in the range [-(2**53)+1, (2**53)-1]. - -We pick UTF-8 as the encoding as it should be available to all platforms and -JSON received from the network is likely to be already encoded using UTF-8. -We sort the keys to give a consistent ordering. We force integers to be in the -range where they can be accurately represented using IEEE double precision -floating point numbers since a number of JSON libraries represent all numbers -using this representation. - -.. code:: python - - import json - - def canonical_json(value): - return json.dumps( - value, - # Encode code-points outside of ASCII as UTF-8 rather than \u escapes - ensure_ascii=False, - # Remove unnecessary white space. - separators=(',',':'), - # Sort the keys of dictionaries. - sort_keys=True, - # Encode the resulting unicode as UTF-8 bytes. - ).encode("UTF-8") - -Grammar -+++++++ - -Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing -insignificant whitespace, fractions, exponents and redundant character escapes - -.. code:: - - value = false / null / true / object / array / number / string - false = %x66.61.6c.73.65 - null = %x6e.75.6c.6c - true = %x74.72.75.65 - object = %x7B [ member *( %x2C member ) ] %7D - member = string %x3A value - array = %x5B [ value *( %x2C value ) ] %5B - number = [ %x2D ] int - int = %x30 / ( %x31-39 *digit ) - digit = %x30-39 - string = %x22 *char %x22 - char = unescaped / %x5C escaped - unescaped = %x20-21 / %x23-5B / %x5D-10FFFF - escaped = %x22 ; " quotation mark U+0022 - / %x5C ; \ reverse solidus U+005C - / %x62 ; b backspace U+0008 - / %x66 ; f form feed U+000C - / %x6E ; n line feed U+000A - / %x72 ; r carriage return U+000D - / %x74 ; t tab U+0009 - / %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X - / %x75.30.30.31 (%x30-39 / %x61-66) ; u001X - -Signing JSON -~~~~~~~~~~~~ - -We can now sign a JSON object by encoding it as a sequence of bytes, computing -the signature for that sequence and then adding the signature to the original -JSON object. - -Signing Details -+++++++++++++++ - -JSON is signed by encoding the JSON object without ``signatures`` or keys grouped -as ``unsigned``, using the canonical encoding described above. The JSON bytes are then signed using the -signature algorithm and the signature encoded using base64 with the padding -stripped. The resulting base64 signature is added to an object under the -*signing key identifier* which is added to the ``signatures`` object under the -name of the server signing it which is added back to the original JSON object -along with the ``unsigned`` object. - -The *signing key identifier* is the concatenation of the *signing algorithm* -and a *key version*. The *signing algorithm* identifies the algorithm used to -sign the JSON. The currently support value for *signing algorithm* is -``ed25519`` as implemented by NACL (http://nacl.cr.yp.to/). The *key version* -is used to distinguish between different signing keys used by the same entity. - -The ``unsigned`` object and the ``signatures`` object are not covered by the -signature. Therefore intermediate servers can add unsigned data such as timestamps -and additional signatures. - - -.. code:: json - - { - "name": "example.org", - "signing_keys": { - "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" - }, - "unsigned": { - "age_ts": 922834800000 - }, - "signatures": { - "example.org": { - "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw" - } - } - } - -.. code:: python - - def sign_json(json_object, signing_key, signing_name): - signatures = json_object.pop("signatures", {}) - unsigned = json_object.pop("unsigned", None) - - signed = signing_key.sign(encode_canonical_json(json_object)) - signature_base64 = encode_base64(signed.signature) - - key_id = "%s:%s" % (signing_key.alg, signing_key.version) - signatures.setdefault(signing_name, {})[key_id] = signature_base64 - - json_object["signatures"] = signatures - if unsigned is not None: - json_object["unsigned"] = unsigned - - return json_object - -Checking for a Signature -++++++++++++++++++++++++ - -To check if an entity has signed a JSON object a server does the following - -1. Checks if the ``signatures`` object contains an entry with the name of the - entity. If the entry is missing then the check fails. -2. Removes any *signing key identifiers* from the entry with algorithms it - doesn't understand. If there are no *signing key identifiers* left then the - check fails. -3. Looks up *verification keys* for the remaining *signing key identifiers* - either from a local cache or by consulting a trusted key server. If it - cannot find a *verification key* then the check fails. -4. Decodes the base64 encoded signature bytes. If base64 decoding fails then - the check fails. -5. Checks the signature bytes using the *verification key*. If this fails then - the check fails. Otherwise the check succeeds. - -Signing Events -~~~~~~~~~~~~~~ - -Signing events is a more complicated process since servers can choose to redact -non-essential parts of an event. Before signing the event it is encoded as -Canonical JSON and hashed using SHA-256. The resulting hash is then stored -in the event JSON in a ``hash`` object under a ``sha256`` key. - -.. code:: python - - def hash_event(event_json_object): - - # Keys under "unsigned" can be modified by other servers. - # They are useful for conveying information like the age of an - # event that will change in transit. - # Since they can be modifed we need to exclude them from the hash. - unsigned = event_json_object.pop("unsigned", None) - - # Signatures will depend on the current value of the "hashes" key. - # We cannot add new hashes without invalidating existing signatures. - signatures = event_json_object.pop("signatures", None) - - # The "hashes" key might contain multiple algorithms if we decide to - # migrate away from SHA-2. We don't want to include an existing hash - # output in our hash so we exclude the "hashes" dict from the hash. - hashes = event_json_object.pop("hashes", {}) - - # Encode the JSON using a canonical encoding so that we get the same - # bytes on every server for the same JSON object. - event_json_bytes = encode_canonical_json(event_json_bytes) - - # Add the base64 encoded bytes of the hash to the "hashes" dict. - hashes["sha256"] = encode_base64(sha256(event_json_bytes).digest()) - - # Add the "hashes" dict back the event JSON under a "hashes" key. - event_json_object["hashes"] = hashes - if unsigned is not None: - event_json_object["unsigned"] = unsigned - return event_json_object - -Then all non-essential keys are stripped from the event object, and the -resulting object which included the ``hash`` key is signed using the JSON -signing algorithm - -.. code:: python - - def sign_event(event_json_object, name, key): - - # Make sure the event has a "hashes" key. - if "hashes" not in event_json_object: - event_json_object = hash_event(event_json_object) - - # Strip all the keys that would be removed if the event was redacted. - # The hashes are not stripped and cover all the keys in the event. - # This means that we can tell if any of the non-essential keys are - # modified or removed. - stripped_json_object = strip_non_essential_keys(event_json_object) - - # Sign the stripped JSON object. The signature only covers the - # essential keys and the hashes. This means that we can check the - # signature even if the event is redacted. - signed_json_object = sign_json(stripped_json_object) - - # Copy the signatures from the stripped event to the original event. - event_json_object["signatures"] = signed_json_oject["signatures"] - return event_json_object - -Servers can then transmit the entire event or the event with the non-essential -keys removed. If the entire event is present, receiving servers can then check -the event by computing the SHA-256 of the event, excluding the ``hash`` object. -If the keys have been redacted, then the ``hash`` object is included when -calculating the SHA-256 instead. - -New hash functions can be introduced by adding additional keys to the ``hash`` -object. Since the ``hash`` object cannot be redacted a server shouldn't allow -too many hashes to be listed, otherwise a server might embed illict data within -the ``hash`` object. For similar reasons a server shouldn't allow hash values -that are too long. - -.. TODO - [[TODO(markjh): We might want to specify a maximum number of keys for the - ``hash`` and we might want to specify the maximum output size of a hash]] - [[TODO(markjh) We might want to allow the server to omit the output of well - known hash functions like SHA-256 when none of the keys have been redacted]] - diff --git a/specification/0-events.rst b/specification/0-events.rst deleted file mode 100644 index 16948462c82..00000000000 --- a/specification/0-events.rst +++ /dev/null @@ -1,38 +0,0 @@ -Events -====== - -All communication in Matrix is expressed in the form of data objects called -Events. These are the fundamental building blocks common to the client-server, -server-server and application-service APIs, and are described below. - -{{common_event_fields}} - -{{common_room_event_fields}} - -{{common_state_event_fields}} - - -Room Events ------------ -.. NOTE:: - This section is a work in progress. - -This specification outlines several standard event types, all of which are -prefixed with ``m.`` - -{{m_room_aliases_event}} - -{{m_room_canonical_alias_event}} - -{{m_room_create_event}} - -{{m_room_history_visibility_event}} - -{{m_room_join_rules_event}} - -{{m_room_member_event}} - -{{m_room_power_levels_event}} - -{{m_room_redaction_event}} - diff --git a/specification/1-client_server_api.rst b/specification/1-client_server_api.rst deleted file mode 100644 index 59e6b68eec4..00000000000 --- a/specification/1-client_server_api.rst +++ /dev/null @@ -1,1233 +0,0 @@ -Client-Server API -================= - -Overview --------- - -The client-server API provides a simple lightweight API to let clients send -messages, control rooms and synchronise conversation history. It is designed to -support both lightweight clients which store no state and lazy-load data from -the server as required - as well as heavyweight clients which maintain a full -local persistent copy of server state. - -This mostly describes v1 of the Client-Server API as featured in the original September -2014 launch of Matrix, apart from user-interactive authentication where it is -encouraged to move to v2, therefore this is the version documented here. -Version 2 is currently in development (as of Jan-March 2015) as an incremental -but backwards-incompatible refinement of Version 1 and will be released -shortly. - -Documentation for the old `V1 authentication -<../attic/v1_registration_login.rst>`_ is still available separately. - -Client Authentication ---------------------- -Most API endpoints require the user to identify themselves by presenting -previously obtained credentials in the form of an ``access_token`` query -parameter. - -In API version 2, when credentials are missing or invalid, the HTTP call will -return with a status of 401 and the error code, ``M_MISSING_TOKEN`` or -``M_UNKNOWN_TOKEN`` respectively. - -User-Interactive Authentication API ------------------------------------ -This section refers to API Version 2. - -Some API endpoints such as ``login`` or ``register`` require authentication that -interacts with the user. The home server may provide many different ways of -authenticating, such as user/password auth, login via a social network (OAuth2), -login by confirming a token sent to their email address, etc. This specification -does not define how home servers should authorise their users but instead -defines the standard interface which implementations should follow so that ANY -client can login to ANY home server. - -The process takes the form of one or more stages, where at each stage the client -submits a set of data for a given stage type and awaits a response from the -server, which will either be a final success or a request to perform an -additional stage. This exchange continues until the final success. - -Authentication works by client and server exchanging dictionaries. This -specification covers how this is done over JSON HTTP POST. - -For each endpoint, a server offers one of more 'flows' that the client can use -to authenticate itself. Each flow comprises one or more 'stages'. Flows may have -more than one stage to implement n-factor auth. When all stages are complete, -authentication is complete and the API call succeeds. To establish what flows a -server supports for an endpoint, a client sends the request with no -authentication. A request to an endpoint that uses User-Interactive -Authentication never succeeds without auth. Home Servers may allow requests that -don't require auth by offering a stage with only the ``m.login.dummy`` auth -type. The home server returns a response with HTTP status 401 and a JSON object -as follows:: - - { - "flows": [ - { - "stages": [ "example.type.foo", "example.type.bar" ] - }, - { - "stages": [ "example.type.foo", "example.type.baz" ] - } - ], - "params": { - "example.type.baz": { - "example_key": "foobar" - } - }, - "session": "xxxxxx" - } - -In addition to the ``flows``, this object contains some extra -information: - -params - This section contains any information that the client will need to know in - order to use a given type of authentication. For each login stage type - presented, that type may be present as a key in this dictionary. For example, - the public part of an OAuth client ID could be given here. -session - This is a session identifier that the client must pass back to the home - server, if one is provided, in subsequent attempts to authenticate in the same - API call. - -The client then chooses a flow and attempts to complete one of the stages. It -does this by resubmitting the same request with the the addition of an 'auth' -key in the object that it submits. This dictionary contains a ``type`` key whose -value is the name of the stage type that the client is attempting to complete. -It must also contains a ``session`` key with the value of the session key given -by the home server, if one was given. It also contains other keys dependent on -the stage type being attempted. For example, if the client is attempting to -complete login type ``example.type.foo``, it might submit something like this:: - - { - "a_request_parameter": "something", - "another_request_parameter": "something else", - "auth": { - "type": "example.type.foo", - "session", "xxxxxx", - "example_credential": "verypoorsharedsecret" - } - } - -If the home server deems the authentication attempt to be successful but still -requires more stages to be completed, it returns HTTP status 401 along with the -same object as when no authentication was attempted, with the addition of the -``completed`` key which is an array of stage type the client has completed -successfully:: - - { - "completed": [ "example.type.foo" ], - "flows": [ - { - "stages": [ "example.type.foo", "example.type.bar" ] - }, - { - "stages": [ "example.type.foo", "example.type.baz" ] - } - ], - "params": { - "example.type.baz": { - "example_key": "foobar" - } - }, - "session": "xxxxxx" - } - -If the home server decides the attempt was unsuccessful, it returns an error -message in the standard format:: - - { - "errcode": "M_EXAMPLE_ERROR", - "error": "Something was wrong" - } - -Individual stages may require more than one request to complete, in which case -the response will be as if the request was unauthenticated with the addition of -any other keys as defined by the login type. - -If the client has completed all stages of a flow, the home server performs the -API call and returns the result as normal. - -Some authentication types may be completed by means other than through the -Matrix client, for example, an email confirmation may be completed when the user -clicks on the link in the email. In this case, the client retries the request -with an auth dict containing only the session key. The response to this will be -the same as if the client were attempting to complete an auth state normally, -i.e. the request will either complete or request auth, with the presence or -absence of that login stage type in the 'completed' array indicating whether -that stage is complete. - -Example -~~~~~~~ -At a high level, the requests made for an API call completing an auth flow with -three stages will resemble the following diagram:: - - _______________________ - | Stage 1 | - | type: "" | - | ___________________ | - | |_Request_1_________| | <-- Returns "session" key which is used throughout. - | ___________________ | - | |_Request_2_________| | - |_______________________| - | - | - _________V_____________ - | Stage 2 | - | type: "" | - | ___________________ | - | |_Request_1_________| | - | ___________________ | - | |_Request_2_________| | - | ___________________ | - | |_Request_3_________| | - |_______________________| - | - | - _________V_____________ - | Stage 3 | - | type: "" | - | ___________________ | - | |_Request_1_________| | <-- Returns API response - |_______________________| - -This specification defines the following login types: - - ``m.login.password`` - - ``m.login.recaptcha`` - - ``m.login.oauth2`` - - ``m.login.email.identity`` - - ``m.login.token`` - - ``m.login.dummy`` - -Password-based -~~~~~~~~~~~~~~ -:Type: - ``m.login.password`` -:Description: - The client submits a username and secret password, both sent in plain-text. - -To respond to this type, reply with an auth dict as follows:: - - { - "type": "m.login.password", - "user": "", - "password": "" - } - -Google ReCaptcha -~~~~~~~~~~~~~~~~ -:Type: - ``m.login.recaptcha`` -:Description: - The user completes a Google ReCaptcha 2.0 challenge - -To respond to this type, reply with an auth dict as follows:: - - { - "type": "m.login.recaptcha", - "response": "" - } - -Token-based -~~~~~~~~~~~ -:Type: - ``m.login.token`` -:Description: - The client submits a username and token. - -To respond to this type, reply with an auth dict as follows:: - - { - "type": "m.login.token", - "user": "", - "token": "", - "txn_id": "" - } - -The ``nonce`` should be a random string generated by the client for the -request. The same ``nonce`` should be used if retrying the request. - -There are many ways a client may receive a ``token``, including via an email or -from an existing logged in device. - -The ``txn_id`` may be used by the server to disallow other devices from using -the token, thus providing "single use" tokens while still allowing the device -to retry the request. This would be done by tying the token to the ``txn_id`` -server side, as well as potentially invalidating the token completely once the -device has successfully logged in (e.g. when we receive a request from the -newly provisioned access_token). - -The ``token`` must be a macaroon. - -OAuth2-based -~~~~~~~~~~~~ -:Type: - ``m.login.oauth2`` -:Description: - Authentication is supported via OAuth2 URLs. This login consists of multiple - requests. -:Parameters: - ``uri``: Authorization Request URI OR service selection URI. Both contain an - encoded ``redirect URI``. - -The home server acts as a 'confidential' client for the purposes of OAuth2. If -the uri is a ``service selection URI``, it MUST point to a webpage which prompts -the user to choose which service to authorize with. On selection of a service, -this MUST link through to an ``Authorization Request URI``. If there is only one -service which the home server accepts when logging in, this indirection can be -skipped and the "uri" key can be the ``Authorization Request URI``. - -The client then visits the ``Authorization Request URI``, which then shows the -OAuth2 Allow/Deny prompt. Hitting 'Allow' redirects to the ``redirect URI`` with -the auth code. Home servers can choose any path for the ``redirect URI``. Once -the OAuth flow has completed, the client retries the request with the session -only, as above. - -Email-based (identity server) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:Type: - ``m.login.email.identity`` -:Description: - Authentication is supported by authorising an email address with an identity - server. - -Prior to submitting this, the client should authenticate with an identity -server. After authenticating, the session information should be submitted to -the home server. - -To respond to this type, reply with an auth dict as follows:: - - { - "type": "m.login.email.identity", - "threepidCreds": [ - { - "sid": "", - "client_secret": "", - "id_server": "" - } - ] - } - -Dummy Auth -~~~~~~~~~~ -:Type: - ``m.login.dummy`` -:Description: - Dummy authentication always succeeds and requires no extra parameters. Its - purpose is to allow servers to not require any form of User-Interactive - Authentication to perform a request. - -To respond to this type, reply with an auth dict with just the type and session, -if provided:: - - { - "type": "m.login.dummy", - } - - -Fallback -~~~~~~~~ -Clients cannot be expected to be able to know how to process every single login -type. If a client does not know how to handle a given login type, it can direct -the user to a web browser with the URL of a fallback page which will allow the -user to complete that login step out-of-band in their web browser. The URL it -should open is the Home Server base URL plus prefix, plus:: - - /auth//fallback/web?session= - -Where ``stage type`` is the type name of the stage it is attempting and -``session id`` is the ID of the session given by the home server. - -This MUST return an HTML page which can perform this authentication stage. This -page must attempt to call the JavaScript function ``window.onAuthDone`` when -the authentication has been completed. - -Pagination ----------- - -Querying large datasets in Matrix always uses the same pagination API pattern to -to give clients a consistent way of selecting subsets of a potentially changing -dataset. Requests pass in ``from``, ``to`` and ``limit`` parameters which describe -where to read from the stream. ``from`` and ``to`` are opaque textual 'stream -tokens' which describe positions in the dataset. The response returns new -``start`` and ``end`` stream token values which can then be passed to subsequent -requests to continue pagination. - -Pagination Request Query Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Query parameters: - from: - $streamtoken - The opaque token to start streaming from. - to: - $streamtoken - The opaque token to end streaming at. Typically, - clients will not know the item of data to end at, so this will usually be - omitted. - limit: - integer - An integer representing the maximum number of items to - return. - -'START' and 'END' are placeholder values used in these examples to describe the -start and end of the dataset respectively. - -Unless specified, the default pagination parameters are from=START, to=END, -without a limit set. This allows you to hit an API like -/events without any query parameters to get everything. - -For example, the event stream has events E1 -> E15. The client wants the last 5 -events and doesn't know any previous events:: - - S E - |-E1-E2-E3-E4-E5-E6-E7-E8-E9-E10-E11-E12-E13-E14-E15-| - | | | - | _____| | - |__________________ | ___________________| - | | | - GET /events?to=START&limit=5&from=END - Returns: - E15,E14,E13,E12,E11 - - -Another example: a public room list has rooms R1 -> R17. The client is showing 5 -rooms at a time on screen, and is on page 2. They want to -now show page 3 (rooms R11 -> 15):: - - S E - | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | stream token - |-R1-R2-R3-R4-R5-R6-R7-R8-R9-R10-R11-R12-R13-R14-R15-R16-R17| room - |____________| |________________| - | | - Currently | - viewing | - | - GET /rooms/list?from=9&to=END&limit=5 - Returns: R11,R12,R13,R14,R15 - -Note that tokens are treated in an *exclusive*, not inclusive, manner. The end -token from the initial request was '9' which corresponded to R10. When the 2nd -request was made, R10 did not appear again, even though from=9 was specified. If -you know the token, you already have the data. - -Pagination Response -~~~~~~~~~~~~~~~~~~~ - -Responses to pagination requests MUST follow the format:: - - { - "chunk": [ ... , Responses , ... ], - "start" : $streamtoken, - "end" : $streamtoken - } - -Where $streamtoken is an opaque token which can be used in another query to -get the next set of results. The "start" and "end" keys can only be omitted if -the complete dataset is provided in "chunk". - -Events ------- - -.. _sect:events: - -Overview -~~~~~~~~ - -The model of conversation history exposed by the client-server API can be -considered as a list of events. The server 'linearises' the -eventually-consistent event graph of events into an 'event stream' at any given -point in time:: - - [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9] - -Clients can add to the stream by POSTing message or state events, and can read -from the stream via the |initialSync|_, |/rooms//initialSync|_, `Event -Stream`_ and |/rooms//messages|_ APIs. - -For reading events, the intended flow of operation is to call -$PREFIX/initialSync, which returns all of the state and the last N events in the -event stream for each room, including ``start`` and ``end`` values describing the -pagination of each room's event stream. For instance, -$PREFIX/initialSync?limit=5 might return the events for a room in the -rooms[0].messages.chunk[] array, with tokens describing the start and end of the -range in rooms[0].messages.start as '1-2-3' and rooms[0].messages.end as -'a-b-c'. - -You can visualise the range of events being returned as:: - - [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9] - ^ ^ - | | - start: '1-2-3' end: 'a-b-c' - -Now, to receive future events in real-time on the eventstream, you simply GET -$PREFIX/events with a ``from`` parameter of 'a-b-c': in other words passing in the -``end`` token returned by initial sync. The request blocks until new events are -available or until your specified timeout elapses, and then returns a -new paginatable chunk of events alongside new start and end parameters:: - - [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]->[E10] - ^ ^ - | | - | end: 'x-y-z' - start: 'a-b-c' - -To resume polling the events stream, you pass in the new ``end`` token as the -``from`` parameter of $PREFIX/events and poll again. - -Similarly, to paginate events backwards in order to lazy-load in previous -history from the room, you simply GET $PREFIX/rooms//messages -specifying the ``from`` token to paginate backwards from and a limit of the number -of messages to retrieve. For instance, calling this API with a ``from`` parameter -of '1-2-3' and a limit of 5 would return:: - - [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]->[E10] - ^ ^ - | | - start: 'u-v-w' end: '1-2-3' - -To continue paginating backwards, one calls the /messages API again, supplying -the new ``start`` value as the ``from`` parameter. - - -Receiving live updates on a client -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Clients receive new events by long-polling the home server via the -$PREFIX/events API, specifying a timeout in milliseconds in the timeout -parameter. This will hold open the HTTP connection for a short period of time -waiting for new events, returning early if an event occurs. This is called the -`Event Stream`_. All events which are visible to the client will appear in the -event stream. When the request returns, an ``end`` token is included in the -response. This token can be used in the next request to continue where the -last request left off. - -All events must be de-duplicated based on their event ID. - -.. TODO - is deduplication actually a hard requirement in CS v2? - -.. TODO-spec - Do we ever return multiple events in a single request? - Don't we get lots of request setup RTT latency if we only do one event per request? - Do we ever support streaming requests? Why not websockets? - -When the client first logs in, they will need to initially synchronise with -their home server. This is achieved via the |initialSync|_ API. This API also -returns an ``end`` token which can be used with the event stream. See the 'Room Sync' section below. - -Events in a room -~~~~~~~~~~~~~~~~ - -Room events are split into two categories: - -:State Events: - These are events which update the metadata state of the room (e.g. room topic, - room membership etc). State is keyed by a tuple of event ``type`` and a - ``state_key``. State in the room with the same key-tuple will be overwritten. - -:Message events: - These are events which describe transient "once-off" activity in a room: - typically communication such as sending an instant message or setting up a - VoIP call. These used to be called 'non-state' events. - -This specification outlines several events, all with the event type prefix -``m.``. However, applications may wish to add their own type of event, and this -can be achieved using the REST API detailed in the following sections. If new -events are added, the event ``type`` key SHOULD follow the Java package naming -convention, e.g. ``com.example.myapp.event``. This ensures event types are -suitably namespaced for each application and reduces the risk of clashes. - -State events -~~~~~~~~~~~~ - -State events can be sent by ``PUT`` ing to -|/rooms//state//|_. These events will be -overwritten if ````, ```` and ```` all match. -If the state event has no ``state_key``, it can be omitted from the path. These -requests **cannot use transaction IDs** like other ``PUT`` paths because they -cannot be differentiated from the ``state_key``. Furthermore, ``POST`` is -unsupported on state paths. Valid requests look like:: - - PUT /rooms/!roomid:domain/state/m.example.event - { "key" : "without a state key" } - - PUT /rooms/!roomid:domain/state/m.another.example.event/foo - { "key" : "with 'foo' as the state key" } - -In contrast, these requests are invalid:: - - POST /rooms/!roomid:domain/state/m.example.event/ - { "key" : "cannot use POST here" } - - PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11 - { "key" : "txnIds are not supported" } - -Care should be taken to avoid setting the wrong ``state key``:: - - PUT /rooms/!roomid:domain/state/m.another.example.event/11 - { "key" : "with '11' as the state key, but was probably intended to be a txnId" } - -The ``state_key`` is often used to store state about individual users, by using -the user ID as the ``state_key`` value. For example:: - - PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com - { "animal" : "cat", "reason": "fluffy" } - -In some cases, there may be no need for a ``state_key``, so it can be omitted:: - - PUT /rooms/!roomid:domain/state/m.room.bgd.color - { "color": "red", "hex": "#ff0000" } - -See `Room Events`_ for the ``m.`` event specification. - -Message events -~~~~~~~~~~~~~~ - -Message events can be sent by sending a request to -|/rooms//send/|_. These requests *can* use transaction -IDs and ``PUT``/``POST`` methods. Message events allow access to historical -events and pagination, making it best suited for sending messages. For -example:: - - POST /rooms/!roomid:domain/send/m.custom.example.message - { "text": "Hello world!" } - - PUT /rooms/!roomid:domain/send/m.custom.example.message/11 - { "text": "Goodbye world!" } - -See `Room Events`_ for the ``m.`` event specification. - -Syncing rooms -~~~~~~~~~~~~~ - -.. NOTE:: - This section is a work in progress. - -When a client logs in, they may have a list of rooms which they have already -joined. These rooms may also have a list of events associated with them. The -purpose of 'syncing' is to present the current room and event information in a -convenient, compact manner. The events returned are not limited to room events; -presence events will also be returned. A single syncing API is provided: - - - |initialSync|_ : A global sync which will present room and event information - for all rooms the user has joined. - -.. TODO-spec room-scoped initial sync - - |/rooms//initialSync|_ : A sync scoped to a single room. Presents - room and event information for this room only. - - Room-scoped initial sync is Very Tricky because typically people would - want to sync the room then listen for any new content from that point - onwards. The event stream cannot do this for a single room currently. - As a result, commenting room-scoped initial sync at this time. - -The |initialSync|_ API contains the following keys: - -``presence`` - Description: - Contains a list of presence information for users the client is interested - in. - Format: - A JSON array of ``m.presence`` events. - -``end`` - Description: - Contains an event stream token which can be used with the `Event Stream`_. - Format: - A string containing the event stream token. - -``rooms`` - Description: - Contains a list of room information for all rooms the client has joined, - and limited room information on rooms the client has been invited to. - Format: - A JSON array containing Room Information JSON objects. - -Room Information: - Description: - Contains all state events for the room, along with a limited amount of - the most recent events, configured via the ``limit`` query - parameter. Also contains additional keys with room metadata, such as the - ``room_id`` and the client's ``membership`` to the room. - Format: - A JSON object with the following keys: - ``room_id`` - A string containing the ID of the room being described. - ``membership`` - A string representing the client's membership status in this room. - ``messages`` - An event stream JSON object containing a ``chunk`` of recent - events (both state events and non-state events), along with an ``end`` token. - ``state`` - A JSON array containing all the current state events for this room. - -Getting events for a room -~~~~~~~~~~~~~~~~~~~~~~~~~ - -There are several APIs provided to ``GET`` events for a room: - -{{rooms_http_api}} - -Redactions -~~~~~~~~~~ -Since events are extensible it is possible for malicious users and/or servers -to add keys that are, for example offensive or illegal. Since some events -cannot be simply deleted, e.g. membership events, we instead 'redact' events. -This involves removing all keys from an event that are not required by the -protocol. This stripped down event is thereafter returned anytime a client or -remote server requests it. Redacting an event cannot be undone, allowing server -owners to delete the offending content from the databases. Events that have been -redacted include a ``redacted_because`` key whose value is the event that caused -it to be redacted, which may include a reason. - -.. TODO - Currently, only room admins can redact events by sending a ``m.room.redaction`` - event, but server admins also need to be able to redact events by a similar - mechanism. - -Upon receipt of a redaction event, the server should strip off any keys not in -the following list: - - - ``event_id`` - - ``type`` - - ``room_id`` - - ``user_id`` - - ``state_key`` - - ``prev_state`` - - ``content`` - -The content object should also be stripped of all keys, unless it is one of -one of the following event types: - - - ``m.room.member`` allows key ``membership`` - - ``m.room.create`` allows key ``creator`` - - ``m.room.join_rules`` allows key ``join_rule`` - - ``m.room.power_levels`` allows keys ``ban``, ``events``, ``events_default``, - ``kick``, ``redact``, ``state_default``, ``users``, ``users_default``. - - ``m.room.aliases`` allows key ``aliases`` - -.. TODO - Need to update m.room.power_levels to reflect new power levels formatting - -The redaction event should be added under the key ``redacted_because``. When a -client receives a redaction event it should change the redacted event -in the same way a server does. - -Rooms ------ - -Creation -~~~~~~~~ -To create a room, a client has to use the |createRoom|_ API. There are various -options which can be set when creating a room: - -``visibility`` - Type: - String - Optional: - Yes - Value: - Either ``public`` or ``private``. - Description: - A ``public`` visibility indicates that the room will be shown in the public - room list. A ``private`` visibility will hide the room from the public room - list. Rooms default to ``private`` visibility if this key is not included. - -``room_alias_name`` - Type: - String - Optional: - Yes - Value: - The room alias localpart. - Description: - If this is included, a room alias will be created and mapped to the newly - created room. The alias will belong on the same home server which created - the room, e.g. ``!qadnasoi:domain.com >>> #room_alias_name:domain.com`` - -``name`` - Type: - String - Optional: - Yes - Value: - The ``name`` value for the ``m.room.name`` state event. - Description: - If this is included, an ``m.room.name`` event will be sent into the room to - indicate the name of the room. See `Room Events`_ for more information on - ``m.room.name``. - -``topic`` - Type: - String - Optional: - Yes - Value: - The ``topic`` value for the ``m.room.topic`` state event. - Description: - If this is included, an ``m.room.topic`` event will be sent into the room - to indicate the topic for the room. See `Room Events`_ for more information - on ``m.room.topic``. - -``invite`` - Type: - List - Optional: - Yes - Value: - A list of user ids to invite. - Description: - This will tell the server to invite everyone in the list to the newly - created room. - -``creation_content`` - Type: - Object - Optional: - Yes - Value: - Extra keys to be added to the content of the ``m.room.create``. The server - will clober the following keys: ``creator``. Future versions of this - spec may allow the server to clobber other keys if required. - Description: - Allows clients to add keys to the content of ``m.room.create``. - -``preset`` - Type: - String - Optional: - Yes - Value: - ``private_chat``, ``trusted_private_chat`` or ``public_chat`` - Description: - Convenience parameter for setting various default state events based on a - preset. - - Three presets are defined: - - - ``private_chat``: Sets the ``join_rules`` to ``invite`` and - ``history_visibility`` to ``shared`` - - ``trusted_private_chat``: Set the ``join_rules`` to ``invite``, - ``history_visibility`` to ``shared`` and gives all invitees the same - power level as the creator. - - ``public_chat``: Sets the ``join_rules`` to ``public`` and - ``history_visibility`` to ``shared`` - -``initial_state`` - Type: - List - Optional: - Yes - Value: - A list of state events to set in the new room. - Description: - Allows the user to override the default state events set in the new room. - - The expected format of the state events are an object with ``type``, - ``state_key`` and ``content`` keys set. - - Takes precedence over events set by ``presets``, but gets overriden by - ``name`` and ``topic`` keys. - -Example:: - - { - "preset": "public_chat", - "room_alias_name": "thepub", - "name": "The Grand Duke Pub", - "topic": "All about happy hour", - "creation_content": { - "m.federate": false - } - } - -The home server will create a ``m.room.create`` event when the room is created, -which serves as the root of the PDU graph for this room. This event also has a -``creator`` key which contains the user ID of the room creator. It will also -generate several other events in order to manage permissions in this room. This -includes: - - - ``m.room.power_levels`` : Sets the power levels of users and required power - levels. - - ``m.room.join_rules`` : Whether the room is "invite-only" or not. - -See `Room Events`_ for more information on these events. - -Room aliases -~~~~~~~~~~~~ -.. NOTE:: - This section is a work in progress. - -Room aliases can be created by sending a ``PUT /directory/room/``:: - - { - "room_id": - } - -They can be deleted by sending a ``DELETE /directory/room/`` with -no content. Only some privileged users may be able to delete room aliases, e.g. -server admins, the creator of the room alias, etc. This specification does not -outline the privilege level required for deleting room aliases. - -As room aliases are scoped to a particular home server domain name, it is -likely that a home server will reject attempts to maintain aliases on other -domain names. This specification does not provide a way for home servers to -send update requests to other servers. - -Rooms store a *partial* list of room aliases via the ``m.room.aliases`` state -event. This alias list is partial because it cannot guarantee that the alias -list is in any way accurate or up-to-date, as room aliases can point to -different room IDs over time. Crucially, the aliases in this event are -**purely informational** and SHOULD NOT be treated as accurate. They SHOULD -be checked before they are used or shared with another user. If a room -appears to have a room alias of ``#alias:example.com``, this SHOULD be checked -to make sure that the room's ID matches the ``room_id`` returned from the -request. - -Room aliases can be checked in the same way they are resolved; by sending a -``GET /directory/room/``:: - - { - "room_id": , - "servers": [ , , ] - } - -Home servers can respond to resolve requests for aliases on other domains than -their own by using the federation API to ask other domain name home servers. - - -Permissions -~~~~~~~~~~~ -.. NOTE:: - This section is a work in progress. - -Permissions for rooms are done via the concept of power levels - to do any -action in a room a user must have a suitable power level. Power levels are -stored as state events in a given room. The power levels required for operations -and the power levels for users are defined in ``m.room.power_levels``, where -both a default and specific users' power levels can be set. -By default all users have a power level of 0, other than the room creator whose -power level defaults to 100. Users can grant other users increased power levels -up to their own power level. For example, user A with a power level of 50 could -increase the power level of user B to a maximum of level 50. Power levels for -users are tracked per-room even if the user is not present in the room. -The keys contained in ``m.room.power_levels`` determine the levels required for -certain operations such as kicking, banning and sending state events. See -`m.room.power_levels`_ for more information. - -Joining rooms -------------- -Users need to be a member of a room in order to send and receive events in that -room. There are several states in which a user may be, in relation to a room: - - - Unrelated (the user cannot send or receive events in the room) - - Invited (the user has been invited to participate in the room, but is not - yet participating) - - Joined (the user can send and receive events in the room) - - Banned (the user is not allowed to join the room) - -Some rooms require that users be invited to it before they can join; others -allow anyone to join. Whether a given room is an "invite-only" room is -determined by the room config key ``m.room.join_rules``. It can have one of the -following values: - -``public`` - This room is free for anyone to join without an invite. - -``invite`` - This room can only be joined if you were invited. - -{{membership_http_api}} - -Leaving rooms -~~~~~~~~~~~~~ -.. TODO-spec - HS deleting rooms they are no longer a part of. Not implemented. - - This is actually Very Tricky. If all clients a HS is serving leave a room, - the HS will no longer get any new events for that room, because the servers - who get the events are determined on the *membership list*. There should - probably be a way for a HS to lurk on a room even if there are 0 of their - members in the room. - - Grace period before deletion? - - Under what conditions should a room NOT be purged? - - -A user can leave a room to stop receiving events for that room. A user must -have joined the room before they are eligible to leave the room. If the room is -an "invite-only" room, they will need to be re-invited before they can re-join -the room. To leave a room, a request should be made to -|/rooms//leave|_ with:: - - {} - -Alternatively, the membership state for this user in this room can be modified -directly by sending the following request to -``/rooms//state/m.room.member/``:: - - { - "membership": "leave" - } - -See the `Room events`_ section for more information on ``m.room.member``. Once a -user has left a room, that room will no longer appear on the |initialSync|_ API. -If all members in a room leave, that room becomes eligible for deletion. - -Banning users in a room -~~~~~~~~~~~~~~~~~~~~~~~ -A user may decide to ban another user in a room. 'Banning' forces the target -user to leave the room and prevents them from re-joining the room. A banned -user will not be treated as a joined user, and so will not be able to send or -receive events in the room. In order to ban someone, the user performing the -ban MUST have the required power level. To ban a user, a request should be made -to |/rooms//ban|_ with:: - - { - "user_id": "" - } - -Banning a user adjusts the banned member's membership state to ``ban`` and -adjusts the power level of this event to a level higher than the banned person. -Like with other membership changes, a user can directly adjust the target -member's state, by making a request to -``/rooms//state/m.room.member/``:: - - { - "membership": "ban" - } - - -Registration ------------- -This section refers to API Version 2. These API calls currently use the prefix -``/_matrix/client/v2_alpha``. - -Registering for a user account is done using the request:: - - POST $V2PREFIX/register - -This API endpoint uses the User-Interactive Authentication API. -This API endpoint does not require an access token. - -The body of the POST request is a JSON object containing: - -username - Optional. This is the local part of the desired Matrix ID. If omitted, the - Home Server must generate a Matrix ID local part. -password - Required. The desired password for the account. -bind_email - Optional. If ``true``, the server binds the email used for authentication to - the Matrix ID with the ID Server. - -On success, this returns a JSON object with keys: - -user_id - The fully-qualified Matrix ID that has been registered. -access_token - An access token for the new account. -home_server - The hostname of the Home Server on which the account has been registered. - -This endpoint may also return the following error codes: - -M_USER_IN_USE - If the Matrix ID is already in use -M_EXCLUSIVE - If the requested Matrix ID is in the exclusive namespace of an application - service. - -Home Servers MUST perform the relevant checks and return these codes before -performing User-Interactive Authentication, although they may also return -them after authentication is completed if, for example, the requested user ID -was registered whilst the client was performing authentication. - -Old V1 API docs: |register|_ - -{{login_http_api}} - -Changing Password -~~~~~~~~~~~~~~~~~ -This section refers to API Version 2. These API calls currently use the prefix -``/_matrix/client/v2_alpha``. - -Request:: - - POST $V2PREFIX/account/password - -This API endpoint uses the User-Interactive Authentication API. An access token -should be submitted to this endpoint if the client has an active session. The -Home Server may change the flows available depending on whether a valid access -token is provided. - -The body of the POST request is a JSON object containing: - -new_password - The new password for the account. - -On success, an empty JSON object is returned. - -The error code M_NOT_FOUND is returned if the user authenticated with a third -party identifier but the Home Server could not find a matching account in its -database. - -Adding a Third Party Identifier -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This section refers to API Version 2. These API calls currently use the prefix -``/_matrix/client/v2_alpha``. - -Request:: - - POST $V2PREFIX/account/3pid - -Used to add a third party identifier to the user's account. - -The body of the POST request is a JSON object containing: - -threePidCreds - An object containing third party identifier credentials. -bind - Optional. A boolean indicating whether the Home Server should also bind this - third party identifier to the account's matrix ID with the Identity Server. If - supplied and true, the Home Server must bind the 3pid accordingly. - -The third party identifier credentials object comprises: - -id_server - The colon-separated hostname and port of the Identity Server used to - authenticate the third party identifier. If the port is the default, it and the - colon should be omitted. -sid - The session ID given by the Identity Server -client_secret - The client secret used in the session with the Identity Server. - -On success, the empty JSON object is returned. - -May also return error codes: - -M_THREEPID_AUTH_FAILED - If the credentials provided could not be verified with the ID Server. - -Fetching Currently Associated Third Party Identifiers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This section refers to API Version 2. These API calls currently use the prefix -``/_matrix/client/v2_alpha``. - -Request:: - - GET $V2PREFIX/account/3pid - -This returns a list of third party identifiers that the Home Server has -associated with the user's account. This is *not* the same as the list of third -party identifiers bound to the user's Matrix ID in Identity Servers. Identifiers -in this list may be used by the Home Server as, for example, identifiers that it -will accept to reset the user's account password. - -Returns a JSON object with the key ``threepids`` whose contents is an array of -objects with the following keys: - -medium - The medium of the 3pid (eg, ``email``) -address - The textual address of the 3pid, eg. the email address - - -Profiles --------- - -{{profile_http_api}} - -Events on Change of Profile Information -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Because the profile display name and avatar information are likely to be used in -many places of a client's display, changes to these fields cause an automatic -propagation event to occur, informing likely-interested parties of the new -values. This change is conveyed using two separate mechanisms: - - - a ``m.room.member`` event is sent to every room the user is a member of, - to update the ``displayname`` and ``avatar_url``. - - a ``m.presence`` presence status update is sent, again containing the new - values of the ``displayname`` and ``avatar_url`` keys, in addition to the - required ``presence`` key containing the current presence state of the user. - -Both of these should be done automatically by the home server when a user -successfully changes their display name or avatar URL fields. - -Additionally, when home servers emit room membership events for their own -users, they should include the display name and avatar URL fields in these -events so that clients already have these details to hand, and do not have to -perform extra round trips to query it. - -Security --------- - -Rate limiting -~~~~~~~~~~~~~ -Home servers SHOULD implement rate limiting to reduce the risk of being -overloaded. If a request is refused due to rate limiting, it should return a -standard error response of the form:: - - { - "errcode": "M_LIMIT_EXCEEDED", - "error": "string", - "retry_after_ms": integer (optional) - } - -The ``retry_after_ms`` key SHOULD be included to tell the client how long they -have to wait in milliseconds before they can try again. - -.. TODO-spec - - Surely we should recommend an algorithm for the rate limiting, rather than letting every - homeserver come up with their own idea, causing totally unpredictable performance over - federated rooms? - - -.. Links through the external API docs are below -.. ============================================= - -.. |createRoom| replace:: ``/createRoom`` -.. _createRoom: /docs/api/client-server/#!/-rooms/create_room - -.. |initialSync| replace:: ``/initialSync`` -.. _initialSync: /docs/api/client-server/#!/-events/initial_sync - -.. |/rooms//initialSync| replace:: ``/rooms//initialSync`` -.. _/rooms//initialSync: /docs/api/client-server/#!/-rooms/get_room_sync_data - -.. |login| replace:: ``/login`` -.. _login: /docs/api/client-server/#!/-login - -.. |register| replace:: ``/register`` -.. _register: /docs/api/client-server/#!/-registration - -.. |/rooms//messages| replace:: ``/rooms//messages`` -.. _/rooms//messages: /docs/api/client-server/#!/-rooms/get_messages - -.. |/rooms//members| replace:: ``/rooms//members`` -.. _/rooms//members: /docs/api/client-server/#!/-rooms/get_members - -.. |/rooms//state| replace:: ``/rooms//state`` -.. _/rooms//state: /docs/api/client-server/#!/-rooms/get_state_events - -.. |/rooms//send/| replace:: ``/rooms//send/`` -.. _/rooms//send/: /docs/api/client-server/#!/-rooms/send_non_state_event - -.. |/rooms//state//| replace:: ``/rooms//state//`` -.. _/rooms//state//: /docs/api/client-server/#!/-rooms/send_state_event - -.. |/rooms//invite| replace:: ``/rooms//invite`` -.. _/rooms//invite: /docs/api/client-server/#!/-rooms/invite - -.. |/rooms//join| replace:: ``/rooms//join`` -.. _/rooms//join: /docs/api/client-server/#!/-rooms/join_room - -.. |/rooms//leave| replace:: ``/rooms//leave`` -.. _/rooms//leave: /docs/api/client-server/#!/-rooms/leave - -.. |/rooms//ban| replace:: ``/rooms//ban`` -.. _/rooms//ban: /docs/api/client-server/#!/-rooms/ban - -.. |/join/| replace:: ``/join/`` -.. _/join/: /docs/api/client-server/#!/-rooms/join - -.. _`Event Stream`: /docs/api/client-server/#!/-events/get_event_stream - diff --git a/specification/2-modules.rst b/specification/2-modules.rst deleted file mode 100644 index 0aad77e1c82..00000000000 --- a/specification/2-modules.rst +++ /dev/null @@ -1,3 +0,0 @@ -Modules -======= - diff --git a/specification/4-server_server_api.rst b/specification/4-server_server_api.rst deleted file mode 100644 index c5ff2b877d3..00000000000 --- a/specification/4-server_server_api.rst +++ /dev/null @@ -1,768 +0,0 @@ -Federation API -============== - -Matrix home servers use the Federation APIs (also known as server-server APIs) -to communicate with each other. Home servers use these APIs to push messages to -each other in real-time, to request historic messages from each other, and to -query profile and presence information about users on each other's servers. - -The APIs are implemented using HTTPS GETs and PUTs between each of the -servers. These HTTPS requests are strongly authenticated using public key -signatures at the TLS transport layer and using public key signatures in -HTTP Authorization headers at the HTTP layer. - -There are three main kinds of communication that occur between home servers: - -Persisted Data Units (PDUs): - These events are broadcast from one home server to any others that have - joined the same "context" (namely, a Room ID). They are persisted in - long-term storage and record the history of messages and state for a - context. - - Like email, it is the responsibility of the originating server of a PDU - to deliver that event to its recipient servers. However PDUs are signed - using the originating server's public key so that it is possible to - deliver them through third-party servers. - -Ephemeral Data Units (EDUs): - These events are pushed between pairs of home servers. They are not - persisted and are not part of the history of a "context", nor does the - receiving home server have to reply to them. - -Queries: - These are single request/response interactions between a given pair of - servers, initiated by one side sending an HTTPS GET request to obtain some - information, and responded by the other. They are not persisted and contain - no long-term significant history. They simply request a snapshot state at - the instant the query is made. - - -EDUs and PDUs are further wrapped in an envelope called a Transaction, which is -transferred from the origin to the destination home server using an HTTPS PUT -request. - -Server Discovery ----------------- - -Resolving Server Names -~~~~~~~~~~~~~~~~~~~~~~ - -Each matrix home server is identified by a server name consisting of a DNS name -and an optional TLS port. - -.. code:: - - server_name = dns_name [ ":" tls_port] - dns_name = - tls_port = *DIGIT - -.. ** - -If the port is present then the server is discovered by looking up an AAAA or -A record for the DNS name and connecting to the specified TLS port. If the port -is absent then the server is discovered by looking up a ``_matrix._tcp`` SRV -record for the DNS name. If this record does not exist then the server is -discovered by looking up an AAAA or A record on the DNS name and taking the -default fallback port number of 8448. -Home servers may use SRV records to load balance requests between multiple TLS -endpoints or to failover to another endpoint if an endpoint fails. - -Retrieving Server Keys -~~~~~~~~~~~~~~~~~~~~~~ - -Version 2 -+++++++++ - -Each home server publishes its public keys under ``/_matrix/key/v2/server/``. -Home servers query for keys by either getting ``/_matrix/key/v2/server/`` -directly or by querying an intermediate notary server using a -``/_matrix/key/v2/query`` API. Intermediate notary servers query the -``/_matrix/key/v2/server/`` API on behalf of another server and sign the -response with their own key. A server may query multiple notary servers to -ensure that they all report the same public keys. - -This approach is borrowed from the `Perspectives Project`_, but modified to -include the NACL keys and to use JSON instead of XML. It has the advantage of -avoiding a single trust-root since each server is free to pick which notary -servers they trust and can corroborate the keys returned by a given notary -server by querying other servers. - -.. _Perspectives Project: http://perspectives-project.org/ - -Publishing Keys -^^^^^^^^^^^^^^^ - -Home servers publish the allowed TLS fingerprints and signing keys in a JSON -object at ``/_matrix/key/v2/server/{key_id}``. The response contains a list of -``verify_keys`` that are valid for signing federation requests made by the -server and for signing events. It contains a list of ``old_verify_keys`` -which are only valid for signing events. Finally the response contains a list -of TLS certificate fingerprints to validate any connection made to the server. - -A server may have multiple keys active at a given time. A server may have any -number of old keys. It is recommended that servers return a single JSON -response listing all of its keys whenever any ``key_id`` is requested to reduce -the number of round trips needed to discover the relevant keys for a server. -However a server may return a different responses for a different ``key_id``. - -The ``tls_certificates`` contain a list of hashes of the X.509 TLS certificates -currently used by the server. The list must include SHA-256 hashes for every -certificate currently in use by the server. These fingerprints are valid until -the millisecond POSIX timestamp in ``valid_until_ts``. - -The ``verify_keys`` can be used to sign requests and events made by the server -until the millisecond POSIX timestamp in ``valid_until_ts``. If a Home Server -receives an event with a ``origin_server_ts`` after the ``valid_until_ts`` then -it should request that ``key_id`` for the originating server to check whether -the key has expired. - -The ``old_verify_keys`` can be used to sign events with an ``origin_server_ts`` -before the ``expired_ts``. The ``expired_ts`` is a millisecond POSIX timestamp -of when the originating server stopped using that key. - -Intermediate notary servers should cache a response for half of its remaining -life time to avoid serving a stale response. Originating servers should avoid -returning responses that expire in less than an hour to avoid repeated requests -for an about to expire certificate. Requesting servers should limit how -frequently they query for certificates to avoid flooding a server with requests. - -If a server goes offline intermediate notary servers should continue to return -the last response they received from that server so that the signatures of old -events sent by that server can still be checked. - -==================== =================== ====================================== - Key Type Description -==================== =================== ====================================== -``server_name`` String DNS name of the home server. -``verify_keys`` Object Public keys of the home server for - verifying digital signatures. -``old_verify_keys`` Object The public keys that the server used - to use and when it stopped using them. -``signatures`` Object Digital signatures for this object - signed using the ``verify_keys``. -``tls_fingerprints`` Array of Objects Hashes of X.509 TLS certificates used - by this this server encoded as base64. -``valid_until_ts`` Integer POSIX timestamp when the list of valid - keys should be refreshed. -==================== =================== ====================================== - - -.. code:: json - - { - "old_verify_keys": { - "ed25519:auto1": { - "expired_ts": 922834800000, - "key": "Base+64+Encoded+Old+Verify+Key" - } - }, - "server_name": "example.org", - "signatures": { - "example.org": { - "ed25519:auto2": "Base+64+Encoded+Signature" - } - }, - "tls_fingerprints": [ - { - "sha256": "Base+64+Encoded+SHA-256-Fingerprint" - } - ], - "valid_until_ts": 1052262000000, - "verify_keys": { - "ed25519:auto2": { - "key": "Base+64+Encoded+Signature+Verification+Key" - } - } - } - -Querying Keys Through Another Server -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Servers may offer a query API ``_matrix/key/v2/query/`` for getting the keys -for another server. This API can be used to GET at list of JSON objects for a -given server or to POST a bulk query for a number of keys from a number of -servers. Either way the response is a list of JSON objects containing the -JSON published by the server under ``_matrix/key/v2/server/`` signed by -both the originating server and by this server. - -The ``minimum_valid_until_ts`` is a millisecond POSIX timestamp indicating -when the returned certificate will need to be valid until to be useful to the -requesting server. This can be set using the maximum ``origin_server_ts`` of -an batch of events that a requesting server is trying to validate. This allows -an intermediate notary server to give a prompt cached response even if the -originating server is offline. - -This API can return keys for servers that are offline be using cached responses -taken from when the server was online. Keys can be queried from multiple -servers to mitigate against DNS spoofing. - -Requests: - -.. code:: - - GET /_matrix/key/v2/query/${server_name}/${key_id}/?minimum_valid_until_ts=${minimum_valid_until_ts} HTTP/1.1 - - POST /_matrix/key/v2/query HTTP/1.1 - Content-Type: application/json - - { - "server_keys": { - "$server_name": { - "$key_id": { - "minimum_valid_until_ts": $posix_timestamp - } - } - } - } - - -Response: - -.. code:: - - HTTP/1.1 200 OK - Content-Type: application/json - { - "server_keys": [ - # List of responses with same format as /_matrix/key/v2/server - # signed by both the originating server and this server. - ] - } - -Version 1 -+++++++++ -.. WARNING:: - Version 1 of key distribution is obsolete - - -Home servers publish their TLS certificates and signing keys in a JSON object -at ``/_matrix/key/v1``. - -==================== =================== ====================================== - Key Type Description -==================== =================== ====================================== -``server_name`` String DNS name of the home server. -``verify_keys`` Object Public keys of the home server for - verifying digital signatures. -``signatures`` Object Digital signatures for this object - signed using the ``verify_keys``. -``tls_certificate`` String The X.509 TLS certificate used by this - this server encoded as base64. -==================== =================== ====================================== - -.. code:: json - - { - "server_name": "example.org", - "signatures": { - "example.org": { - "ed25519:auto": "Base+64+Encoded+Signature" - } - }, - "tls_certificate": "Base+64+Encoded+DER+Encoded+X509+TLS+Certificate" - "verify_keys": { - "ed25519:auto": "Base+64+Encoded+Signature+Verification+Key" - } - } - -When fetching the keys for a server the client should check that the TLS -certificate in the JSON matches the TLS server certificate for the connection -and should check that the JSON signatures are correct for the supplied -``verify_keys`` - -Transactions ------------- -.. WARNING:: - This section may be misleading or inaccurate. - -The transfer of EDUs and PDUs between home servers is performed by an exchange -of Transaction messages, which are encoded as JSON objects, passed over an HTTP -PUT request. A Transaction is meaningful only to the pair of home servers that -exchanged it; they are not globally-meaningful. - -Each transaction has: - - An opaque transaction ID. - - A timestamp (UNIX epoch time in milliseconds) generated by its origin - server. - - An origin and destination server name. - - A list of "previous IDs". - - A list of PDUs and EDUs - the actual message payload that the Transaction - carries. - -Transaction Fields -~~~~~~~~~~~~~~~~~~ - -==================== =================== ====================================== - Key Type Description -==================== =================== ====================================== -``origin`` String DNS name of homeserver making this - transaction. -``origin_server_ts`` Integer Timestamp in milliseconds on - originating homeserver when this - transaction started. -``previous_ids`` List of Strings List of transactions that were sent - immediately prior to this transaction. -``pdus`` List of Objects List of persistent updates to rooms. -``edus`` List of Objects List of ephemeral messages. -==================== =================== ====================================== - -.. code:: json - - { - "transaction_id":"916d630ea616342b42e98a3be0b74113", - "ts":1404835423000, - "origin":"red", - "prev_ids":["e1da392e61898be4d2009b9fecce5325"], - "pdus":[...], - "edus":[...] - } - -The ``prev_ids`` field contains a list of previous transaction IDs that the -``origin`` server has sent to this ``destination``. Its purpose is to act as a -sequence checking mechanism - the destination server can check whether it has -successfully received that Transaction, or ask for a re-transmission if not. - -The ``pdus`` field of a transaction is a list, containing zero or more PDUs.[*] -Each PDU is itself a JSON object containing a number of keys, the exact details -of which will vary depending on the type of PDU. Similarly, the ``edus`` field -is another list containing the EDUs. This key may be entirely absent if there -are no EDUs to transfer. - -(* Normally the PDU list will be non-empty, but the server should cope with -receiving an "empty" transaction, as this is useful for informing peers of other -transaction IDs they should be aware of. This effectively acts as a push -mechanism to encourage peers to continue to replicate content.) - -PDUs ----- - -All PDUs have: - -- An ID -- A context -- A declaration of their type -- A list of other PDU IDs that have been seen recently on that context - (regardless of which origin sent them) - - -Required PDU Fields -~~~~~~~~~~~~~~~~~~~ - -==================== ================== ======================================= - Key Type Description -==================== ================== ======================================= -``context`` String Event context identifier -``user_id`` String The ID of the user sending the PDU -``origin`` String DNS name of homeserver that created - this PDU -``pdu_id`` String Unique identifier for PDU on the - originating homeserver -``origin_server_ts`` Integer Timestamp in milliseconds on origin - homeserver when this PDU was created. -``pdu_type`` String PDU event type -``content`` Object The content of the PDU. -``prev_pdus`` List of (String, The originating homeserver, PDU ids and - String, Object) hashes of the most recent PDUs the - Triplets homeserver was aware of for the context - when it made this PDU -``depth`` Integer The maximum depth of the previous PDUs - plus one -``is_state`` Boolean True if this PDU is updating room state -==================== ================== ======================================= - -.. code:: json - - { - "context":"#example:green.example.com", - "origin":"green.example.com", - "pdu_id":"a4ecee13e2accdadf56c1025af232176", - "origin_server_ts":1404838188000, - "pdu_type":"m.room.message", - "prev_pdus":[ - ["blue.example.com","99d16afbc8", - {"sha256":"abase64encodedsha256hashshouldbe43byteslong"}] - ], - "hashes":{"sha256":"thishashcoversallfieldsincasethisisredacted"}, - "signatures":{ - "green.example.com":{ - "ed25519:key_version:":"these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus" - } - }, - "is_state":false, - "content": {...} - } - -In contrast to Transactions, it is important to note that the ``prev_pdus`` -field of a PDU refers to PDUs that any origin server has sent, rather than -previous IDs that this ``origin`` has sent. This list may refer to other PDUs -sent by the same origin as the current one, or other origins. - -Because of the distributed nature of participants in a Matrix conversation, it -is impossible to establish a globally-consistent total ordering on the events. -However, by annotating each outbound PDU at its origin with IDs of other PDUs -it has received, a partial ordering can be constructed allowing causality -relationships to be preserved. A client can then display these messages to the -end-user in some order consistent with their content and ensure that no message -that is semantically in reply of an earlier one is ever displayed before it. - -State Update PDU Fields -~~~~~~~~~~~~~~~~~~~~~~~ - -PDUs fall into two main categories: those that deliver Events, and those that -synchronise State. For PDUs that relate to State synchronisation, additional -keys exist to support this: - -======================== ============ ========================================= - Key Type Description -======================== ============ ========================================= -``state_key`` String Combined with the ``pdu_type`` this - identifies the which part of the room - state is updated -``required_power_level`` Integer The required power level needed to - replace this update. -``prev_state_id`` String The homeserver of the update this - replaces -``prev_state_origin`` String The PDU id of the update this replaces. -``user_id`` String The user updating the state. -======================== ============ ========================================= - -.. code:: json - - {..., - "is_state":true, - "state_key":TODO-doc - "required_power_level":TODO-doc - "prev_state_id":TODO-doc - "prev_state_origin":TODO-doc - } - - -EDUs ----- - -EDUs, by comparison to PDUs, do not have an ID, a context, or a list of -"previous" IDs. The only mandatory fields for these are the type, origin and -destination home server names, and the actual nested content. - -======================== ============ ========================================= - Key Type Description -======================== ============ ========================================= -``edu_type`` String The type of the ephemeral message. -``content`` Object Content of the ephemeral message. -======================== ============ ========================================= - -.. code:: json - - { - "edu_type":"m.presence", - "origin":"blue", - "destination":"orange", - "content":{...} - } - - -Protocol URLs -------------- - -.. WARNING:: - This section may be misleading or inaccurate. - -All these URLs are name-spaced within a prefix of:: - - /_matrix/federation/v1/... - -For active pushing of messages representing live activity "as it happens":: - - PUT .../send// - Body: JSON encoding of a single Transaction - Response: TODO-doc - -The transaction_id path argument will override any ID given in the JSON body. -The destination name will be set to that of the receiving server itself. Each -embedded PDU in the transaction body will be processed. - - -To fetch a particular PDU:: - - GET .../pdu/// - Response: JSON encoding of a single Transaction containing one PDU - -Retrieves a given PDU from the server. The response will contain a single new -Transaction, inside which will be the requested PDU. - - -To fetch all the state of a given context:: - - GET .../state// - Response: JSON encoding of a single Transaction containing multiple PDUs - -Retrieves a snapshot of the entire current state of the given context. The -response will contain a single Transaction, inside which will be a list of PDUs -that encode the state. - -To backfill events on a given context:: - - GET .../backfill// - Query args: v, limit - Response: JSON encoding of a single Transaction containing multiple PDUs - -Retrieves a sliding-window history of previous PDUs that occurred on the given -context. Starting from the PDU ID(s) given in the "v" argument, the PDUs that -preceded it are retrieved, up to a total number given by the "limit" argument. -These are then returned in a new Transaction containing all of the PDUs. - - -To stream events all the events:: - - GET .../pull/ - Query args: origin, v - Response: JSON encoding of a single Transaction consisting of multiple PDUs - -Retrieves all of the transactions later than any version given by the "v" -arguments. - - -To make a query:: - - GET .../query/ - Query args: as specified by the individual query types - Response: JSON encoding of a response object - -Performs a single query request on the receiving home server. The Query Type -part of the path specifies the kind of query being made, and its query -arguments have a meaning specific to that kind of query. The response is a -JSON-encoded object whose meaning also depends on the kind of query. - -Backfilling ------------ -.. NOTE:: - This section is a work in progress. - -.. TODO-doc - - What it is, when is it used, how is it done - - -Authentication --------------- - -Request Authentication -~~~~~~~~~~~~~~~~~~~~~~ - -Every HTTP request made by a homeserver is authenticated using public key -digital signatures. The request method, target and body are signed by wrapping -them in a JSON object and signing it using the JSON signing algorithm. The -resulting signatures are added as an Authorization header with an auth scheme -of X-Matrix. Note that the target field should include the full path starting with -``/_matrix/...``, including the ``?`` and any query parameters if present, but -should not include the leading ``https:``, nor the destination server's -hostname. - -Step 1 sign JSON: - -.. code:: - - { - "method": "GET", - "uri": "/target", - "origin": "origin.hs.example.com", - "destintation": "destination.hs.example.com", - "content": { JSON content ... }, - "signatures": { - "origin.hs.example.com": { - "ed25519:key1": "ABCDEF..." - } - } - } - -Step 2 add Authorization header: - -.. code:: - - GET /target HTTP/1.1 - Authorization: X-Matrix origin=origin.example.com,key="ed25519:key1",sig="ABCDEF..." - Content-Type: application/json - - { JSON content ... } - - -Example python code: - -.. code:: python - - def authorization_headers(origin_name, origin_signing_key, - destination_name, request_method, request_target, - content_json=None): - request_json = { - "method": request_method, - "uri": request_target, - "origin": origin_name, - "destination": destination_name, - } - - if content_json is not None: - request["content"] = content_json - - signed_json = sign_json(request_json, origin_name, origin_signing_key) - - authorization_headers = [] - - for key, sig in signed_json["signatures"][origin_name].items(): - authorization_headers.append(bytes( - "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % ( - origin_name, key, sig, - ) - )) - - return ("Authorization", authorization_headers) - -Response Authentication -~~~~~~~~~~~~~~~~~~~~~~~ - -Responses are authenticated by the TLS server certificate. A homeserver should -not send a request until it has authenticated the connected server to avoid -leaking messages to eavesdroppers. - -Client TLS Certificates -~~~~~~~~~~~~~~~~~~~~~~~ - -Requests are authenticated at the HTTP layer rather than at the TLS layer -because HTTP services like Matrix are often deployed behind load balancers that -handle the TLS and these load balancers make it difficult to check TLS client -certificates. - -A home server may provide a TLS client certficate and the receiving home server -may check that the client certificate matches the certificate of the origin -home server. - -Server-Server Authorization ---------------------------- - -.. TODO-doc - - PDU signing (see the Event signing section earlier) - - State conflict resolution (see below) - -State Conflict Resolution -------------------------- -.. NOTE:: - This section is a work in progress. - -.. TODO-doc - - How do conflicts arise (diagrams?) - - How are they resolved (incl tie breaks) - - How does this work with deleting current state - - How do we reject invalid federation traffic? - - [[TODO(paul): At this point we should probably have a long description of how - State management works, with descriptions of clobbering rules, power levels, etc - etc... But some of that detail is rather up-in-the-air, on the whiteboard, and - so on. This part needs refining. And writing in its own document as the details - relate to the server/system as a whole, not specifically to server-server - federation.]] - -Presence --------- -The server API for presence is based entirely on exchange of the following -EDUs. There are no PDUs or Federation Queries involved. - -Performing a presence update and poll subscription request:: - - EDU type: m.presence - - Content keys: - push: (optional): list of push operations. - Each should be an object with the following keys: - user_id: string containing a User ID - presence: "offline"|"unavailable"|"online"|"free_for_chat" - status_msg: (optional) string of free-form text - last_active_ago: milliseconds since the last activity by the user - - poll: (optional): list of strings giving User IDs - - unpoll: (optional): list of strings giving User IDs - -The presence of this combined message is two-fold: it informs the recipient -server of the current status of one or more users on the sending server (by the -``push`` key), and it maintains the list of users on the recipient server that -the sending server is interested in receiving updates for, by adding (by the -``poll`` key) or removing them (by the ``unpoll`` key). The ``poll`` and -``unpoll`` lists apply *changes* to the implied list of users; any existing IDs -that the server sent as ``poll`` operations in a previous message are not -removed until explicitly requested by a later ``unpoll``. - -On receipt of a message containing a non-empty ``poll`` list, the receiving -server should immediately send the sending server a presence update EDU of its -own, containing in a ``push`` list the current state of every user that was in -the original EDU's ``poll`` list. - -Sending a presence invite:: - - EDU type: m.presence_invite - - Content keys: - observed_user: string giving the User ID of the user whose presence is - requested (i.e. the recipient of the invite) - observer_user: string giving the User ID of the user who is requesting to - observe the presence (i.e. the sender of the invite) - -Accepting a presence invite:: - - EDU type: m.presence_accept - - Content keys - as for m.presence_invite - -Rejecting a presence invite:: - - EDU type: m.presence_deny - - Content keys - as for m.presence_invite - -.. TODO-doc - - Explain the timing-based round-trip reduction mechanism for presence - messages - - Explain the zero-byte presence inference logic - See also: docs/client-server/model/presence - -Profiles --------- - -The server API for profiles is based entirely on the following Federation -Queries. There are no additional EDU or PDU types involved, other than the -implicit ``m.presence`` and ``m.room.member`` events (see section below). - -Querying profile information:: - - Query type: profile - - Arguments: - user_id: the ID of the user whose profile to return - field: (optional) string giving a field name - - Returns: JSON object containing the following keys: - displayname: string of free-form text - avatar_url: string containing an HTTP-scheme URL - -If the query contains the optional ``field`` key, it should give the name of a -result field. If such is present, then the result should contain only a field -of that name, with no others present. If not, the result should contain as much -of the user's profile as the home server has available and can make public. - -Directory ---------- - -The server API for directory queries is also based on Federation Queries. - -Querying directory information:: - - Query type: directory - - Arguments: - room_alias: the room alias to query - - Returns: JSON object containing the following keys: - room_id: string giving the underlying room ID the alias maps to - servers: list of strings giving the join candidates - -The list of join candidates is a list of server names that are likely to hold -the given room; these are servers that the requesting server may wish to try -joining with. This list may or may not include the server answering the query. - diff --git a/specification/5-identity_servers.rst b/specification/5-identity_servers.rst deleted file mode 100644 index 6ec013bd75b..00000000000 --- a/specification/5-identity_servers.rst +++ /dev/null @@ -1,8 +0,0 @@ -Identity Servers -================ -.. NOTE:: - This section is a work in progress. - -.. TODO-doc Dave - - 3PIDs and identity server, functions - diff --git a/specification/appendices.rst b/specification/appendices.rst new file mode 100644 index 00000000000..4a106a3a5e3 --- /dev/null +++ b/specification/appendices.rst @@ -0,0 +1,19 @@ +.. Copyright 2015 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Appendices +========== + +.. contents:: Table of Contents +.. sectnum:: diff --git a/specification/appendices/base64.rst b/specification/appendices/base64.rst new file mode 100644 index 00000000000..d046e0fc15c --- /dev/null +++ b/specification/appendices/base64.rst @@ -0,0 +1,57 @@ +.. Copyright 2017 Vector Creations Limited +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Unpadded Base64 +--------------- + +*Unpadded* Base64 refers to 'standard' Base64 encoding as defined in `RFC +4648`_, without "=" padding. Specifically, where RFC 4648 requires that encoded +data be padded to a multiple of four characters using ``=`` characters, +unpadded Base64 omits this padding. + +For reference, RFC 4648 uses the following alphabet for Base 64:: + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w + 15 P 32 g 49 x + 16 Q 33 h 50 y + +Examples of strings encoded using unpadded Base64:: + + UNPADDED_BASE64("") = "" + UNPADDED_BASE64("f") = "Zg" + UNPADDED_BASE64("fo") = "Zm8" + UNPADDED_BASE64("foo") = "Zm9v" + UNPADDED_BASE64("foob") = "Zm9vYg" + UNPADDED_BASE64("fooba") = "Zm9vYmE" + UNPADDED_BASE64("foobar") = "Zm9vYmFy" + +When decoding Base64, implementations SHOULD accept input with or without +padding characters whereever possible, to ensure maximum interoperability. + +.. _`RFC 4648`: https://tools.ietf.org/html/rfc4648 diff --git a/specification/appendices/signing_json.rst b/specification/appendices/signing_json.rst new file mode 100644 index 00000000000..30bf91a078f --- /dev/null +++ b/specification/appendices/signing_json.rst @@ -0,0 +1,167 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Signing JSON +------------ + +Various points in the Matrix specification require JSON objects to be +cryptographically signed. This requires us to encode the JSON as a binary +string. Unfortunately the same JSON can be encoded in different ways by +changing how much white space is used or by changing the order of keys within +objects. + +Signing an object therefore requires it to be encoded as a sequence of bytes +using `Canonical JSON`_, computing the signature for that sequence and then +adding the signature to the original JSON object. + +Canonical JSON +~~~~~~~~~~~~~~ + +We define the canonical JSON encoding for a value to be the shortest UTF-8 JSON +encoding with dictionary keys lexicographically sorted by unicode codepoint. +Numbers in the JSON must be integers in the range ``[-(2**53)+1, (2**53)-1]``. + +We pick UTF-8 as the encoding as it should be available to all platforms and +JSON received from the network is likely to be already encoded using UTF-8. +We sort the keys to give a consistent ordering. We force integers to be in the +range where they can be accurately represented using IEEE double precision +floating point numbers since a number of JSON libraries represent all numbers +using this representation. + +.. code:: python + + import json + + def canonical_json(value): + return json.dumps( + value, + # Encode code-points outside of ASCII as UTF-8 rather than \u escapes + ensure_ascii=False, + # Remove unnecessary white space. + separators=(',',':'), + # Sort the keys of dictionaries. + sort_keys=True, + # Encode the resulting unicode as UTF-8 bytes. + ).encode("UTF-8") + +Grammar ++++++++ + +Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing +insignificant whitespace, fractions, exponents and redundant character escapes + +.. code:: + + value = false / null / true / object / array / number / string + false = %x66.61.6c.73.65 + null = %x6e.75.6c.6c + true = %x74.72.75.65 + object = %x7B [ member *( %x2C member ) ] %7D + member = string %x3A value + array = %x5B [ value *( %x2C value ) ] %5B + number = [ %x2D ] int + int = %x30 / ( %x31-39 *digit ) + digit = %x30-39 + string = %x22 *char %x22 + char = unescaped / %x5C escaped + unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + escaped = %x22 ; " quotation mark U+0022 + / %x5C ; \ reverse solidus U+005C + / %x62 ; b backspace U+0008 + / %x66 ; f form feed U+000C + / %x6E ; n line feed U+000A + / %x72 ; r carriage return U+000D + / %x74 ; t tab U+0009 + / %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X + / %x75.30.30.31 (%x30-39 / %x61-66) ; u001X + +Signing Details +~~~~~~~~~~~~~~~ + +JSON is signed by encoding the JSON object without ``signatures`` or keys grouped +as ``unsigned``, using the canonical encoding described above. The JSON bytes are then signed using the +signature algorithm and the signature is encoded using `unpadded Base64`_. +The resulting base64 signature is added to an object under the +*signing key identifier* which is added to the ``signatures`` object under the +name of the entity signing it which is added back to the original JSON object +along with the ``unsigned`` object. + +The *signing key identifier* is the concatenation of the *signing algorithm* +and a *key identifier*. The *signing algorithm* identifies the algorithm used +to sign the JSON. The currently supported value for *signing algorithm* is +``ed25519`` as implemented by NACL (http://nacl.cr.yp.to/). The *key identifier* +is used to distinguish between different signing keys used by the same entity. + +The ``unsigned`` object and the ``signatures`` object are not covered by the +signature. Therefore intermediate entities can add unsigned data such as +timestamps and additional signatures. + + +.. code:: json + + { + "name": "example.org", + "signing_keys": { + "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" + }, + "unsigned": { + "age_ts": 922834800000 + }, + "signatures": { + "example.org": { + "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw" + } + } + } + +.. code:: python + + def sign_json(json_object, signing_key, signing_name): + signatures = json_object.pop("signatures", {}) + unsigned = json_object.pop("unsigned", None) + + signed = signing_key.sign(encode_canonical_json(json_object)) + signature_base64 = encode_base64(signed.signature) + + key_id = "%s:%s" % (signing_key.alg, signing_key.version) + signatures.setdefault(signing_name, {})[key_id] = signature_base64 + + json_object["signatures"] = signatures + if unsigned is not None: + json_object["unsigned"] = unsigned + + return json_object + +Checking for a Signature +~~~~~~~~~~~~~~~~~~~~~~~~ + +To check if an entity has signed a JSON object an implementation does the +following: + +1. Checks if the ``signatures`` member of the object contains an entry with + the name of the entity. If the entry is missing then the check fails. +2. Removes any *signing key identifiers* from the entry with algorithms it + doesn't understand. If there are no *signing key identifiers* left then the + check fails. +3. Looks up *verification keys* for the remaining *signing key identifiers* + either from a local cache or by consulting a trusted key server. If it + cannot find a *verification key* then the check fails. +4. Decodes the base64 encoded signature bytes. If base64 decoding fails then + the check fails. +5. Removes the ``signatures`` and ``unsigned`` members of the object. +6. Encodes the remainder of the JSON object using the `Canonical JSON`_ + encoding. +7. Checks the signature bytes against the encoded object using the + *verification key*. If this fails then the check fails. Otherwise the check + succeeds. diff --git a/specification/appendices/test_vectors.rst b/specification/appendices/test_vectors.rst new file mode 100644 index 00000000000..e2b8fb588ef --- /dev/null +++ b/specification/appendices/test_vectors.rst @@ -0,0 +1,171 @@ +.. Copyright 2015 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + + +Cryptographic Test Vectors +-------------------------- + +To assist in the development of compatible implementations, the following test +values may be useful for verifying the cryptographic event signing code. + +Signing Key +~~~~~~~~~~~ + +The following test vectors all use the 32-byte value given by the following +Base64-encoded string as the seed for generating the ``ed25519`` signing key: + +.. code:: + + SIGNING_KEY_SEED = decode_base64( + "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1" + ) + +In each case, the server name and key ID are as follows: + +.. code:: + + SERVER_NAME = "domain" + + KEY_ID = "ed25519:1" + +JSON Signing +~~~~~~~~~~~~ + +Given an empty JSON object: + +.. code:: json + + {} + +The JSON signing algorithm should emit the following signed data: + +.. code:: json + + { + "signatures": { + "domain": { + "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" + } + } + } + +Given the following JSON object with data values in it: + +.. code:: json + + { + "one": 1, + "two": "Two" + } + +The JSON signing algorithm should emit the following signed JSON: + +.. code:: json + + { + "one": 1, + "signatures": { + "domain": { + "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw" + } + }, + "two": "Two" + } + +Event Signing +~~~~~~~~~~~~~ + +Given the following minimally-sized event: + +.. code:: json + + { + "event_id": "$0:domain", + "origin": "domain", + "origin_server_ts": 1000000, + "signatures": {}, + "type": "X", + "unsigned": { + "age_ts": 1000000 + } + } + +The event signing algorithm should emit the following signed event: + +.. code:: json + + { + "event_id": "$0:domain", + "hashes": { + "sha256": "6tJjLpXtggfke8UxFhAKg82QVkJzvKOVOOSjUDK4ZSI" + }, + "origin": "domain", + "origin_server_ts": 1000000, + "signatures": { + "domain": { + "ed25519:1": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA" + } + }, + "type": "X", + "unsigned": { + "age_ts": 1000000 + } + } + +Given the following event containing redactable content: + +.. code:: json + + { + "content": { + "body": "Here is the message content", + }, + "event_id": "$0:domain", + "origin": "domain", + "origin_server_ts": 1000000, + "type": "m.room.message", + "room_id": "!r:domain", + "sender": "@u:domain", + "signatures": {}, + "unsigned": { + "age_ts": 1000000 + } + } + +The event signing algorithm should emit the following signed event: + +.. code:: json + + { + "content": { + "body": "Here is the message content", + }, + "event_id": "$0:domain", + "hashes": { + "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g" + }, + "origin": "domain", + "origin_server_ts": 1000000, + "type": "m.room.message", + "room_id": "!r:domain", + "sender": "@u:domain", + "signatures": { + "domain": { + "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA" + } + }, + "unsigned": { + "age_ts": 1000000 + } + } diff --git a/specification/6-appendices.rst b/specification/appendices/threat_model.rst similarity index 83% rename from specification/6-appendices.rst rename to specification/appendices/threat_model.rst index de1ac290560..0dea62e0102 100644 --- a/specification/6-appendices.rst +++ b/specification/appendices/threat_model.rst @@ -1,5 +1,16 @@ -Appendices -========== +.. Copyright 2015 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. Security Threat Model ---------------------- @@ -24,7 +35,7 @@ Threat: Unrecoverable Consistency Violations ++++++++++++++++++++++++++++++++++++++++++++ An attacker could send messages which created an unrecoverable "split-brain" -state in the cluster such that the victim's servers could no longer dervive a +state in the cluster such that the victim's servers could no longer derive a consistent view of the chatroom state. Threat: Bad History @@ -63,7 +74,7 @@ Spoofing An attacker could try to send a message claiming to be from the victim without the victim having sent the message in order to: -* Impersonate the victim while performing illict activity. +* Impersonate the victim while performing illicit activity. * Obtain privileges of the victim. Threat: Altering Message Contents @@ -81,7 +92,7 @@ with a phony "origin" field. Spamming ~~~~~~~~ -The attacker could try to send a high volume of solicicted or unsolicted +The attacker could try to send a high volume of solicited or unsolicited messages to the victim in order to: * Find victims for scams. @@ -127,4 +138,3 @@ Threat: Disclosure to Servers Within Chatroom An attacker could take control of a server within a chatroom to expose message contents or metadata for messages in that room. - diff --git a/specification/3-application_service_api.rst b/specification/application_service_api.rst similarity index 53% rename from specification/3-application_service_api.rst rename to specification/application_service_api.rst index a6e82137e10..b4950eac02f 100644 --- a/specification/3-application_service_api.rst +++ b/specification/application_service_api.rst @@ -1,10 +1,24 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Application Service API ======================= The Matrix client-server API and server-server APIs provide the means to implement a consistent self-contained federated messaging fabric. However, they provide limited means of implementing custom server-side behaviour in Matrix -(e.g. gateways, filters, extensible hooks etc). The Application Service API +(e.g. gateways, filters, extensible hooks etc). The Application Service API (AS API) defines a standard API to allow such extensible functionality to be implemented irrespective of the underlying homeserver implementation. @@ -12,15 +26,28 @@ irrespective of the underlying homeserver implementation. Add in Client-Server services? Overview of bots? Seems weird to be in the spec given it is VERY implementation specific. -Passive Application Services ----------------------------- -"Passive" application services can only observe events from a given home server, -and inject events into a room they are participating in. +.. contents:: Table of Contents +.. sectnum:: + +Specification version +--------------------- + +This version of the specification is generated from +`matrix-doc `_ as of Git commit +`{{git_version}} `_. + +Application Services +-------------------- +Application services are passive and can only observe events from a given +homeserver. They can inject events into rooms they are participating in. They cannot prevent events from being sent, nor can they modify the content of the event being sent. In order to observe events from a homeserver, the homeserver needs to be configured to pass certain types of traffic to the application service. This is achieved by manually configuring the homeserver -with information about the AS. +with information about the application service (AS). + +Registration +~~~~~~~~~~~~ .. NOTE:: Previously, application services could register with a homeserver via HTTP @@ -38,10 +65,34 @@ with information about the AS. A better solution would be to somehow mandate that the API done to avoid abuse. -An example HS configuration required to pass traffic to the AS is: +Application services register "namespaces" of user IDs, room aliases and room IDs. +These namespaces are represented as regular expressions. An application service +is said to be "interested" in a given event if one of the IDs in the event match +the regular expression provided by the application service. An application +service can also state whether they should be the only ones who +can manage a specified namespace. This is referred to as an "exclusive" +namespace. An exclusive namespace prevents humans and other application +services from creating/deleting entities in that namespace. Typically, +exclusive namespaces are used when the rooms represent real rooms on +another service (e.g. IRC). Non-exclusive namespaces are used when the +application service is merely augmenting the room itself (e.g. providing +logging or searching facilities). Namespaces are represented by POSIX extended +regular expressions and look like: + +.. code-block:: yaml + + users: + - exclusive: true + regex: @irc.freenode.net_.* + + +The registration is represented by a series of key-value pairs, which this +specification will present as YAML. An example HS configuration required to pass +traffic to the AS is: .. code-block:: yaml + id: url: as_token: hs_token: @@ -54,205 +105,100 @@ An example HS configuration required to pass traffic to the AS is: aliases: [] # Namespaces of room aliases which should be delegated to the AS rooms: [] # Namespaces of room ids which should be delegated to the AS -- An application service can state whether they should be the only ones who - can manage a specified namespace. This is referred to as an "exclusive" - namespace. An exclusive namespace prevents humans and other application - services from creating/deleting entities in that namespace. Typically, - exclusive namespaces are used when the rooms represent real rooms on - another service (e.g. IRC). Non-exclusive namespaces are used when the - application service is merely augmenting the room itself (e.g. providing - logging or searching facilities). -- Namespaces are represented by POSIX extended regular expressions, - e.g: +.. WARNING:: + If the homeserver in question has multiple application services, each + ``as_token`` and ``id`` MUST be unique per application service as these are + used to identify the application service. The homeserver MUST enforce this. -.. code-block:: yaml - users: - - exclusive: true - regex: @irc.freenode.net_.* - - -Home Server -> Application Service API +Homeserver -> Application Service API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This contains application service APIs which are used by the home server. All -application services MUST implement these APIs. -User Query -++++++++++ +Pushing events +++++++++++++++ -This API is called by the HS to query the existence of a user on the Application -Service's namespace. +The application service API provides a transaction API for sending a list of +events. Each list of events includes a transaction ID, which works as follows: -Inputs: - - User ID - - HS Credentials -Output: - - Whether the user exists. -Side effects: - - User is created on the HS by the AS via CS APIs during the processing of this request. -API called when: - - HS receives an event for an unknown user ID in the AS's namespace, e.g. an - invite event to a room. -Notes: - - When the AS receives this request, if the user exists, it must create the user via - the CS API. - - It can also set arbitrary information about the user (e.g. display name, join rooms, etc) - using the CS API. - - When this setup is complete, the AS should respond to the HS request. This means the AS - blocks the HS until the user is created. - - This is deemed more flexible than alternative methods (e.g. returning a JSON blob with the - user's display name and get the HS to provision the user). -Retry notes: - - The home server cannot respond to the client's request until the response to - this API is obtained from the AS. - - Recommended that home servers try a few times then time out, returning a - 408 Request Timeout to the client. - :: - GET /users/$user_id?access_token=$hs_token - - Returns: - 200 : User is recognised. - 404 : User not found. - 401 : Credentials need to be supplied. - 403 : HS credentials rejected. - - - 200 OK response format - - {} - -Room Alias Query -++++++++++++++++ -This API is called by the HS to query the existence of a room alias on the -Application Service's namespace. + Typical + HS ---> AS : Homeserver sends events with transaction ID T. + <--- : AS sends back 200 OK. -Inputs: - - Room alias - - HS Credentials -Output: - - Whether the room exists. -Side effects: - - Room is created on the HS by the AS via CS APIs during the processing of - this request. -API called when: - - HS receives an event to join a room alias in the AS's namespace. -Notes: - - When the AS receives this request, if the room exists, it must create the room via - the CS API. - - It can also set arbitrary information about the room (e.g. name, topic, etc) - using the CS API. - - It can send messages as other users in order to populate scrollback. - - When this setup is complete, the AS should respond to the HS request. This means the AS - blocks the HS until the room is created and configured. - - This is deemed more flexible than alternative methods (e.g. returning an initial sync - style JSON blob and get the HS to provision the room). It also means that the AS knows - the room ID -> alias mapping. -Retry notes: - - The home server cannot respond to the client's request until the response to - this API is obtained from the AS. - - Recommended that home servers try a few times then time out, returning a - 408 Request Timeout to the client. - -:: + AS ACK Lost + HS ---> AS : Homeserver sends events with transaction ID T. + <-/- : AS 200 OK is lost. + HS ---> AS : Homeserver retries with the same transaction ID of T. + <--- : AS sends back 200 OK. If the AS had processed these events + already, it can NO-OP this request (and it knows if it is the same + events based on the transaction ID). + +The events sent to the application service should be linearised, as if they were +from the event stream. The homeserver MUST maintain a queue of transactions to +send to the AS. If the application service cannot be reached, the homeserver +SHOULD backoff exponentially until the application service is reachable again. +As application services cannot *modify* the events in any way, these requests can +be made without blocking other aspects of the homeserver. Homeservers MUST NOT +alter (e.g. add more) events they were going to send within that transaction ID +on retries, as the AS may have already processed the events. + +Querying +++++++++ - GET /rooms/$room_alias?access_token=$hs_token - - Returns: - 200 : Room is recognised. - 404 : Room not found. - 401 : Credentials need to be supplied. - 403 : HS credentials rejected. - - - 200 OK response format - - {} - -Pushing -+++++++ -This API is called by the HS when the HS wants to push an event (or batch of -events) to the AS. +The application service API includes two querying APIs: for room aliases and for +user IDs. The application service SHOULD create the queried entity if it desires. +During this process, the application service is blocking the homeserver until the +entity is created and configured. If the homeserver does not receive a response +to this request, the homeserver should retry several times before timing out. This +should result in an HTTP status 408 "Request Timeout" on the client which initiated +this request (e.g. to join a room alias). -Inputs: - - HS Credentials - - Event(s) to give to the AS - - HS-generated transaction ID -Output: - - None. +.. admonition:: Rationale -Data flows: + Blocking the homeserver and expecting the application service to create the entity + using the client-server API is simpler and more flexible than alternative methods + such as returning an initial sync style JSON blob and get the HS to provision + the room/user. This also meant that there didn't need to be a "backchannel" to inform + the application service about information about the entity such as room ID to + room alias mappings. -:: - Typical - HS ---> AS : Home server sends events with transaction ID T. - <--- : AS sends back 200 OK. - - AS ACK Lost - HS ---> AS : Home server sends events with transaction ID T. - <-/- : AS 200 OK is lost. - HS ---> AS : Home server retries with the same transaction ID of T. - <--- : AS sends back 200 OK. If the AS had processed these events - already, it can NO-OP this request (and it knows if it is the same - events based on the transacton ID). - - -Retry notes: - - If the HS fails to pass on the events to the AS, it must retry the request. - - Since ASes by definition cannot alter the traffic being passed to it (unlike - say, a Policy Server), these requests can be done in parallel to general HS - processing; the HS doesn't need to block whilst doing this. - - Home servers should use exponential backoff as their retry algorithm. - - Home servers MUST NOT alter (e.g. add more) events they were going to - send within that transaction ID on retries, as the AS may have already - processed the events. - -Ordering notes: - - The events sent to the AS should be linearised, as they are from the event - stream. - - The home server will need to maintain a queue of transactions to send to - the AS. +HTTP APIs ++++++++++ -:: +This contains application service APIs which are used by the homeserver. All +application services MUST implement these APIs. These APIs are defined below. - PUT /transactions/$transaction_id?access_token=$hs_token - - Request format - { - events: [ - ... - ] - } +{{application_service_as_http_api}} -Client-Server v2 API Extensions + +.. _create the user: `sect:asapi-permissions`_ + +Client-Server API Extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Passive application services can utilise a more powerful version of the +Application services can use a more powerful version of the client-server API by identifying itself as an application service to the -home server. +homeserver. Identity assertion ++++++++++++++++++ -The client-server API infers the user ID from the ``access_token`` provided in +The client-server API infers the user ID from the ``access_token`` provided in every request. It would be an annoying amount of book-keeping to maintain tokens for every virtual user. It would be preferable if the application service could use the CS API with its own ``as_token`` instead, and specify the virtual user -they wish to be acting on behalf of. For real users, this would require +they wish to be acting on behalf of. For real users, this would require additional permissions granting the AS permission to masquerade as a matrix user. Inputs: - Application service token (``access_token``) + - User ID in the AS namespace to act as. - Either: - - User ID in the AS namespace to act as. - Or: - - OAuth2 token of real user (which may end up being an access token) Notes: - This will apply on all aspects of the CS API, except for Account Management. - The ``as_token`` is inserted into ``access_token`` which is usually where the - client token is. This is done on purpose to allow application services to + client token is. This is done on purpose to allow application services to reuse client SDKs. :: @@ -262,12 +208,6 @@ Notes: Query Parameters: access_token: The application service token user_id: The desired user ID to act as. - - /path?access_token=$token&user_token=$token - - Query Parameters: - access_token: The application service token - user_token: The token granted to the AS by the real user Timestamp massaging +++++++++++++++++++ @@ -280,7 +220,7 @@ Inputs: - Desired timestamp Notes: - This will only apply when sending events. - + :: /path?access_token=$token&ts=$timestamp @@ -291,7 +231,10 @@ Notes: Server admin style permissions ++++++++++++++++++++++++++++++ -The home server needs to give the application service *full control* over its + +.. _sect:asapi-permissions: + +The homeserver needs to give the application service *full control* over its namespace, both for users and for room aliases. This means that the AS should be able to create/edit/delete any room alias in its namespace, as well as create/delete any user in its namespace. No additional API changes need to be @@ -308,16 +251,16 @@ including the AS token on a ``/register`` request, along with a login type of :: /register?access_token=$as_token - + Content: { type: "m.login.application_service", - user: "" + username: "" } Application services which attempt to create users or aliases *outside* of their defined namespaces will receive an error code ``M_EXCLUSIVE``. Similarly, -normal users who attempt to create users or alises *inside* an application +normal users who attempt to create users or aliases *inside* an application service-defined namespace will receive the same ``M_EXCLUSIVE`` error code, but only if the application service has defined the namespace as ``exclusive``. @@ -356,12 +299,12 @@ at the cost of an extra round trip (of which the response can be cached). Munging the URI would allow clients to apply the mapping locally, but would force user X on service Y to always map to the same munged user ID. Considering the exposed API could just be applying this munging, there is more flexibility if -an API is exposed. +an API is exposed. :: - GET /_matrix/app/v1/user?uri=$url_encoded_uri - + GET /_matrix/app/%CLIENT_MAJOR_VERSION%/user?uri=$url_encoded_uri + Returns 200 OK: { user_id: @@ -370,31 +313,31 @@ an API is exposed. Room Aliases ++++++++++++ We may want to expose some 3P network rooms so Matrix users can join them directly, -e.g. IRC rooms. We don't want to expose every 3P network room though, e.g. mailto, -tel. Rooms which are publicly accessible (e.g. IRC rooms) can be exposed as an alias by -the application service. Private rooms (e.g. sending an email to someone) should not +e.g. IRC rooms. We don't want to expose every 3P network room though, e.g. +``mailto``, ``tel``. Rooms which are publicly accessible (e.g. IRC rooms) can be +exposed as an alias by the application service. Private rooms +(e.g. sending an email to someone) should not be exposed in this way, but should instead operate using normal invite/join semantics. -Therefore, the ID conventions discussed below are only valid for public rooms which +Therefore, the ID conventions discussed below are only valid for public rooms which expose room aliases. Matrix users may wish to join XMPP rooms (e.g. using XEP-0045) or IRC rooms. In both -cases, these rooms can be expressed as URIs. For consistency, these "room" URIs +cases, these rooms can be expressed as URIs. For consistency, these "room" URIs SHOULD be mapped in the same way as "user" URIs. :: - GET /_matrix/app/v1/alias?uri=$url_encoded_uri - + GET /_matrix/app/%CLIENT_MAJOR_VERSION%/alias?uri=$url_encoded_uri + Returns 200 OK: { alias: } - + Event fields ++++++++++++ -We recommend that any gatewayed events should include an ``external_url`` field -in their content to provide a way for Matrix clients to link into the 'native' -client from which the event originated. For instance, this could contain the -message-ID for emails/nntp posts, or a link to a blog comment when gatewaying -blog comment traffic in & out of matrix - +We recommend that any events that originated from a remote network should +include an ``external_url`` field in their content to provide a way for Matrix +clients to link into the 'native' client from which the event originated. +For instance, this could contain the message-ID for emails/nntp posts, or a link +to a blog comment when bridging blog comment traffic in & out of Matrix. diff --git a/specification/client_server_api.rst b/specification/client_server_api.rst new file mode 100644 index 00000000000..ab140641dc9 --- /dev/null +++ b/specification/client_server_api.rst @@ -0,0 +1,1440 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Client-Server API +================= + +The client-server API provides a simple lightweight API to let clients send +messages, control rooms and synchronise conversation history. It is designed to +support both lightweight clients which store no state and lazy-load data from +the server as required - as well as heavyweight clients which maintain a full +local persistent copy of server state. + +.. contents:: Table of Contents +.. sectnum:: + +Changelog +--------- + +.. topic:: Version: %CLIENT_RELEASE_LABEL% +{{client_server_changelog}} + +This version of the specification is generated from +`matrix-doc `_ as of Git commit +`{{git_version}} `_. + +For the full historical changelog, see +https://github.com/matrix-org/matrix-doc/blob/master/changelogs/client_server.rst + +If this is an unstable snapshot, any changes since the last release may be +viewed using ``git log``. + +Other versions of this specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following other versions are also available, in reverse chronological order: + +- `HEAD `_: Includes all changes since the latest versioned release. +- `r0.2.0 `_ +- `r0.1.0 `_ +- `r0.0.1 `_ +- `r0.0.0 `_ +- `Legacy `_: The last draft before the spec was formally released in version r0.0.0. + + +API Standards +------------- + +.. TODO + Need to specify any HMAC or access_token lifetime/ratcheting tricks + We need to specify capability negotiation for extensible transports + +The mandatory baseline for client-server communication in Matrix is exchanging +JSON objects over HTTP APIs. HTTPS is recommended for communication, although +HTTP may be supported as a fallback to support basic +HTTP clients. More efficient optional transports +will in future be supported as optional extensions - e.g. a +packed binary encoding over stream-cipher encrypted TCP socket for +low-bandwidth/low-roundtrip mobile usage. For the default HTTP transport, all +API calls use a Content-Type of ``application/json``. In addition, all strings +MUST be encoded as UTF-8. Clients are authenticated using opaque +``access_token`` strings (see `Client Authentication`_ for details), passed as a +query string parameter on all requests. + +The names of the API endponts for the HTTP transport follow a convention of +using underscores to separate words (for example ``/delete_devices``). The key +names in JSON objects passed over the API also follow this convention. + +.. NOTE:: + There are a few historical exceptions to this rule, such as + ``/createRoom``. A future version of this specification will address the + inconsistency. + + +Any errors which occur at the Matrix API level MUST return a "standard error +response". This is a JSON object which looks like: + +.. code:: json + + { + "errcode": "", + "error": "" + } + +The ``error`` string will be a human-readable error message, usually a sentence +explaining what went wrong. The ``errcode`` string will be a unique string +which can be used to handle an error message e.g. ``M_FORBIDDEN``. These error +codes should have their namespace first in ALL CAPS, followed by a single _ to +ease separating the namespace from the error code. For example, if there was a +custom namespace ``com.mydomain.here``, and a +``FORBIDDEN`` code, the error code should look like +``COM.MYDOMAIN.HERE_FORBIDDEN``. There may be additional keys depending on the +error, but the keys ``error`` and ``errcode`` MUST always be present. + +Some standard error codes are below: + +:``M_FORBIDDEN``: + Forbidden access, e.g. joining a room without permission, failed login. + +:``M_UNKNOWN_TOKEN``: + The access token specified was not recognised. + +:``M_BAD_JSON``: + Request contained valid JSON, but it was malformed in some way, e.g. missing + required keys, invalid values for keys. + +:``M_NOT_JSON``: + Request did not contain valid JSON. + +:``M_NOT_FOUND``: + No resource was found for this request. + +:``M_LIMIT_EXCEEDED``: + Too many requests have been sent in a short period of time. Wait a while then + try again. + +Some requests have unique error codes: + +:``M_USER_IN_USE``: + Encountered when trying to register a user ID which has been taken. + +:``M_INVALID_USERNAME``: + Encountered when trying to register a user ID which is not valid. + +:``M_ROOM_IN_USE``: + Sent when the room alias given to the ``createRoom`` API is already in use. + +:``M_INVALID_ROOM_STATE``: + Sent when the intial state given to the ``createRoom`` API is invalid. + +:``M_BAD_PAGINATION``: + Encountered when specifying bad pagination query parameters. + +:``M_THREEPID_IN_USE``: + Sent when a threepid given to an API cannot be used because the same threepid is already in use. + +:``M_THREEPID_NOT_FOUND``: + Sent when a threepid given to an API cannot be used because no record matching the threepid was found. + +:``M_SERVER_NOT_TRUSTED``: + The client's request used a third party server, eg. ID server, that this server does not trust. + +.. _sect:txn_ids: + +The client-server API typically uses ``HTTP PUT`` to submit requests with a +client-generated transaction identifier. This means that these requests are +idempotent. The scope of a transaction identifier is a particular access token. +It **only** serves to identify new +requests from retransmits. After the request has finished, the ``{txnId}`` +value should be changed (how is not specified; a monotonically increasing +integer is recommended). + +Some API endpoints may allow or require the use of ``POST`` requests without a +transaction ID. Where this is optional, the use of a ``PUT`` request is strongly +recommended. + +{{versions_cs_http_api}} + +Client Authentication +--------------------- + +Most API endpoints require the user to identify themselves by presenting +previously obtained credentials in the form of an ``access_token`` query +parameter. An access token is typically obtained via the `Login`_ or +`Registration`_ processes. + +When credentials are required but missing or invalid, the HTTP call will +return with a status of 401 and the error code, ``M_MISSING_TOKEN`` or +``M_UNKNOWN_TOKEN`` respectively. + +.. NOTE:: + + This specification does not mandate a particular format for the access + token. Clients should treat it as an opaque byte sequence. Servers are free + to choose an appropriate format. Server implementors may like to investigate + `macaroons `_. + +Relationship between access tokens and devices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Client `devices`_ are closely related to access tokens. Matrix servers should +record which device each access token is assigned to, so that subsequent +requests can be handled correctly. + +By default, the `Login`_ and `Registration`_ processes auto-generate a new +``device_id``. A client is also free to generate its own ``device_id`` or, +provided the user remains the same, reuse a device: in ether case the client +should pass the ``device_id`` in the request body. If the client sets the +``device_id``, the server will invalidate any access token previously assigned +to that device. There is therefore at most one active access token assigned to +each device at any one time. + +User-Interactive Authentication API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Overview +<<<<<<<< + +Some API endpoints require authentication that +interacts with the user. The homeserver may provide many different ways of +authenticating, such as user/password auth, login via a social network (OAuth2), +login by confirming a token sent to their email address, etc. This specification +does not define how homeservers should authorise their users but instead +defines the standard interface which implementations should follow so that ANY +client can login to ANY homeserver. + +The process takes the form of one or more 'stages'. At each stage the client +submits a set of data for a given authentication type and awaits a response +from the server, which will either be a final success or a request to perform +an additional stage. This exchange continues until the final success. + +For each endpoint, a server offers one or more 'flows' that the client can use +to authenticate itself. Each flow comprises a series of stages, as described +above. The client is free to choose which flow it follows. When all stages in a +flow are complete, authentication is complete and the API call succeeds. + +User-interactive API in the REST API +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +In the REST API described in this specification, authentication works by the +client and server exchanging JSON dictionaries. The server indicates what +authentication data it requires via the body of an HTTP 401 response, and the +client submits that authentication data via the ``auth`` request parameter. + +A client should first make a request with no ``auth`` parameter [#]_. The +homeserver returns an HTTP 401 response, with a JSON body, as follows: + +.. code:: + + HTTP/1.1 401 Unauthorized + Content-Type: application/json + + { + "flows": [ + { + "stages": [ "example.type.foo", "example.type.bar" ] + }, + { + "stages": [ "example.type.foo", "example.type.baz" ] + } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" + } + +In addition to the ``flows``, this object contains some extra +information: + +params + This section contains any information that the client will need to know in + order to use a given type of authentication. For each authentication type + presented, that type may be present as a key in this dictionary. For example, + the public part of an OAuth client ID could be given here. +session + This is a session identifier that the client must pass back to the home + server, if one is provided, in subsequent attempts to authenticate in the same + API call. + +The client then chooses a flow and attempts to complete one of the stages. It +does this by resubmitting the same request with the addition of an ``auth`` +key in the object that it submits. This dictionary contains a ``type`` key whose +value is the name of the authentication type that the client is attempting to complete. +It must also contain a ``session`` key with the value of the session key given +by the homeserver, if one was given. It also contains other keys dependent on +the auth type being attempted. For example, if the client is attempting to +complete auth type ``example.type.foo``, it might submit something like this: + +.. code:: + + POST /_matrix/client/r0/endpoint HTTP/1.1 + Content-Type: application/json + + { + "a_request_parameter": "something", + "another_request_parameter": "something else", + "auth": { + "type": "example.type.foo", + "session": "xxxxxx", + "example_credential": "verypoorsharedsecret" + } + } + +If the homeserver deems the authentication attempt to be successful but still +requires more stages to be completed, it returns HTTP status 401 along with the +same object as when no authentication was attempted, with the addition of the +``completed`` key which is an array of auth types the client has completed +successfully: + +.. code:: + + HTTP/1.1 401 Unauthorized + Content-Type: application/json + + { + "completed": [ "example.type.foo" ], + "flows": [ + { + "stages": [ "example.type.foo", "example.type.bar" ] + }, + { + "stages": [ "example.type.foo", "example.type.baz" ] + } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" + } + +Individual stages may require more than one request to complete, in which case +the response will be as if the request was unauthenticated with the addition of +any other keys as defined by the auth type. + +If the homeserver decides that an attempt on a stage was unsuccessful, but the +client may make a second attempt, it returns the same HTTP status 401 response +as above, with the addition of the standard ``errcode`` and ``error`` fields +describing the error. For example: + +.. code:: + + HTTP/1.1 401 Unauthorized + Content-Type: application/json + + { + "errcode": "M_FORBIDDEN", + "error": "Invalid password", + "completed": [ "example.type.foo" ], + "flows": [ + { + "stages": [ "example.type.foo", "example.type.bar" ] + }, + { + "stages": [ "example.type.foo", "example.type.baz" ] + } + ], + "params": { + "example.type.baz": { + "example_key": "foobar" + } + }, + "session": "xxxxxx" + } + +If the request fails for a reason other than authentication, the server returns an error +message in the standard format. For example: + +.. code:: + + HTTP/1.1 400 Bad request + Content-Type: application/json + + { + "errcode": "M_EXAMPLE_ERROR", + "error": "Something was wrong" + } + +If the client has completed all stages of a flow, the homeserver performs the +API call and returns the result as normal. + +Some authentication types may be completed by means other than through the +Matrix client, for example, an email confirmation may be completed when the user +clicks on the link in the email. In this case, the client retries the request +with an auth dict containing only the session key. The response to this will be +the same as if the client were attempting to complete an auth state normally, +i.e. the request will either complete or request auth, with the presence or +absence of that auth type in the 'completed' array indicating whether +that stage is complete. + +.. [#] A request to an endpoint that uses User-Interactive Authentication never + succeeds without auth. Homeservers may allow requests that don't require + auth by offering a stage with only the ``m.login.dummy`` auth type, but + they must still give a 401 response to requests with no auth data. + +Example ++++++++ +At a high level, the requests made for an API call completing an auth flow with +three stages will resemble the following diagram:: + + _______________________ + | Stage 0 | + | No auth | + | ___________________ | + | |_Request_1_________| | <-- Returns "session" key which is used throughout. + |_______________________| + | + | + _________V_____________ + | Stage 1 | + | type: "" | + | ___________________ | + | |_Request_1_________| | + |_______________________| + | + | + _________V_____________ + | Stage 2 | + | type: "" | + | ___________________ | + | |_Request_1_________| | + | ___________________ | + | |_Request_2_________| | + | ___________________ | + | |_Request_3_________| | + |_______________________| + | + | + _________V_____________ + | Stage 3 | + | type: "" | + | ___________________ | + | |_Request_1_________| | <-- Returns API response + |_______________________| + + +Authentication types +++++++++++++++++++++ + +This specification defines the following auth types: + - ``m.login.password`` + - ``m.login.recaptcha`` + - ``m.login.oauth2`` + - ``m.login.email.identity`` + - ``m.login.token`` + - ``m.login.dummy`` + +Password-based +<<<<<<<<<<<<<< +:Type: + ``m.login.password`` +:Description: + The client submits a username and secret password, both sent in plain-text. + +To use this authentication type, clients should submit an auth dict as follows: + +.. code:: json + + { + "type": "m.login.password", + "user": "", + "password": "", + "session": "" + } + +Alternatively reply using a 3pid bound to the user's account on the homeserver +using the |/account/3pid|_ API rather then giving the ``user`` explicitly as +follows: + +.. code:: json + + { + "type": "m.login.password", + "medium": "", + "address": "", + "password": "", + "session": "" + } + +In the case that the homeserver does not know about the supplied 3pid, the +homeserver must respond with 403 Forbidden. + +Google ReCaptcha +<<<<<<<<<<<<<<<< +:Type: + ``m.login.recaptcha`` +:Description: + The user completes a Google ReCaptcha 2.0 challenge + +To use this authentication type, clients should submit an auth dict as follows: + +.. code:: json + + { + "type": "m.login.recaptcha", + "response": "", + "session": "" + } + +Token-based +<<<<<<<<<<< +:Type: + ``m.login.token`` +:Description: + The client submits a login token. + +To use this authentication type, clients should submit an auth dict as follows: + +.. code:: json + + { + "type": "m.login.token", + "token": "", + "txn_id": "", + "session": "" + } + +The ``nonce`` should be a random string generated by the client for the +request. The same ``nonce`` should be used if retrying the request. + +There are many ways a client may receive a ``token``, including via an email or +from an existing logged in device. + +The ``txn_id`` may be used by the server to disallow other devices from using +the token, thus providing "single use" tokens while still allowing the device +to retry the request. This would be done by tying the token to the ``txn_id`` +server side, as well as potentially invalidating the token completely once the +device has successfully logged in (e.g. when we receive a request from the +newly provisioned access_token). + +The server must encode the user id in the ``token``. There is therefore no need +for the client to submit a separate username. + +OAuth2-based +<<<<<<<<<<<< +:Type: + ``m.login.oauth2`` +:Description: + Authentication is supported via OAuth2 URLs. This login consists of multiple + requests. +:Parameters: + ``uri``: Authorization Request URI OR service selection URI. Both contain an + encoded ``redirect URI``. + +The homeserver acts as a 'confidential' client for the purposes of OAuth2. If +the uri is a ``service selection URI``, it MUST point to a webpage which prompts +the user to choose which service to authorize with. On selection of a service, +this MUST link through to an ``Authorization Request URI``. If there is only one +service which the homeserver accepts when logging in, this indirection can be +skipped and the "uri" key can be the ``Authorization Request URI``. + +The client then visits the ``Authorization Request URI``, which then shows the +OAuth2 Allow/Deny prompt. Hitting 'Allow' redirects to the ``redirect URI`` with +the auth code. Homeservers can choose any path for the ``redirect URI``. Once +the OAuth flow has completed, the client retries the request with the session +only, as above. + +Email-based (identity server) +<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +:Type: + ``m.login.email.identity`` +:Description: + Authentication is supported by authorising an email address with an identity + server. + +Prior to submitting this, the client should authenticate with an identity +server. After authenticating, the session information should be submitted to +the homeserver. + +To use this authentication type, clients should submit an auth dict as follows: + +.. code:: json + + { + "type": "m.login.email.identity", + "threepidCreds": [ + { + "sid": "", + "client_secret": "", + "id_server": "" + } + ], + "session": "" + } + +Dummy Auth +<<<<<<<<<< +:Type: + ``m.login.dummy`` +:Description: + Dummy authentication always succeeds and requires no extra parameters. Its + purpose is to allow servers to not require any form of User-Interactive + Authentication to perform a request. + +To use this authentication type, clients should submit an auth dict with just +the type and session, if provided: + +.. code:: json + + { + "type": "m.login.dummy", + "session": "" + } + + +Fallback +++++++++ +Clients cannot be expected to be able to know how to process every single login +type. If a client does not know how to handle a given login type, it can direct +the user to a web browser with the URL of a fallback page which will allow the +user to complete that login step out-of-band in their web browser. The URL it +should open is:: + + /_matrix/client/%CLIENT_MAJOR_VERSION%/auth//fallback/web?session= + +Where ``auth type`` is the type name of the stage it is attempting and +``session ID`` is the ID of the session given by the homeserver. + +This MUST return an HTML page which can perform this authentication stage. This +page must use the following JavaScript when the authentication has been +completed: + +.. code:: javascript + + if (window.onAuthDone) { + window.onAuthDone(); + } else if (window.opener && window.opener.postMessage) { + window.opener.postMessage("authDone", "*"); + } + +This allows the client to either arrange for the global function ``onAuthDone`` +to be defined in an embedded browser, or to use the HTML5 `cross-document +messaging `_ API, to receive +a notification that the authentication stage has been completed. + +Once a client receives the notificaton that the authentication stage has been +completed, it should resubmit the request with an auth dict with just the +session ID: + +.. code:: json + + { + "session": "" + } + + +Example +<<<<<<< +A client webapp might use the following javascript to open a popup window which will +handle unknown login types: + +.. code:: javascript + + /** + * Arguments: + * homeserverUrl: the base url of the homeserver (eg "https://matrix.org") + * + * apiEndpoint: the API endpoint being used (eg + * "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password") + * + * loginType: the loginType being attempted (eg "m.login.recaptcha") + * + * sessionID: the session ID given by the homeserver in earlier requests + * + * onComplete: a callback which will be called with the results of the request + */ + function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) { + var popupWindow; + + var eventListener = function(ev) { + // check it's the right message from the right place. + if (ev.data !== "authDone" || ev.origin !== homeserverUrl) { + return; + } + + // close the popup + popupWindow.close(); + window.removeEventListener("message", eventListener); + + // repeat the request + var requestBody = { + auth: { + session: sessionID, + }, + }; + + request({ + method:'POST', url:apiEndpint, json:requestBody, + }, onComplete); + }; + + window.addEventListener("message", eventListener); + + var url = homeserverUrl + + "/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" + + encodeURIComponent(loginType) + + "/fallback/web?session=" + + encodeURIComponent(sessionID); + + + popupWindow = window.open(url); + } + + +Login +~~~~~ + +A client can obtain access tokens using the ``/login`` API. + +Note that this endpoint does `not` currently use the user-interactive +authentication API. + +For a simple username/password login, clients should submit a ``/login`` +request as follows: + +.. code:: json + + { + "type": "m.login.password", + "user": "", + "password": "" + } + +Alternatively, a client can use a 3pid bound to the user's account on the +homeserver using the |/account/3pid|_ API rather then giving the ``user`` +explicitly, as follows: + +.. code:: json + + { + "type": "m.login.password", + "medium": "", + "address": "", + "password": "" + } + +In the case that the homeserver does not know about the supplied 3pid, the +homeserver must respond with ``403 Forbidden``. + +To log in using a login token, clients should submit a ``/login`` request as +follows: + +.. code:: json + + { + "type": "m.login.token", + "token": "" + } + +As with `token-based`_ interactive login, the ``token`` must encode the +user id. In the case that the token is not valid, the homeserver must respond +with ``403 Forbidden`` and an error code of ``M_FORBIDDEN``. + +{{login_cs_http_api}} + +{{logout_cs_http_api}} + +Login Fallback +<<<<<<<<<<<<<< + +If a client does not recognize any or all login flows it can use the fallback +login API:: + + GET /_matrix/static/client/login/ + +This returns an HTML and JavaScript page which can perform the entire login +process. The page will attempt to call the JavaScript function +``window.onLogin`` when login has been successfully completed. + +.. _Registration: + +Account registration and management +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{{registration_cs_http_api}} + +Notes on password management +++++++++++++++++++++++++++++ + +.. WARNING:: + Clients SHOULD enforce that the password provided is suitably complex. The + password SHOULD include a lower-case letter, an upper-case letter, a number + and a symbol and be at a minimum 8 characters in length. Servers MAY reject + weak passwords with an error code ``M_WEAK_PASSWORD``. + + +Adding Account Administrative Contact Information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A homeserver may keep some contact information for administrative use. +This is independent of any information kept by any Identity Servers. + +{{administrative_contact_cs_http_api}} + +Pagination +---------- + +.. NOTE:: + The paths referred to in this section are not actual endpoints. They only + serve as examples to explain how pagination functions. + +Pagination is the process of dividing a dataset into multiple discrete pages. +Matrix makes use of pagination to allow clients to view extremely large datasets. +These datasets are not limited to events in a room (for example clients may want +to paginate a list of rooms in addition to events within those rooms). Regardless +of *what* is being paginated, there is a common underlying API which is used to +to give clients a consistent way of selecting subsets of a potentially changing +dataset. Requests pass in ``from``, ``to``, ``dir`` and ``limit`` parameters +which describe where to read from the stream. ``from`` and ``to`` are opaque +textual 'stream tokens' which describe the current position in the dataset. +The ``dir`` parameter is an enum representing the direction of events to return: +either ``f`` orwards or ``b`` ackwards. The response returns new ``start`` and +``end`` stream token values which can then be passed to subsequent requests to +continue pagination. Not all endpoints will make use of all the parameters +outlined here: see the specific endpoint in question for more information. + +Pagination Request Query Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Query parameters: + from: + $streamtoken - The opaque token to start streaming from. + to: + $streamtoken - The opaque token to end streaming at. Typically, + clients will not know the item of data to end at, so this will usually be + omitted. + limit: + integer - An integer representing the maximum number of items to + return. + dir: + f|b - The direction to return events in. Typically this is ``b`` to paginate + backwards in time. + +'START' and 'END' are placeholder values used in these examples to describe the +start and end of the dataset respectively. + +Unless specified, the default pagination parameters are ``from=START``, +``to=END``, without a limit set. + +For example, if an endpoint had events E1 -> E15. The client wants the last 5 +events and doesn't know any previous events:: + + S E + |-E1-E2-E3-E4-E5-E6-E7-E8-E9-E10-E11-E12-E13-E14-E15-| + | | | + | _____| <--backwards-- | + |__________________ | | ________| + | | | | + GET /somepath?to=START&limit=5&dir=b&from=END + Returns: + E15,E14,E13,E12,E11 + + +Another example: a public room list has rooms R1 -> R17. The client is showing 5 +rooms at a time on screen, and is on page 2. They want to +now show page 3 (rooms R11 -> 15):: + + S E + | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | stream token + |-R1-R2-R3-R4-R5-R6-R7-R8-R9-R10-R11-R12-R13-R14-R15-R16-R17| room + |____________| |________________| + | | + Currently | + viewing | + | + GET /roomslist?from=9&to=END&limit=5 + Returns: R11,R12,R13,R14,R15 + +Note that tokens are treated in an *exclusive*, not inclusive, manner. The end +token from the initial request was '9' which corresponded to R10. When the 2nd +request was made, R10 did not appear again, even though from=9 was specified. If +you know the token, you already have the data. + +Pagination Response +~~~~~~~~~~~~~~~~~~~ + +Responses to pagination requests MUST follow the format:: + + { + "chunk": [ ... , Responses , ... ], + "start" : $streamtoken, + "end" : $streamtoken + } + +Where $streamtoken is an opaque token which can be used in another query to +get the next set of results. The "start" and "end" keys can only be omitted if +the complete dataset is provided in "chunk". + +Filtering +--------- + +Filters can be created on the server and can be passed as as a parameter to APIs +which return events. These filters alter the data returned from those APIs. +Not all APIs accept filters. + +{{filter_cs_http_api}} + +Events +------ + +.. _sect:events: + +The model of conversation history exposed by the client-server API can be +considered as a list of events. The server 'linearises' the +eventually-consistent event graph of events into an 'event stream' at any given +point in time:: + + [E0]->[E1]->[E2]->[E3]->[E4]->[E5] + + + +Types of room events +~~~~~~~~~~~~~~~~~~~~ + +Room events are split into two categories: + +:State Events: + These are events which update the metadata state of the room (e.g. room topic, + room membership etc). State is keyed by a tuple of event ``type`` and a + ``state_key``. State in the room with the same key-tuple will be overwritten. + +:Message events: + These are events which describe transient "once-off" activity in a room: + typically communication such as sending an instant message or setting up a + VoIP call. + +This specification outlines several events, all with the event type prefix +``m.``. (See `Room Events`_ for the m. event specification.) However, +applications may wish to add their own type of event, and this can be achieved +using the REST API detailed in the following sections. If new events are added, +the event ``type`` key SHOULD follow the Java package naming convention, +e.g. ``com.example.myapp.event``. This ensures event types are suitably +namespaced for each application and reduces the risk of clashes. + + +Syncing +~~~~~~~ + +To read events, the intended flow of operation is for clients to first call the +|/sync|_ API without a ``since`` parameter. This returns the most recent +message events for each room, as well as the state of the room at the start of +the returned timeline. The response also includes a ``next_batch`` field, which +should be used as the value of the ``since`` parameter in the next call to +``/sync``. Finally, the response includes, for each room, a ``prev_batch`` +field, which can be passed as a ``start`` parameter to the +|/rooms//messages|_ API to retrieve earlier messages. + +You can visualise the range of events being returned as:: + + [E0]->[E1]->[E2]->[E3]->[E4]->[E5] + ^ ^ + | | + prev_batch: '1-2-3' next_batch: 'a-b-c' + + +Clients then receive new events by "long-polling" the homeserver via the +``/sync`` API, passing the value of the ``next_batch`` field from the response +to the previous call as the ``since`` parameter. The client should also pass a +``timeout`` parameter. The server will then hold open the HTTP connection for a +short period of time waiting for new events, returning early if an event +occurs. Only the ``/sync`` API (and the deprecated ``/events`` API) support +long-polling in this way. + +The response for such an incremental sync can be visualised as:: + + [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6] + ^ ^ + | | + | next_batch: 'x-y-z' + prev_batch: 'a-b-c' + + +Normally, all new events which are visible to the client will appear in the +response to the ``/sync`` API. However, if a large number of events arrive +between calls to ``/sync``, a "limited" timeline is returned, containing only +the most recent message events. A state "delta" is also returned, summarising +any state changes in the omitted part of the timeline. The client may therefore +end up with "gaps" in its knowledge of the message timeline. The client can +fill these gaps using the |/rooms//messages|_ API. This situation +looks like this:: + + | gap | + | <-> | + [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]->[E10] + ^ ^ + | | + prev_batch: 'd-e-f' next_batch: 'u-v-w' + + +.. Warning:: + Events are ordered in this API according to the arrival time of the event on + the homeserver. This can conflict with other APIs which order events based on + their partial ordering in the event graph. This can result in duplicate events + being received (once per distinct API called). Clients SHOULD de-duplicate + events based on the event ID when this happens. + +.. NOTE:: + + The ``/sync`` API returns a ``state`` list which is separate from the + ``timeline``. This ``state`` list allows clients to keep their model of the + room state in sync with that on the server. In the case of an initial + (``since``-less) sync, the ``state`` list represents the complete state of + the room at the **start** of the returned timeline (so in the case of a + recently-created room whose state fits entirely in the ``timeline``, the + ``state`` list will be empty). + + In the case of an incremental sync, the ``state`` list gives a delta + between the state of the room at the ``since`` parameter and that at the + start of the returned ``timeline``. (It will therefore be empty + unless the timeline was ``limited``.) + + In both cases, it should be noted that the events returned in the ``state`` + list did **not** necessarily take place just before the returned + ``timeline``, so clients should not display them to the user in the timeline. + +.. admonition:: Rationale + + An early design of this specification made the ``state`` list represent the + room state at the end of the returned timeline, instead of the start. This + was unsatisfactory because it led to duplication of events between the + ``state`` list and the ``timeline``, but more importantly, it made it + difficult for clients to show the timeline correctly. + + In particular, consider a returned timeline [M0, S1, M2], where M0 and M2 are + both messages sent by the same user, and S1 is a state event where that user + changes their displayname. If the ``state`` list represents the room state at + the end of the timeline, the client must take a copy of the state dictionary, + and *rewind* S1, in order to correctly calculate the display name for M0. + +.. TODO-spec + Do we ever support streaming requests? Why not websockets? + +{{sync_cs_http_api}} + +{{old_sync_cs_http_api}} + + +Getting events for a room +~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are several APIs provided to ``GET`` events for a room: + +{{rooms_cs_http_api}} + +{{message_pagination_cs_http_api}} + +{{room_initial_sync_cs_http_api}} + + +Sending events to a room +~~~~~~~~~~~~~~~~~~~~~~~~ + +{{room_state_cs_http_api}} + + +**Examples** + +Valid requests look like:: + + PUT /rooms/!roomid:domain/state/m.example.event + { "key" : "without a state key" } + + PUT /rooms/!roomid:domain/state/m.another.example.event/foo + { "key" : "with 'foo' as the state key" } + +In contrast, these requests are invalid:: + + POST /rooms/!roomid:domain/state/m.example.event/ + { "key" : "cannot use POST here" } + + PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11 + { "key" : "txnIds are not supported" } + +Care should be taken to avoid setting the wrong ``state key``:: + + PUT /rooms/!roomid:domain/state/m.another.example.event/11 + { "key" : "with '11' as the state key, but was probably intended to be a txnId" } + +The ``state_key`` is often used to store state about individual users, by using +the user ID as the ``state_key`` value. For example:: + + PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com + { "animal" : "cat", "reason": "fluffy" } + +In some cases, there may be no need for a ``state_key``, so it can be omitted:: + + PUT /rooms/!roomid:domain/state/m.room.bgd.color + { "color": "red", "hex": "#ff0000" } + +{{room_send_cs_http_api}} + + +Redactions +~~~~~~~~~~ +Since events are extensible it is possible for malicious users and/or servers +to add keys that are, for example offensive or illegal. Since some events +cannot be simply deleted, e.g. membership events, we instead 'redact' events. +This involves removing all keys from an event that are not required by the +protocol. This stripped down event is thereafter returned anytime a client or +remote server requests it. Redacting an event cannot be undone, allowing server +owners to delete the offending content from the databases. Events that have been +redacted include a ``redacted_because`` key whose value is the event that caused +it to be redacted, which may include a reason. + + +Upon receipt of a redaction event, the server should strip off any keys not in +the following list: + +- ``event_id`` +- ``type`` +- ``room_id`` +- ``sender`` +- ``state_key`` +- ``prev_content`` +- ``content`` + +The content object should also be stripped of all keys, unless it is one of +one of the following event types: + +- ``m.room.member`` allows key ``membership`` +- ``m.room.create`` allows key ``creator`` +- ``m.room.join_rules`` allows key ``join_rule`` +- ``m.room.power_levels`` allows keys ``ban``, ``events``, ``events_default``, + ``kick``, ``redact``, ``state_default``, ``users``, ``users_default``. +- ``m.room.aliases`` allows key ``aliases`` + +The server should add the event causing the redaction to the ``unsigned`` +property of the redacted event, under the ``redacted_because`` key. When a +client receives a redaction event it should change the redacted event in the +same way a server does. + +Events +++++++ + +{{m_room_redaction_event}} + +Client behaviour +++++++++++++++++ + +{{redaction_cs_http_api}} + +Rooms +----- + +Creation +~~~~~~~~ +The homeserver will create an ``m.room.create`` event when a room is created, +which serves as the root of the event graph for this room. This event also has a +``creator`` key which contains the user ID of the room creator. It will also +generate several other events in order to manage permissions in this room. This +includes: + +- ``m.room.power_levels`` : Sets the power levels of users and required power + levels for various actions within the room such as sending events. +- ``m.room.join_rules`` : Whether the room is "invite-only" or not. + +See `Room Events`_ for more information on these events. To create a room, a +client has to use the following API. + +{{create_room_cs_http_api}} + +Room aliases +~~~~~~~~~~~~ + +Servers may host aliases for rooms with human-friendly names. Aliases take the +form ``#friendlyname:server.name``. + +As room aliases are scoped to a particular homeserver domain name, it is +likely that a homeserver will reject attempts to maintain aliases on other +domain names. This specification does not provide a way for homeservers to +send update requests to other servers. However, homeservers MUST handle +``GET`` requests to resolve aliases on other servers; they should do this using +the federation API if necessary. + +Rooms store a *partial* list of room aliases via the ``m.room.aliases`` state +event. This alias list is partial because it cannot guarantee that the alias +list is in any way accurate or up-to-date, as room aliases can point to +different room IDs over time. Crucially, the aliases in this event are +**purely informational** and SHOULD NOT be treated as accurate. They SHOULD +be checked before they are used or shared with another user. If a room +appears to have a room alias of ``#alias:example.com``, this SHOULD be checked +to make sure that the room's ID matches the ``room_id`` returned from the +request. + +{{directory_cs_http_api}} + + +Permissions +~~~~~~~~~~~ +.. NOTE:: + This section is a work in progress. + +Permissions for rooms are done via the concept of power levels - to do any +action in a room a user must have a suitable power level. Power levels are +stored as state events in a given room. The power levels required for operations +and the power levels for users are defined in ``m.room.power_levels``, where +both a default and specific users' power levels can be set. +By default all users have a power level of 0, other than the room creator whose +power level defaults to 100. Users can grant other users increased power levels +up to their own power level. For example, user A with a power level of 50 could +increase the power level of user B to a maximum of level 50. Power levels for +users are tracked per-room even if the user is not present in the room. +The keys contained in ``m.room.power_levels`` determine the levels required for +certain operations such as kicking, banning and sending state events. See +`m.room.power_levels`_ for more information. + +Clients may wish to assign names to particular power levels. A suggested mapping is as follows: +- 0 User +- 50 Moderator +- 100 Admin + + +Room membership +~~~~~~~~~~~~~~~ +Users need to be a member of a room in order to send and receive events in that +room. There are several states in which a user may be, in relation to a room: + +- Unrelated (the user cannot send or receive events in the room) +- Invited (the user has been invited to participate in the room, but is not + yet participating) +- Joined (the user can send and receive events in the room) +- Banned (the user is not allowed to join the room) + +There is an exception to the requirement that a user join a room before sending +events to it: users may send an ``m.room.member`` event to a room with +``content.membership`` set to ``leave`` to reject an invitation if they have +currently been invited to a room but have not joined it. + +Some rooms require that users be invited to it before they can join; others +allow anyone to join. Whether a given room is an "invite-only" room is +determined by the room config key ``m.room.join_rules``. It can have one of the +following values: + +``public`` + This room is free for anyone to join without an invite. + +``invite`` + This room can only be joined if you were invited. + +The allowable state transitions of membership are:: + + /ban + +------------------------------------------------------+ + | | + | +----------------+ +----------------+ | + | | /leave | | | | + | | v v | | + /invite +--------+ +-------+ | | + ------------>| invite |<----------| leave |----+ | | + +--------+ /invite +-------+ | | | + | | ^ | | | + | | | | | | + /join | +---------------+ | | | | + | | /join if | | | | + | | join_rules | | /ban | /unban | + | | public /leave | | | | + v v or | | | | + +------+ /kick | | | | + ------------>| join |-------------------+ | | | + /join +------+ v | | + if | +-----+ | | + join_rules +-------------------------->| ban |-----+ | + public /ban +-----+ | + ^ ^ | + | | | + ----------------------------------------------+ +----------------------+ + /ban + + +Joining rooms ++++++++++++++ + +{{inviting_cs_http_api}} + +{{joining_cs_http_api}} + +Leaving rooms ++++++++++++++ +A user can leave a room to stop receiving events for that room. A user must +have been invited to or have joined the room before they are eligible to leave +the room. Leaving a room to which the user has been invited rejects the invite. +Once a user leaves a room, it will no longer appear in the response to the +|/sync|_ API unless it is explicitly requested via a filter with the +``include_leave`` field set to ``true``. + +Whether or not they actually joined the room, if the room is an "invite-only" +room the user will need to be re-invited before they can re-join the room. + +A user can also forget a room which they have left. Rooms which have been +forgotten will never appear the response to the |/sync|_ API, until the user +re-joins or is re-invited. + +A user may wish to force another user to leave a room. This can be done by +'kicking' the other user. To do so, the user performing the kick MUST have the +required power level. Once a user has been kicked, the behaviour is the same as +if they had left of their own accord. In particular, the user is free to +re-join if the room is not "invite-only". + +{{leaving_cs_http_api}} + +{{kicking_cs_http_api}} + +Banning users in a room ++++++++++++++++++++++++ +A user may decide to ban another user in a room. 'Banning' forces the target +user to leave the room and prevents them from re-joining the room. A banned +user will not be treated as a joined user, and so will not be able to send or +receive events in the room. In order to ban someone, the user performing the +ban MUST have the required power level. To ban a user, a request should be made +to |/rooms//ban|_ with:: + + { + "user_id": "" + "reason": "string: " + } + +Banning a user adjusts the banned member's membership state to ``ban``. +Like with other membership changes, a user can directly adjust the target +member's state, by making a request to +``/rooms//state/m.room.member/``:: + + { + "membership": "ban" + } + +A user must be explicitly unbanned with a request to |/rooms//unban|_ +before they can re-join the room or be re-invited. + +{{banning_cs_http_api}} + + + +Listing rooms +~~~~~~~~~~~~~ + +{{list_public_rooms_cs_http_api}} + +Profiles +-------- + +{{profile_cs_http_api}} + +Events on Change of Profile Information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Because the profile display name and avatar information are likely to be used in +many places of a client's display, changes to these fields cause an automatic +propagation event to occur, informing likely-interested parties of the new +values. This change is conveyed using two separate mechanisms: + +- a ``m.room.member`` event (with a ``join`` membership) is sent to every room + the user is a member of, to update the ``displayname`` and ``avatar_url``. +- a ``m.presence`` presence status update is sent, again containing the new + values of the ``displayname`` and ``avatar_url`` keys, in addition to the + required ``presence`` key containing the current presence state of the user. + +Both of these should be done automatically by the homeserver when a user +successfully changes their display name or avatar URL fields. + +Additionally, when homeservers emit room membership events for their own +users, they should include the display name and avatar URL fields in these +events so that clients already have these details to hand, and do not have to +perform extra round trips to query it. + +Security +-------- + +Rate limiting +~~~~~~~~~~~~~ +Homeservers SHOULD implement rate limiting to reduce the risk of being +overloaded. If a request is refused due to rate limiting, it should return a +standard error response of the form:: + + { + "errcode": "M_LIMIT_EXCEEDED", + "error": "string", + "retry_after_ms": integer (optional) + } + +The ``retry_after_ms`` key SHOULD be included to tell the client how long they +have to wait in milliseconds before they can try again. + +.. TODO-spec + - Surely we should recommend an algorithm for the rate limiting, rather than letting every + homeserver come up with their own idea, causing totally unpredictable performance over + federated rooms? + +.. References + +.. _`macaroon`: http://research.google.com/pubs/pub41892.html +.. _`devices`: ../intro.html#devices + +.. Links through the external API docs are below +.. ============================================= + +.. |/initialSync| replace:: ``/initialSync`` +.. _/initialSync: #get-matrix-client-%CLIENT_MAJOR_VERSION%-initialsync + +.. |/sync| replace:: ``/sync`` +.. _/sync: #get-matrix-client-%CLIENT_MAJOR_VERSION%-sync + +.. |/events| replace:: ``/events`` +.. _/events: #get-matrix-client-%CLIENT_MAJOR_VERSION%-events + +.. |/createRoom| replace:: ``/createRoom`` +.. _/createRoom: #post-matrix-client-%CLIENT_MAJOR_VERSION%-createroom + +.. |/rooms//initialSync| replace:: ``/rooms//initialSync`` +.. _/rooms//initialSync: #get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-initialsync + +.. |/rooms//messages| replace:: ``/rooms//messages`` +.. _/rooms//messages: #get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-messages + +.. |/rooms//members| replace:: ``/rooms//members`` +.. _/rooms//members: #get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-members + +.. |/rooms//state| replace:: ``/rooms//state`` +.. _/rooms//state: #get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-state + +.. |/rooms//send| replace:: ``/rooms//send`` +.. _/rooms//send: #put-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-send-eventtype-txnid + +.. |/rooms//invite| replace:: ``/rooms//invite`` +.. _/rooms//invite: #post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-invite + +.. |/rooms//join| replace:: ``/rooms//join`` +.. _/rooms//join: #post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-join + +.. |/rooms//leave| replace:: ``/rooms//leave`` +.. _/rooms//leave: #post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-leave + +.. |/rooms//ban| replace:: ``/rooms//ban`` +.. _/rooms//ban: #post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-ban + +.. |/rooms//unban| replace:: ``/rooms//unban`` +.. _/rooms//unban: #post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-unban + +.. |/rooms/{roomId}/context/{eventId}| replace:: ``/rooms/{roomId}/context/{eventId}`` +.. _/rooms/{roomId}/context/{eventId}: #get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-context-eventid + +.. |/account/3pid| replace:: ``/account/3pid`` +.. _/account/3pid: #post-matrix-client-%CLIENT_MAJOR_VERSION%-account-3pid + +.. |/user//account_data/| replace:: ``/user//account_data/`` +.. _/user//account_data/: #put-matrix-client-%CLIENT_MAJOR_VERSION%-user-userid-account-data-type + +.. _`Unpadded Base64`: ../appendices.html#unpadded-base64 diff --git a/specification/events.rst b/specification/events.rst new file mode 100644 index 00000000000..91b837c6d8d --- /dev/null +++ b/specification/events.rst @@ -0,0 +1,69 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Event Structure +=============== + +All communication in Matrix is expressed in the form of data objects called +Events. These are the fundamental building blocks common to the client-server, +server-server and application-service APIs, and are described below. + +Note that the structure of these events may be different than those in the +server-server API. + +{{common_event_fields}} + +{{common_room_event_fields}} + +{{common_state_event_fields}} + + +Size limits +----------- + +The total size of any event MUST NOT exceed 65 KB. There are additional +restrictions on sizes per key: + +- ``sender`` MUST NOT exceed 255 bytes (including domain). +- ``room_id`` MUST NOT exceed 255 bytes. +- ``state_key`` MUST NOT exceed 255 bytes. +- ``type`` MUST NOT exceed 255 bytes. +- ``event_id`` MUST NOT exceed 255 bytes. + +Some event types have additional size restrictions which are specified in +the description of the event. Additional keys have no limit other than that +implied by the total 65 KB limit on events. + +Room Events +----------- +.. NOTE:: + This section is a work in progress. + +This specification outlines several standard event types, all of which are +prefixed with ``m.`` + +{{m_room_aliases_event}} + +{{m_room_canonical_alias_event}} + +{{m_room_create_event}} + +{{m_room_join_rules_event}} + +{{m_room_member_event}} + +{{m_room_power_levels_event}} + +{{m_room_redaction_event}} + diff --git a/specification/0-feature_profiles.rst b/specification/feature_profiles.rst similarity index 78% rename from specification/0-feature_profiles.rst rename to specification/feature_profiles.rst index b9f12b74bbd..7fc9696dedf 100644 --- a/specification/0-feature_profiles.rst +++ b/specification/feature_profiles.rst @@ -1,7 +1,21 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Feature Profiles ================ -.. sect:feature-profiles: +.. _sect:feature-profiles: Matrix supports many different kinds of clients: from embedded IoT devices to desktop clients. Not all clients can provide the same feature sets as other @@ -25,12 +39,13 @@ Summary `VoIP`_ Required Required Required Optional Optional `Content Repository`_ Required Required Required Optional Optional `Managing History Visibility`_ Required Required Required Required Optional - `End-to-End Encryption`_ Optional Optional Optional Optional Optional + `Server Side Search`_ Optional Optional Optional Optional Optional + `Server Administration`_ Optional Optional Optional Optional Optional + `Event Context`_ Optional Optional Optional Optional Optional ===================================== ========== ========== ========== ========== ========== *Please see each module for more details on what clients need to implement.* -.. _End-to-End Encryption: `module:e2e`_ .. _Instant Messaging: `module:im`_ .. _Presence: `module:presence`_ .. _Push Notifications: `module:push`_ @@ -39,6 +54,9 @@ Summary .. _VoIP: `module:voip`_ .. _Content Repository: `module:content`_ .. _Managing History Visibility: `module:history-visibility`_ +.. _Server Side Search: `module:search`_ +.. _Server Administration: `module:admin`_ +.. _Event Context: `module:event-context`_ Clients ------- diff --git a/specification/identity_service_api.rst b/specification/identity_service_api.rst new file mode 100644 index 00000000000..fa03e162cb5 --- /dev/null +++ b/specification/identity_service_api.rst @@ -0,0 +1,293 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Identity Service API +==================== + +The Matrix client-server and server-server APIs are largely expressed in Matrix +user identifiers. From time to time, it is useful to refer to users by other +("third-party") identifiers, or "3pid"s, e.g. their email address or phone +number. This identity service specification describes how mappings between +third-party identifiers and Matrix user identifiers can be established, +validated, and used. This description technically may apply to any 3pid, but in +practice has only been applied specifically to email addresses. + +.. contents:: Table of Contents +.. sectnum:: + +Specification version +--------------------- + +This version of the specification is generated from +`matrix-doc `_ as of Git commit +`{{git_version}} `_. + +General principles +------------------ + +The purpose of an identity service is to validate, store, and answer questions +about the identities of users. In particular, it stores associations of the form +"identifier X represents the same user as identifier Y", where identities may +exist on different systems (such as email addresses, phone numbers, +Matrix user IDs, etc). + +The identity service has some private-public keypairs. When asked about an +association, it will sign details of the association with its private key. +Clients may validate the assertions about associations by verifying the signature +with the public key of the identity service. + +In general, identity services are treated as reliable oracles. They do not +necessarily provide evidence that they have validated associations, but claim to +have done so. Establishing the trustworthiness of an individual identity service +is left as an exercise for the client. + +Privacy +------- + +Identity is a privacy-sensitive issue. While the identity service exists to +provide identity information, access should be restricted to avoid leaking +potentially sensitive data. In particular, being able to construct large-scale +connections between identities should be avoided. To this end, in general APIs +should allow a 3pid to be mapped to a Matrix user identity, but not in the other +direction (i.e. one should not be able to get all 3pids associated with a Matrix +user ID, or get all 3pids associated with a 3pid). + +Key management +-------------- + +An identity service has some long-term public-private keypairs. These are named +in a scheme ``algorithm:identifier``, e.g. ``ed25519:0``. When signing an +association, the Matrix standard JSON signing format is used, as specified in +the server-server API specification under the heading "Signing Events". + +In the event of key compromise, the identity service may revoke any of its keys. +An HTTP API is offered to get public keys, and check whether a particular key is +valid. + +The identity server may also keep track of some short-term public-private +keypairs, which may have different usage and lifetime characteristics than the +service's long-term keys. + +{{pubkey_is_http_api}} + +Association Lookup +------------------ + +{{lookup_is_http_api}} + +Establishing Associations +------------------------- + +The flow for creating an association is session-based. + +Within a session, one may prove that one has ownership of a 3pid. +Once this has been established, the user can form an association between that +3pid and a Matrix user ID. Note that this association is only proved one way; +a user can associate *any* Matrix user ID with a validated 3pid, +i.e. I can claim that any email address I own is associated with +@billg:microsoft.com. + +Sessions are time-limited; a session is considered to have been modified when +it was created, and then when a validation is performed within it. A session can +only be checked for validation, and validation can only be performed within a +session, within a 24 hour period since its most recent modification. Any +attempts to perform these actions after the expiry will be rejected, and a new +session should be created and used instead. + +Creating a session +~~~~~~~~~~~~~~~~~~ + +A client makes a call to:: + + POST https://my.id.server:8090/_matrix/identity/api/v1/validate/email/requestToken + + client_secret=monkeys_are_GREAT& + email=foo@bar.com& + send_attempt=1 + +It may also optionally specify next_link. If next_link is specified, when the +validation is completed, the identity service will redirect the user to that +URL. + +This will create a new "session" on the identity service, identified by an +``sid``. + +The identity service will send an email containing a token. If that token is +presented to the identity service in the future, it indicates that that user was +able to read the email for that email address, and so we validate ownership of +the email address. + +We return the ``sid`` generated for this session to the caller, in a JSON object +containing the ``sid`` key. + +If a send_attempt is specified, the server will only send an email if the +send_attempt is a number greater than the most recent one which it has seen (or +if it has never seen one), scoped to that email address + client_secret pair. +This is to avoid repeatedly sending the same email in the case of request +retries between the POSTing user and the identity service. The client should +increment this value if they desire a new email (e.g. a reminder) to be sent. + +Note that Home Servers offer APIs that proxy this API, adding additional +behaviour on top, for example, ``/register/email/requestToken`` is designed +specifically for use when registering an account and therefore will inform +the user if the email address given is already registered on the server. + +Validating ownership of an email +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A user may make either a ``GET`` or a ``POST`` request to +``/_matrix/identity/api/v1/validate/email/submitToken`` with the following +parameters (either as query parameters or URL-encoded POST parameters): +- ``sid`` the sid for the session, generated by the ``requestToken`` call. +- ``client_secret`` the client secret which was supplied to the ``requestToken`` call. +- ``token`` the token generated by the ``requestToken`` call, and emailed to the user. + +If these three values are consistent with a set generated by a ``requestToken`` +call, ownership of the email address is considered to have been validated. This +does not publish any information publicly, or associate the email address with +any Matrix user ID. Specifically, calls to ``/lookup`` will not show a binding. + +Otherwise, an error will be returned. + +Checking non-published 3pid ownership +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A client can check whether ownership of a 3pid was validated by making an +HTTP GET request to ``/_matrix/identity/api/v1/3pid/getValidated3pid``, passing +the ``sid`` and ``client_secret`` as query parameters from the ``requestToken`` +call. + +It will return something of either the form:: + + {"medium": "email", "validated_at": 1457622739026, "address": "foo@bar.com"} + +or:: + + {"errcode": "M_SESSION_NOT_VALIDATED", "error": "This validation session has not yet been completed"} + +If the ``sid`` and ``client_secret`` were not recognised, or were not correct, +an error will be returned. + +Publishing a validated association +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An association between a session and a Matrix user ID can be published by making +a URL-encoded HTTP POST request to ``/_matrix/identity/api/v1/3pid/bind`` with +the following parameters:: + + sid=sid& + client_secret=monkeys_are_GREAT& + mxid=@foo:bar.com + +If the session is still valid, this will publish an association between the +3pids validated on that session and the passed Matrix user ID. Future calls +to ``/lookup`` for any of the session's 3pids will return this association. + +If the 3pid has not yet been validated, the HTTP request will be rejected, and +the association will not be established. + +If the ``sid`` and ``client_secret`` were not recognised, or were not correct, +an error will be returned. + +Invitation Storage +------------------ + +An identity service can store pending invitations to a user's 3pid, which will +be retrieved and can be either notified on or look up when the 3pid is +associated with a Matrix user ID. + +If one makes a ``POST`` request to ``/_matrix/identity/api/v1/store-invite`` with the following URL-encoded POST parameters: + +- ``medium`` (string, required): The literal string ``email``. +- ``address`` (string, required): The email address of the invited user. +- ``room_id`` (string, required): The Matrix room ID to which the user is invited. +- ``sender`` (string, required): The matrix user ID of the inviting user. + +An arbitrary number of other parameters may also be specified. These may be used in the email generation described below. + +The service will look up whether the 3pid is bound to a Matrix user ID. If it is, the request will be rejected with a 400 status code. + +If the medium is something other than the literal string ``email``, the request will be rejected with a 400 status code. + +Otherwise, the service will then generate a random string called ``token``, and an ephemeral public key. + +The service also generates a ``display_name`` for the inviter, which is a redacted version of ``address`` which does not leak the full contents of the ``address``. + +The service records persistently all of the above information. + +It also generates an email containing all of this data, sent to the ``address`` parameter, notifying them of the invitation. + +The response body is then populated as the JSON-encoded dictionary containing the following fields: +- ``token`` (string): The generated token. +- ``public_keys`` ([string]): A list of [server's long-term public key, generated ephemeral public key]. +- ``display_name`` (string): The generated (redacted) display_name. + +At a later point, if the owner of that particular 3pid binds it with a Matrix user ID, the identity server will attempt to make an HTTP POST to the Matrix user's homeserver which looks roughly as below:: + + POST https://bar.com:8448/_matrix/federation/v1/3pid/onbind + Content-Type: application/json + + { + "medium": "email", + "address": "foo@bar.baz", + "mxid": "@alice:example.tld", + "invites": [ + { + "medium": "email", + "address": "foo@bar.baz", + "mxid": "@alice:example.tld", + "room_id": "!something:example.tld", + "sender": "@bob:example.tld", + "signed": { + "mxid": "@alice:example.tld", + "signatures": { + "vector.im": { + "ed25519:0": "somesignature" + } + }, + "token": "sometoken" + } + } + ] + } + +Where the signature is produced using a long-term private key. + +Also, the generated ephemeral public key will be listed as valid on requests to ``/_matrix/identity/api/v1/pubkey/ephemeral/isvalid``. + +Ephemeral invitation signing +---------------------------- + +To aid clients who may not be able to perform crypto themselves, the identity service offers some crypto functionality to help in accepting invitations. +This is less secure than the client doing it itself, but may be useful where this isn't possible. + +The identity service will happily sign invitation details with a request-specified ed25519 private key for you, if you want it to. It takes URL-encoded POST parameters: +- mxid (string, required) +- token (string, required) +- private_key (string, required): The private key, encoded as `Unpadded base64`_. + +It will look up ``token`` which was stored in a call to ``store-invite``, and fetch the sender of the invite. It will then respond with JSON which looks something like:: + + { + "mxid": "@foo:bar.com", + "sender": "@baz:bar.com", + "signatures" { + "my.id.server": { + "ed25519:0": "def987" + } + }, + "token": "abc123" + } + +.. _`Unpadded Base64`: ../appendices.html#unpadded-base64 diff --git a/specification/index.rst b/specification/index.rst new file mode 100644 index 00000000000..ae6611c06e9 --- /dev/null +++ b/specification/index.rst @@ -0,0 +1,56 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Matrix Specification +==================== + +.. Note that this file is specifically unversioned because we don't want to +.. have to add Yet Another version number, and the commentary on what specs we +.. have should hopefully not get complex enough that we need to worry about +.. versioning it. + +Matrix defines a set of open APIs for decentralised communication, suitable for +securely publishing, persisting and subscribing to data over a global open +federation of servers with no single point of control. Uses include Instant Messaging (IM), +Voice over IP (VoIP) signalling, Internet of Things (IoT) communication, and bridging +together existing communication silos - providing the basis of a new open real-time +communication ecosystem. + +`Introduction to Matrix `_ provides a full introduction to Matrix and the spec. + +Matrix APIs +----------- + +The following APIs are documented in this specification: + +{{apis}} + +`Appendices `_ with supplemental information not specific to +one of the above APIs are also available. + +Specification Versions +---------------------- + +The specification for each API is versioned in the form ``rX.Y.Z``. + * A change to ``X`` reflects a breaking change: a client implemented against + ``r1.0.0`` may need changes to work with a server which supports (only) + ``r2.0.0``. + * A change to ``Y`` represents a change which is backwards-compatible for + existing clients, but not necessarily existing servers: a client implemented + against ``r1.1.0`` will work without changes against a server which supports + ``r1.2.0``; but a client which requires ``r1.2.0`` may not work correctly + with a server which implements only ``r1.1.0``. + * A change to ``Z`` represents a change which is backwards-compatible on both + sides. Typically this implies a clarification to the specification, rather + than a change which must be implemented. diff --git a/specification/0-intro.rst b/specification/intro.rst similarity index 53% rename from specification/0-intro.rst rename to specification/intro.rst index 445b32ef28a..1d3dfafe304 100644 --- a/specification/0-intro.rst +++ b/specification/intro.rst @@ -1,33 +1,35 @@ -Matrix Specification -==================== - -Version: {{spec_version}} ------------------------------ -This specification has been generated from -https://github.com/matrix-org/matrix-doc using -https://github.com/matrix-org/matrix-doc/blob/master/scripts/gendoc.py as of -revision ``{{git_version}}`` - https://github.com/matrix-org/matrix-doc/tree/{{git_rev}} - -Changelog -~~~~~~~~~ -{{spec_changelog}} - -For a full changelog, see -https://github.com/matrix-org/matrix-doc/blob/master/CHANGELOG.rst +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. .. contents:: Table of Contents .. sectnum:: +.. Note that this file is specifically unversioned because we don't want to +.. have to add Yet Another version number, and the commentary on what specs we +.. have should hopefully not get complex enough that we need to worry about +.. versioning it. + Introduction -============ +------------ .. WARNING:: The Matrix specification is still evolving: the APIs are not yet frozen - and this document is in places a work in progress or stale. We have made every + and this document is in places a work in progress or stale. We have made every effort to clearly flag areas which are still being finalised. We're publishing it at this point because it's complete enough to be more than useful and provide a canonical reference to how Matrix is evolving. Our end - goal is to mirror WHATWG's `Living Standard - `_. + goal is to mirror WHATWG's `Living Standard + `_. Matrix is a set of open APIs for open-federated Instant Messaging (IM), Voice over IP (VoIP) and Internet of Things (IoT) communication, designed to create @@ -66,6 +68,7 @@ The principles that Matrix attempts to follow are: + Trying to take the best aspects of XMPP, SIP, IRC, SMTP, IMAP and NNTP whilst trying to avoid their failings + The functionality that Matrix provides includes: - Creation and management of fully distributed chat rooms with no @@ -86,36 +89,34 @@ The functionality that Matrix provides includes: + Publishing user public keys for PKI + Mapping of 3PIDs to Matrix IDs + The end goal of Matrix is to be a ubiquitous messaging layer for synchronising arbitrary data between sets of people, devices and services - be that for instant messages, VoIP call setups, or any other objects that need to be -reliably and persistently pushed from A to B in an inter-operable and federated +reliably and persistently pushed from A to B in an interoperable and federated manner. -Overview -======== - Architecture ------------ Matrix defines APIs for synchronising extensible JSON objects known as -``events`` between compatible clients, servers and services. Clients are +"events" between compatible clients, servers and services. Clients are typically messaging/VoIP applications or IoT devices/hubs and communicate by -synchronising communication history with their ``homeserver`` using the -``Client-Server API``. Each homeserver stores the communication history and +synchronising communication history with their "homeserver" using the +"Client-Server API". Each homeserver stores the communication history and account information for all of its clients, and shares data with the wider Matrix ecosystem by synchronising communication history with other homeservers and their clients. Clients typically communicate with each other by emitting events in the -context of a virtual ``room``. Room data is replicated across *all of the +context of a virtual "room". Room data is replicated across *all of the homeservers* whose users are participating in a given room. As such, *no single homeserver has control or ownership over a given room*. Homeservers model communication history as a partially ordered graph of events known as -the room's ``event graph``, which is synchronised with eventual consistency -between the participating servers using the ``Server-Server API``. This process +the room's "event graph", which is synchronised with eventual consistency +between the participating servers using the "Server-Server API". This process of synchronising shared conversation history between homeservers run by -different parties is called ``Federation``. Matrix optimises for the the +different parties is called "Federation". Matrix optimises for the the Availability and Partitioned properties of CAP theorem at the expense of Consistency. @@ -140,7 +141,7 @@ a long-lived GET request. | V | V +------------------+ +------------------+ | |---------( HTTPS )--------->| | - | Home Server | | Home Server | + | homeserver | | homeserver | | |<--------( HTTPS )----------| | +------------------+ Server-Server API +------------------+ History Synchronisation @@ -151,16 +152,42 @@ Users ~~~~~ Each client is associated with a user account, which is identified in Matrix -using a unique "User ID". This ID is namespaced to the home server which +using a unique "user ID". This ID is namespaced to the homeserver which allocated the account and has the form:: @localpart:domain -The ``localpart`` of a user ID may be a user name, or an opaque ID identifying -this user. They are case-insensitive. +See the `Identifier Grammar`_ section for full details of the structure of +user IDs. -.. TODO-spec - - Need to specify precise grammar for Matrix IDs + +Devices +~~~~~~~ + +The Matrix specification has a particular meaning for the term "device". As a +user, I might have several devices: a desktop client, some web browsers, an +Android device, an iPhone, etc. They broadly relate to a real device in the +physical world, but you might have several browsers on a physical device, or +several Matrix client applications on a mobile device, each of which would be +its own device. + +Devices are used primarily to manage the keys used for end-to-end encryption +(each device gets its own copy of the decryption keys), but they also help +users manage their access - for instance, by revoking access to particular +devices. + +When a user first uses a client, it registers itself as a new device. The +longevity of devices might depend on the type of client. A web client will +probably drop all of its state on logout, and create a new device every time +you log in, to ensure that cryptography keys are not leaked to a new user. In +a mobile client, it might be acceptable to reuse the device if a login session +expires, provided the user is the same. + +Devices are identified by a ``device_id``, which is unique within the scope of +a given user. + +A user may assign a human-readable display name to a device, to help them +manage their devices. Events ~~~~~~ @@ -183,9 +210,9 @@ Event Graphs .. _sect:event-graph: Events exchanged in the context of a room are stored in a directed acyclic graph -(DAG) called an ``event graph``. The partial ordering of this graph gives the +(DAG) called an "event graph". The partial ordering of this graph gives the chronological ordering of events within the room. Each event in the graph has a -list of zero or more ``parent`` events, which refer to any preceding events +list of zero or more "parent" events, which refer to any preceding events which have no chronological successor from the perspective of the homeserver which created the event. @@ -213,8 +240,12 @@ which have the form:: There is exactly one room ID for each room. Whilst the room ID does contain a domain, it is simply for globally namespacing room IDs. The room does NOT -reside on the domain specified. Room IDs are not meant to be human readable. -They are case-sensitive. The following conceptual diagram shows an +reside on the domain specified. + +See the `Identifier Grammar`_ section for full details of the structure of +a room ID. + +The following conceptual diagram shows an ``m.room.message`` event being sent to the room ``!qporfwt:matrix.org``:: { @alice:matrix.org } { @bob:domain.com } @@ -227,7 +258,7 @@ They are case-sensitive. The following conceptual diagram shows an | | V | +------------------+ +------------------+ - | Home Server | | Home Server | + | homeserver | | homeserver | | matrix.org | | domain.com | +------------------+ +------------------+ | ^ @@ -239,7 +270,7 @@ They are case-sensitive. The following conceptual diagram shows an PKI signature from matrix.org Transaction-layer metadata PKI Authorization header - + ................................... | Shared Data | | State: | @@ -256,7 +287,7 @@ They are case-sensitive. The following conceptual diagram shows an Federation maintains *shared data structures* per-room between multiple home servers. The data is split into ``message events`` and ``state events``. -Message events: +Message events: These describe transient 'once-off' activity in a room such as an instant messages, VoIP call setups, file transfers, etc. They generally describe communication activity. @@ -287,16 +318,15 @@ Each room can also have multiple "Room Aliases", which look like:: #room_alias:domain -.. TODO - - Need to specify precise grammar for Room Aliases +See the `Identifier Grammar`_ section for full details of the structure of +a room alias. A room alias "points" to a room ID and is the human-readable label by which rooms are publicised and discovered. The room ID the alias is pointing to can -be obtained by visiting the domain specified. They are case-insensitive. Note -that the mapping from a room alias to a room ID is not fixed, and may change -over time to point to a different room ID. For this reason, Clients SHOULD -resolve the room alias to a room ID once and then use that ID on subsequent -requests. +be obtained by visiting the domain specified. Note that the mapping from a room +alias to a room ID is not fixed, and may change over time to point to a +different room ID. For this reason, Clients SHOULD resolve the room alias to a +room ID once and then use that ID on subsequent requests. When resolving a room alias the server will also respond with a list of servers that are in the room that can be used to join via. @@ -318,14 +348,14 @@ that are in the room that can be used to join via. Identity ~~~~~~~~ -Users in Matrix are identified via their matrix user ID (MXID). However, +Users in Matrix are identified via their Matrix user ID. However, existing 3rd party ID namespaces can also be used in order to identify Matrix users. A Matrix "Identity" describes both the user ID and any other existing IDs from third party namespaces *linked* to their account. Matrix users can *link* third-party IDs (3PIDs) such as email addresses, social network accounts and phone numbers to their user ID. Linking 3PIDs creates a mapping from a 3PID to a user ID. This mapping can then be used by Matrix -users in order to discover the MXIDs of their contacts. +users in order to discover the user IDs of their contacts. In order to ensure that the mapping from 3PID to user ID is genuine, a globally federated cluster of trusted "Identity Servers" (IS) are used to verify the 3PID and persist and replicate the mappings. @@ -339,12 +369,9 @@ Profiles ~~~~~~~~ Users may publish arbitrary key/value data associated with their account - such -as a human readable ``display name``, a profile photo URL, contact information +as a human readable display name, a profile photo URL, contact information (email address, phone numbers, website URLs etc). -In Client-Server API v2, profile data is typed using namespaced keys for -interoperability, much like events - e.g. ``m.profile.display_name``. - .. TODO Actually specify the different types of data - e.g. what format are display names allowed to be? @@ -360,113 +387,223 @@ dedicated API. The API is symmetrical to managing Profile data. Would it really be overengineered to use the same API for both profile & private user data, but with different ACLs? -API Standards -------------- -.. TODO - Need to specify any HMAC or access_token lifetime/ratcheting tricks - We need to specify capability negotiation for extensible transports - -The mandatory baseline for communication in Matrix is exchanging JSON objects -over HTTP APIs. HTTPS is mandated as the baseline for server-server -(federation) communication. HTTPS is recommended for client-server -communication, although HTTP may be supported as a fallback to support basic -HTTP clients. More efficient optional transports for client-server -communication will in future be supported as optional extensions - e.g. a -packed binary encoding over stream-cipher encrypted TCP socket for -low-bandwidth/low-roundtrip mobile usage. For the default HTTP transport, all -API calls use a Content-Type of ``application/json``. In addition, all strings -MUST be encoded as UTF-8. Clients are authenticated using opaque -``access_token`` strings (see `Client Authentication`_ for details), passed as a -query string parameter on all requests. - -Any errors which occur at the Matrix API level MUST return a "standard error -response". This is a JSON object which looks like:: - - { - "errcode": "", - "error": "" - } - -The ``error`` string will be a human-readable error message, usually a sentence -explaining what went wrong. The ``errcode`` string will be a unique string -which can be used to handle an error message e.g. ``M_FORBIDDEN``. These error -codes should have their namespace first in ALL CAPS, followed by a single _ to -ease separating the namespace from the error code. For example, if there was a -custom namespace ``com.mydomain.here``, and a -``FORBIDDEN`` code, the error code should look like -``COM.MYDOMAIN.HERE_FORBIDDEN``. There may be additional keys depending on the -error, but the keys ``error`` and ``errcode`` MUST always be present. - -Some standard error codes are below: - -:``M_FORBIDDEN``: - Forbidden access, e.g. joining a room without permission, failed login. - -:``M_UNKNOWN_TOKEN``: - The access token specified was not recognised. - -:``M_BAD_JSON``: - Request contained valid JSON, but it was malformed in some way, e.g. missing - required keys, invalid values for keys. - -:``M_NOT_JSON``: - Request did not contain valid JSON. - -:``M_NOT_FOUND``: - No resource was found for this request. - -:``M_LIMIT_EXCEEDED``: - Too many requests have been sent in a short period of time. Wait a while then - try again. - -Some requests have unique error codes: - -:``M_USER_IN_USE``: - Encountered when trying to register a user ID which has been taken. - -:``M_ROOM_IN_USE``: - Encountered when trying to create a room which has been taken. - -:``M_BAD_PAGINATION``: - Encountered when specifying bad pagination query parameters. - -:``M_LOGIN_EMAIL_URL_NOT_YET``: - Encountered when polling for an email link which has not been clicked yet. - -The C-S API typically uses ``HTTP POST`` to submit requests. This means these -requests are not idempotent. The C-S API also allows ``HTTP PUT`` to make -requests idempotent. In order to use a ``PUT``, paths should be suffixed with -``/{txnId}``. ``{txnId}`` is a unique client-generated transaction ID which -identifies the request, and is scoped to a given Client (identified by that -client's ``access_token``). Crucially, it **only** serves to identify new -requests from retransmits. After the request has finished, the ``{txnId}`` -value should be changed (how is not specified; a monotonically increasing -integer is recommended). It is preferable to use ``HTTP PUT`` to make sure -requests to send messages do not get sent more than once should clients need to -retransmit requests. - -Valid requests look like:: - - POST /some/path/here?access_token=secret - { - "key": "This is a post." - } - - PUT /some/path/here/11?access_token=secret - { - "key": "This is a put with a txnId of 11." - } - -In contrast, these are invalid requests:: - - POST /some/path/here/11?access_token=secret - { - "key": "This is a post, but it has a txnId." - } - - PUT /some/path/here?access_token=secret - { - "key": "This is a put but it is missing a txnId." - } +Identifier Grammar +------------------ + +Server Name +~~~~~~~~~~~ + +A homeserver is uniquely identified by its server name. This value is used in a +number of identifiers, as described below. + +The server name represents the address at which the homeserver in question can +be reached by other homeservers. The complete grammar is:: + + server_name = dns_name [ ":" port] + dns_name = host + port = *DIGIT + +where ``host`` is as defined by `RFC3986, section 3.2.2 +`_. + +Examples of valid server names are: + +* ``matrix.org`` +* ``matrix.org:8888`` +* ``1.2.3.4`` (IPv4 literal) +* ``1.2.3.4:1234`` (IPv4 literal with explicit port) +* ``[1234:5678::abcd]`` (IPv6 literal) +* ``[1234:5678::abcd]:5678`` (IPv6 literal with explicit port) + + +Common Identifier Format +~~~~~~~~~~~~~~~~~~~~~~~~ + +The Matrix protocol uses a common format to assign unique identifiers to a +number of entities, including users, events and rooms. Each identifier takes +the form:: + + &localpart:domain + +where ``&`` represents a 'sigil' character; ``domain`` is the `server name`_ of +the homeserver which allocated the identifier, and ``localpart`` is an +identifier allocated by that homeserver. + +The sigil characters are as follows: + +* ``@``: User ID +* ``!``: Room ID +* ``$``: Event ID +* ``#``: Room alias + +The precise grammar defining the allowable format of an identifier depends on +the type of identifier. + +User Identifiers +++++++++++++++++ + +Users within Matrix are uniquely identified by their Matrix user ID. The user +ID is namespaced to the homeserver which allocated the account and has the +form:: + + @localpart:domain + +The ``localpart`` of a user ID is an opaque identifier for that user. It MUST +NOT be empty, and MUST contain only the characters ``a-z``, ``0-9``, ``.``, +``_``, ``=``, and ``-``. + +The ``domain`` of a user ID is the `server name`_ of the homeserver which +allocated the account. + +The length of a user ID, including the ``@`` sigil and the domain, MUST NOT +exceed 255 characters. + +The complete grammar for a legal user ID is:: + + user_id = "@" user_id_localpart ":" server_name + user_id_localpart = 1*user_id_char + user_id_char = DIGIT + / %x61-7A ; a-z + / "-" / "." / "=" / "_" + +.. admonition:: Rationale + + A number of factors were considered when defining the allowable characters + for a user ID. + + Firstly, we chose to exclude characters outside the basic US-ASCII character + set. User IDs are primarily intended for use as an identifier at the protocol + level, and their use as a human-readable handle is of secondary + benefit. Furthermore, they are useful as a last-resort differentiator between + users with similar display names. Allowing the full unicode character set + would make very difficult for a human to distinguish two similar user IDs. The + limited character set used has the advantage that even a user unfamiliar with + the Latin alphabet should be able to distinguish similar user IDs manually, if + somewhat laboriously. + + We chose to disallow upper-case characters because we do not consider it + valid to have two user IDs which differ only in case: indeed it should be + possible to reach ``@user:matrix.org`` as ``@USER:matrix.org``. However, + user IDs are necessarily used in a number of situations which are inherently + case-sensitive (notably in the ``state_key`` of ``m.room.member`` + events). Forbidding upper-case characters (and requiring homeservers to + downcase usernames when creating user IDs for new users) is a relatively simple + way to ensure that ``@USER:matrix.org`` cannot refer to a different user to + ``@user:matrix.org``. + + Finally, we decided to restrict the allowable punctuation to a very basic set + to ensure that the identifier can be used as-is in as wide a number of + situations as possible, without requiring escaping. For instance, allowing + "%" or "/" would make it harder to use a user ID in a URI. "*" is used as a + wildcard in some APIs (notably the filter API), so it also cannot be a legal + user ID character. + + The length restriction is derived from the limit on the length of the + ``sender`` key on events; since the user ID appears in every event sent by the + user, it is limited to ensure that the user ID does not dominate over the actual + content of the events. + +Matrix user IDs are sometimes informally referred to as MXIDs. + +Historical User IDs +<<<<<<<<<<<<<<<<<<< + +Older versions of this specification were more tolerant of the characters +permitted in user ID localparts. There are currently active users whose user +IDs do not conform to the permitted character set, and a number of rooms whose +history includes events with a ``sender`` which does not conform. In order to +handle these rooms successfully, clients and servers MUST accept user IDs with +localparts from the expanded character set:: + + extended_user_id_char = %x21-7E + +Mapping from other character sets +<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +In certain circumstances it will be desirable to map from a wider character set +onto the limited character set allowed in a user ID localpart. Examples include +a homeserver creating a user ID for a new user based on the username passed to +``/register``, or a bridge mapping user ids from another protocol. + +.. TODO-spec + + We need to better define the mechanism by which homeservers can allow users + to have non-Latin login credentials. The general idea is for clients to pass + the non-Latin in the ``username`` field to ``/register`` and ``/login``, and + the HS then maps it onto the MXID space when turning it into the + fully-qualified ``user_id`` which is returned to the client and used in + events. + +Implementations are free to do this mapping however they choose. Since the user +ID is opaque except to the implementation which created it, the only +requirement is that the implemention can perform the mapping +consistently. However, we suggest the following algorithm: + +1. Encode character strings as UTF-8. + +2. Convert the bytes ``A-Z`` to lower-case. + + * In the case where a bridge must be able to distinguish two different users + with ids which differ only by case, escape upper-case characters by + prefixing with ``_`` before downcasing. For example, ``A`` becomes + ``_a``. Escape a real ``_`` with a second ``_``. + +3. Encode any remaining bytes outside the allowed character set, as well as + ``=``, as their hexadecimal value, prefixed with ``=``. For example, ``#`` + becomes ``=23``; ``á`` becomes ``=c3=a1``. + +.. admonition:: Rationale + + The suggested mapping is an attempt to preserve human-readability of simple + ASCII identifiers (unlike, for example, base-32), whilst still allowing + representation of *any* character (unlike punycode, which provides no way to + encode ASCII punctuation). + + +Room IDs and Event IDs +++++++++++++++++++++++ + +A room has exactly one room ID. A room ID has the format:: + + !opaque_id:domain + +An event has exactly one event ID. An event ID has the format:: + + $opaque_id:domain + +The ``domain`` of a room/event ID is the `server name`_ of the homeserver which +created the room/event. The domain is used only for namespacing to avoid the +risk of clashes of identifiers between different homeservers. There is no +implication that the room or event in question is still available at the +corresponding homeserver. + +Event IDs and Room IDs are case-sensitive. They are not meant to be human +readable. + +.. TODO-spec + What is the grammar for the opaque part? https://matrix.org/jira/browse/SPEC-389 + +Room Aliases +++++++++++++ + +A room may have zero or more aliases. A room alias has the format:: + + #room_alias:domain + +The ``domain`` of a room alias is the `server name`_ of the homeserver which +created the alias. Other servers may contact this homeserver to look up the +alias. + +Room aliases MUST NOT exceed 255 bytes (including the ``#`` sigil and the +domain). + +.. TODO-spec + - Need to specify precise grammar for Room Aliases. https://matrix.org/jira/browse/SPEC-391 + + +License +------- +The Matrix specification is licensed under the `Apache License, Version 2.0 +`_. diff --git a/specification/modules.rst b/specification/modules.rst new file mode 100644 index 00000000000..36f79cfd852 --- /dev/null +++ b/specification/modules.rst @@ -0,0 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Modules +======= + diff --git a/specification/modules/_template.rst b/specification/modules/_template.rst index 9eee98439d7..aa4f93db2d8 100644 --- a/specification/modules/_template.rst +++ b/specification/modules/_template.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Module Heading ============== @@ -31,10 +45,10 @@ Client behaviour ---------------- List any new HTTP endpoints. These endpoints should be documented using Swagger. Once there is Swagger, there will be a template variable based on the name of -the YAML file with the suffix ``_http_api``. You can insert a template for +the YAML file with the suffix ``_cs_http_api``. You can insert a template for swagger docs like so: -{{name-of-yaml-file-without-file-ext_http_api}} +{{name-of-yaml-file-without-file-ext_cs_http_api}} List the steps the client needs to take to correctly process this module. List what data structures the client should be diff --git a/specification/modules/account_data.rst b/specification/modules/account_data.rst new file mode 100644 index 00000000000..1c031ee125e --- /dev/null +++ b/specification/modules/account_data.rst @@ -0,0 +1,41 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Client Config +============= + +.. _module:account_data: + +Clients can store custom config data for their account on their homeserver. +This account data will be synced between different devices and can persist +across installations on a particular device. Users may only view the account +data for their own account + +The account_data may be either global or scoped to a particular rooms. + +Events +------ + +The client recieves the account data as events in the ``account_data`` sections +of a ``/sync``. + +These events can also be received in a ``/events`` response or in the +``account_data`` section of a room in ``/initialSync``. ``m.tag`` +events appearing in ``/events`` will have a ``room_id`` with the room +the tags are for. + +Client Behaviour +---------------- + +{{account_data_cs_http_api}} diff --git a/specification/modules/admin.rst b/specification/modules/admin.rst new file mode 100644 index 00000000000..c4465675ea1 --- /dev/null +++ b/specification/modules/admin.rst @@ -0,0 +1,26 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Server Administration +===================== + +.. _module:admin: + +This module adds capabilities for server administrators to inspect server state +and data. + +Client Behaviour +---------------- + +{{admin_cs_http_api}} diff --git a/specification/modules/anonymous_access.rst b/specification/modules/anonymous_access.rst new file mode 100644 index 00000000000..a6ffbfb6ec6 --- /dev/null +++ b/specification/modules/anonymous_access.rst @@ -0,0 +1,64 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Guest access +================ + +.. _module:guest-access: + +It may be desirable to allow users without a fully registered user account to +ephemerally access Matrix rooms. This module specifies limited ways of doing so. + +Note that this is not currently a complete anonymous access solution; in +particular, it only allows servers to provided anonymous access to rooms in +which they are already participating, and relies on individual homeservers to +adhere to the conventions which this module sets, rather than allowing all +participating homeservers to enforce them. + +Events +------ + +{{m_room_guest_accessibility}} + +Client behaviour +---------------- +A client can register for guest access using the FOO endpoint. From that point +on, they can interact with a limited subset of the existing client-server API, +as if they were a fully registered user, using the access token granted to them +by the server. + +These users are only allowed to make calls in relation to rooms which have the +``m.room.history_visibility`` event set to ``world_readable``. + +The APIs they are allowed to hit are: + +/rooms/{roomId}/messages +/rooms/{roomId}/state +/rooms/{roomId}/state/{eventType}/{stateKey} +/events + +Server behaviour +---------------- +Does the server need to handle any of the new events in a special way (e.g. +typing timeouts, presence). Advice on how to persist events and/or requests are +recommended to aid implementation. Federation-specific logic should be included +here. + +Security considerations +----------------------- +This includes privacy leaks: for example leaking presence info. How do +misbehaving clients or servers impact this module? This section should always be +included, if only to say "we've thought about it but there isn't anything to do +here". + diff --git a/specification/modules/cas_login.rst b/specification/modules/cas_login.rst new file mode 100644 index 00000000000..4c3b826a10c --- /dev/null +++ b/specification/modules/cas_login.rst @@ -0,0 +1,119 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +CAS-based client login +====================== + +.. _module:cas_login: + +`Central Authentication Service +`_ +(CAS) is a web-based single sign-on protocol. + +An overview of the process, as used in Matrix, is as follows: + +1. The Matrix client instructs the user's browser to navigate to the + |/login/cas/redirect|_ endpoint on the user's homeserver. + +2. The homeserver responds with an HTTP redirect to the CAS user interface, + which the browser follows. + +3. The CAS system authenticates the user. + +4. The CAS server responds to the user's browser with a redirect back to the + |/login/cas/ticket|_ endpoint on the homeserver, which the browser + follows. A 'ticket' identifier is passed as a query parameter in the + redirect. + +5. The homeserver receives the ticket ID from the user's browser, and makes a + request to the CAS server to validate the ticket. + +6. Having validated the ticket, the homeserver responds to the browser with a + third HTTP redirect, back to the Matrix client application. A login token + is passed as a query parameter in the redirect. + +7. The Matrix client receives the login token and passes it to the |/login|_ + API. + +Client behaviour +---------------- + +The client starts the process by instructing the browser to navigate to +|/login/cas/redirect|_ with an appropriate ``redirectUrl``. Once authentication +is successful, the browser will be redirected to that ``redirectUrl``. + +.. TODO-spec + + Should we recommend some sort of CSRF protection here (specifically, we + should guard against people accidentally logging in by sending them a link + to ``/login/cas/redirect``. + + Maybe we should recommend that the ``redirectUrl`` should contain a CSRF + token which the client should then check before sending the login token to + ``/login``? + +{{cas_login_redirect_cs_http_api}} +{{cas_login_ticket_cs_http_api}} + +Server behaviour +---------------- + +The URI for the CAS system to be used should be configured on the server by the +server administrator. + +Handling the redirect endpoint +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When responding to the ``/login/cas/redirect`` endpoint, the server must +generate a URI for the CAS login page. The server should take the base CAS URI +described above, and add a ``service`` query parameter. This parameter MUST be +the URI of the ``/login/cas/ticket`` endpoint, including the ``redirectUrl`` +query parameter. Because the homeserver may not know its base URI, this may +also require manual configuration. + +.. TODO-spec: + + It might be nice if the server did some validation of the ``redirectUrl`` + parameter, so that we could check that aren't going to redirect to a non-TLS + endpoint, and to give more meaningful errors in the case of + faulty/poorly-configured clients. + +Handling the authentication endpoint +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When responding to the ``/login/cas/ticket`` endpoint, the server MUST make a +request to the CAS server to validate the provided ticket. The server MAY also +check for certain user attributes in the response. Any required attributes +should be configured by the server administrator. + +Once the ticket has been validated, the server MUST map the CAS ``user_id`` +to a valid `Matrix user identifier <../intro.html#user-identifiers>`_. The +guidance in `Mapping from other character sets +<../intro.html#mapping-from-other-character-sets>`_ may be useful. + +If the generated user identifier represents a new user, it should be registered +as a new user. + +Finally, the server should generate a short-term login token. The generated +token should be a macaroon, suitable for use with the ``m.login.token`` type of +the |/login|_ API, and `token-based interactive login <#token-based>`_. The +lifetime of this token SHOULD be limited to around five seconds. + + +.. |/login| replace:: ``/login`` +.. _/login: #post-matrix-client-%CLIENT_MAJOR_VERSION%-login +.. |/login/cas/redirect| replace:: ``/login/cas/redirect`` +.. _/login/cas/redirect: #get-matrix-client-%CLIENT_MAJOR_VERSION%-login-cas-redirect +.. |/login/cas/ticket| replace:: ``/login/cas/ticket`` +.. _/login/cas/ticket: #get-matrix-client-%CLIENT_MAJOR_VERSION%-login-cas-ticket diff --git a/specification/modules/content_repo.rst b/specification/modules/content_repo.rst index 9ac5e1991df..0f1a9944fce 100644 --- a/specification/modules/content_repo.rst +++ b/specification/modules/content_repo.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Content repository ================== @@ -24,7 +38,7 @@ Client behaviour Clients can upload and download content using the following HTTP APIs. -{{content_repo_http_api}} +{{content_repo_cs_http_api}} Thumbnails ~~~~~~~~~~ @@ -36,6 +50,10 @@ width and height are close to the requested size and the aspect matches the requested size. The client should scale the image if it needs to fit within a given rectangle. +In summary: + * "scale" maintains the original aspect ratio of the image + * "crop" provides an image in the aspect ratio of the sizes given in the request + Server behaviour ---------------- @@ -52,23 +70,32 @@ The HTTP GET endpoint does not require any authentication. Knowing the URL of the content is sufficient to retrieve the content, even if the entity isn't in the room. -Homeservers have additional concerns: +MXC URIs are vulnerable to directory traversal attacks such as +``mxc://127.0.0.1/../../../some_service/etc/passwd``. This would cause the target +homeserver to try to access and return this file. As such, homeservers MUST +sanitise MXC URIs by allowing only alphanumeric (``A-Za-z0-9``), ``_`` +and ``-`` characters in the ``server-name`` and ``media-id`` values. This set +of whitelisted characters allows URL-safe base64 encodings specified in RFC 4648. +Applying this character whitelist is preferable to blacklisting ``.`` and ``/`` +as there are techniques around blacklisted characters (percent-encoded characters, +UTF-8 encoded traversals, etc). - - Clients may try to upload very large files. Homeservers should not store files - that are too large and should not serve them to clients. +Homeservers have additional content-specific concerns: - - Clients may try to upload very large images. Homeservers should not attempt to - generate thumbnails for images that are too large. +- Clients may try to upload very large files. Homeservers should not store files + that are too large and should not serve them to clients. - - Remote homeservers may host very large files or images. Homeservers should not - proxy or thumbnail large files or images from remote homeservers. +- Clients may try to upload very large images. Homeservers should not attempt to + generate thumbnails for images that are too large. - - Clients may try to upload a large number of files. Homeservers should limit the - number and total size of media that can be uploaded by clients. +- Remote homeservers may host very large files or images. Homeservers should not + proxy or thumbnail large files or images from remote homeservers. - - Clients may try to access a large number of remote files through a homeserver. - Homeservers should restrict the number and size of remote files that it caches. +- Clients may try to upload a large number of files. Homeservers should limit the + number and total size of media that can be uploaded by clients. - - Clients or remote homeservers may try to upload malicious files targeting - vulnerabilities in either the homeserver thumbnailing or the client decoders. +- Clients may try to access a large number of remote files through a homeserver. + Homeservers should restrict the number and size of remote files that it caches. +- Clients or remote homeservers may try to upload malicious files targeting + vulnerabilities in either the homeserver thumbnailing or the client decoders. diff --git a/specification/modules/device_management.rst b/specification/modules/device_management.rst new file mode 100644 index 00000000000..3f9c84529bf --- /dev/null +++ b/specification/modules/device_management.rst @@ -0,0 +1,41 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Device Management +================= + +.. _module:device-management: + +This module provides a means for a user to manage their `devices`_. + +Client behaviour +---------------- +Clients that implement this module should offer the user a list of registered +devices, as well as the means to update their display names. Clients should +also allow users to delete disused devices. + +{{device_management_cs_http_api}} + +Security considerations +----------------------- + +Deleting devices has security implications: it invalidates the access_token +assigned to the device, so an attacker could use it to log out the real user +(and do it repeatedly every time the real user tries to log in to block the +attacker). Servers should require additional authentication beyond the access +token when deleting devices (for example, requiring that the user resubmit +their password). + +The display names of devices are publicly visible. Clients should consider +advising the user of this. diff --git a/specification/modules/dm.rst b/specification/modules/dm.rst new file mode 100644 index 00000000000..a89d3522aca --- /dev/null +++ b/specification/modules/dm.rst @@ -0,0 +1,58 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Direct Messaging +================ + +.. _module:dm: + +All communication over Matrix happens within a room. It is sometimes +desirable to offer users the concept of speaking directly to one +particular person. This module defines a way of marking certain rooms +as 'direct chats' with a given person. This does not restrict the chat +to being between exactly two people since this would preclude the +presence of automated 'bot' users or even a 'personal assistant' who is +able to answer direct messages on behalf of the user in their absence. + +A room may not necessarily be considered 'direct' by all members of the +room, but a signalling mechanism exists to propagate the information of +whether a chat is 'direct' to an invitee. + +Events +------ + +{{m_direct_event}} + +Client behaviour +---------------- +To start a direct chat with another user, the inviting user's client +should set the ``is_direct`` flag to |/createRoom|_. The client should do +this whenever the flow the user has followed is one where their +intention is to speak directly with another person, as opposed to bringing that +person in to a shared room. For example, clicking on 'Start Chat' beside a +person's profile picture would imply the ``is_direct`` flag should be set. + +The invitee's client may use the ``is_direct`` flag in the `m.room.member`_ +event to automatically mark the room as a direct chat but this is not +required: it may for example, prompt the user, or ignore the flag altogether. + +Both the inviting client and the invitee's client should record the fact that +the room is a direct chat by storing an ``m.direct`` event in the account data +using |/user//account_data/|_. + +Server behaviour +---------------- +When the ``is_direct`` flag is given to |/createRoom|_, the home +server must set the ``is_direct`` flag in the invite member event for any users +invited in the |/createRoom|_ call. diff --git a/specification/modules/end_to_end_encryption.rst b/specification/modules/end_to_end_encryption.rst index e3a526136a4..1f778bc2c7b 100644 --- a/specification/modules/end_to_end_encryption.rst +++ b/specification/modules/end_to_end_encryption.rst @@ -1,17 +1,306 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + End-to-End Encryption ===================== .. _module:e2e: -.. TODO-doc - - Why is this needed. - - Overview of process - - Implementation - Matrix optionally supports end-to-end encryption, allowing rooms to be created whose conversation contents is not decryptable or interceptable on any of the participating homeservers. -End-to-end crypto is still being designed and prototyped - notes on the design -may be found at https://lwn.net/Articles/634144/ +.. WARNING:: + + End to end encryption is being worked on and will be coming soon. This + section is incomplete. You can read more about what's underway at + http://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#end-to-end-encryption. + +Key Distribution +---------------- +Encryption and Authentication in Matrix is based around public-key +cryptography. The Matrix protocol provides a basic mechanism for exchange of +public keys, though an out-of-band channel is required to exchange fingerprints +between users to build a web of trust. + +Overview +~~~~~~~~ + +.. code:: + + 1) Bob publishes the public keys and supported algorithms for his + device. This may include long-term identity keys, and/or one-time + keys. + + +----------+ +--------------+ + | Bob's HS | | Bob's Device | + +----------+ +--------------+ + | | + |<=============| + /keys/upload + + 2) Alice requests Bob's public identity keys and supported algorithms. + + +----------------+ +------------+ +----------+ + | Alice's Device | | Alice's HS | | Bob's HS | + +----------------+ +------------+ +----------+ + | | | + |=================>|==============>| + /keys/query + + 3) Alice selects an algorithm and claims any one-time keys needed. + + +----------------+ +------------+ +----------+ + | Alice's Device | | Alice's HS | | Bob's HS | + +----------------+ +------------+ +----------+ + | | | + |=================>|==============>| + /keys/claim + + +Key algorithms +~~~~~~~~~~~~~~ + +The name ``ed25519`` corresponds to the `Ed25519`_ signature algorithm. The key +is a 32-byte Ed25519 public key, encoded using `unpadded Base64`_. Example: + +.. code:: json + + "SogYyrkTldLz0BXP+GYWs0qaYacUI0RleEqNT8J3riQ" + +The name ``curve25519`` corresponds to the `Curve25519`_ ECDH algorithm. The +key is a 32-byte Curve25519 public key, encoded using `unpadded +Base64`_. Example: + +.. code:: json + + "JGLn/yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y" + +The name ``signed_curve25519`` also corresponds to the Curve25519 algorithm, +but keys using this algorithm are objects with the properties ``key`` (giving +the Base64-encoded 32-byte Curve25519 public key), and ``signatures`` (giving a +signature for the key object, as described in `Signing JSON`_). Example: + +.. code:: json + + { + "key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8", + "signatures": { + "@user:example.com": { + "ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ" + } + } + } + +Device keys +~~~~~~~~~~~ + +Each device should have one Ed25519 signing key. This key should be generated +on the device from a cryptographically secure source, and the private part of +the key should never be exported from the device. This key is used as the +fingerprint for a device by other clients. + +A device will generally need to generate a number of additional keys. Details +of these will vary depending on the messaging algorithm in use. + +Algorithms generally require device identity keys as well as signing keys. Some +algorithms also require one-time keys to improve their secrecy and deniability. +These keys are used once during session establishment, and are then thrown +away. + +For Olm version 1, each device requires a single Curve25519 identity key, and a +number of signed Curve25519 one-time keys. + +Uploading keys +~~~~~~~~~~~~~~ + +A device uploads the public parts of identity keys to their homeserver as a +signed JSON object, using the |/keys/upload|_ API. +The JSON object must include the public part of the device's Ed25519 key, and +must be signed by that key, as described in `Signing JSON`_. + +One-time keys are also uploaded to the homeserver using the |/keys/upload|_ +API. + +Devices must store the private part of each key they upload. They can +discard the private part of a one-time key when they receive a message using +that key. However it's possible that a one-time key given out by a homeserver +will never be used, so the device that generates the key will never know that +it can discard the key. Therefore a device could end up trying to store too +many private keys. A device that is trying to store too many private keys may +discard keys starting with the oldest. + +Tracking the device list for a user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before Alice can send an encrypted message to Bob, she needs a list of each of +his devices and the associated identity keys, so that she can establish an +encryption session with each device. This list can be obtained by calling +|/keys/query|_, passing Bob's user ID in the ``device_keys`` parameter. + +From time to time, Bob may add new devices, and Alice will need to know this so +that she can include his new devices for later encrypted messages. A naive +solution to this would be to call |/keys/query|_ before sending each message - +however, the number of users and devices may be large and this would be +inefficient. + +It is therefore expected that each client will maintain a list of devices for a +number of users (in practice, typically each user with whom we share an +encrypted room). Furthermore, it is likely that this list will need to be +persisted between invocations of the client application (to preserve device +verification data and to alert Alice if Bob suddently gets a new +device). + +Alice's client can maintain a list of Bob's devices via the following +process: + +#. It first sets a flag to record that it is now tracking Bob's device list, + and a separate flag to indicate that its list of Bob's devices is + outdated. Both flags should be in storage which persists over client + restarts. + +#. It then makes a request to |/keys/query|_, passing Bob's user ID in the + ``device_keys`` parameter. When the request completes, it stores the + resulting list of devices in persistent storage, and clears the 'outdated' + flag. + +#. During its normal processing of responses to |/sync|_, Alice's client + inspects the |device_lists|_ field. If it is tracking the device lists of + any of the listed users, then it marks the device lists for those users + outdated, and initiates another request to |/keys/query|_ for them. + +#. Periodically, Alice's client stores the ``next_batch`` field of the result + from |/sync|_ in persistent storage. If Alice later restarts her client, it + can obtain a list of the users who have updated their device list while it + was offline by calling |/keys/changes|_, passing the recorded ``next_batch`` + field as the ``from`` parameter. If the client is tracking the device list + of any of the users listed in the response, it marks them as outdated. It + combines this list with those already flagged as outdated, and initiates a + |/keys/query|_ requests for all of them. + +.. Warning:: + + Bob may update one of his devices while Alice has a request to + ``/keys/query`` in flight. Alice's client may therefore see Bob's user ID in + the ``device_lists`` field of the ``/sync`` response while the first request + is in flight, and initiate a second request to ``/keys/query``. This may + lead to either of two related problems. + + The first problem is that, when the first request completes, the client will + clear the 'outdated' flag for Bob's devices. If the second request fails, or + the client is shut down before it completes, this could lead to Alice using + an outdated list of Bob's devices. + + The second possibility is that, under certain conditions, the second request + may complete *before* the first one. When the first request completes, the + client could overwrite the later results from the second request with those + from the first request. + + Clients MUST guard against these situations. For example, a client could + ensure that only one request to ``/keys/query`` is in flight at a time for + each user, by queuing additional requests until the first completes. + Alternatively, the client could make a new request immediately, but ensure + that the first request's results are ignored (possibly by cancelling the + request). + +.. |device_lists| replace:: ``device_lists`` +.. _`device_lists`: `device_lists_sync`_ + +Claiming one-time keys +~~~~~~~~~~~~~~~~~~~~~~ + +A client wanting to set up a session with another device can claim a one-time +key for that device. This is done by making a request to the |/keys/claim|_ +API. + +A homeserver should rate-limit the number of one-time keys that a given user or +remote server can claim. A homeserver should discard the public part of a one +time key once it has given that key to another user. + + +Protocol definitions +-------------------- + +{{keys_cs_http_api}} + + +.. anchor for link from /sync api spec +.. |device_lists_sync| replace:: End-to-end encryption +.. _device_lists_sync: + +Extensions to /sync +~~~~~~~~~~~~~~~~~~~ + +This module adds an optional ``device_lists`` property to the |/sync|_ +response, as specified below. The server need only populate this property for +an incremental ``/sync`` (ie, one where the ``since`` parameter was +specified). The client is expected to use |/keys/query|_ or |/keys/changes|_ +for the equivalent functionality after an initial sync, as documented in +`Tracking the device list for a user`_. + +.. todo: generate this from a swagger definition? + +.. device_lists: { changed: ["@user:server", ... ]}, + +============ =========== ===================================================== +Parameter Type Description +============ =========== ===================================================== +device_lists DeviceLists Optional. Information on e2e device updates. Note: + only present on an incremental sync. +============ =========== ===================================================== + +``DeviceLists`` + +========= ========= ============================================= +Parameter Type Description +========= ========= ============================================= +changed [string] List of users who have updated their device identity keys + since the previous sync response. +========= ========= ============================================= + + +Example response: + +.. code:: json + + { + "next_batch": "s72595_4483_1934", + "rooms": {"leave": {}, "join": {}, "invite": {}}, + "device_lists": { + "changed": [ + "@alice:example.com", + ], + }, + } + +.. References + +.. _ed25519: http://ed25519.cr.yp.to/ +.. _curve25519: https://cr.yp.to/ecdh.html + +.. _`Signing JSON`: ../appendices.html#signing-json + +.. |m.olm.v1.curve25519-aes-sha2| replace:: ``m.olm.v1.curve25519-aes-sha2`` + +.. |/keys/upload| replace:: ``/keys/upload`` +.. _/keys/upload: #post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-upload + +.. |/keys/query| replace:: ``/keys/query`` +.. _/keys/query: #post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-query + +.. |/keys/claim| replace:: ``/keys/claim`` +.. _/keys/claim: #post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-claim +.. |/keys/changes| replace:: ``/keys/changes`` +.. _/keys/changes: #get-matrix-client-%CLIENT_MAJOR_VERSION%-keys-changes diff --git a/specification/modules/event_context.rst b/specification/modules/event_context.rst new file mode 100644 index 00000000000..2d5f22b14c2 --- /dev/null +++ b/specification/modules/event_context.rst @@ -0,0 +1,33 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Event Context +============= + +.. _module:event-context: + +This API returns a number of events that happened just before and after the +specified event. This allows clients to get the context surrounding an event. + +Client behaviour +---------------- + +There is a single HTTP API for retrieving event context, documented below. + +{{event_context_cs_http_api}} + +Security considerations +----------------------- + +The server must only return results that the user has permission to see. diff --git a/specification/modules/guest_access.rst b/specification/modules/guest_access.rst new file mode 100644 index 00000000000..4e04aa0df91 --- /dev/null +++ b/specification/modules/guest_access.rst @@ -0,0 +1,100 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Guest Access +============ + +.. _module:guest-access: + +There are times when it is desirable for clients to be able to interact with +rooms without having to fully register for an account on a homeserver or join +the room. This module specifies how these clients should interact with servers +in order to participate in rooms as guests. + +Guest users retrieve access tokens from a homeserver using the ordinary +`register endpoint <#post-matrix-client-%CLIENT_MAJOR_VERSION%-register>`_, specifying +the ``kind`` parameter as ``guest``. They may then interact with the +client-server API as any other user would, but will only have access to a subset +of the API as described the Client behaviour subsection below. +Homeservers may choose not to allow this access at all to their local users, but +have no information about whether users on other homeservers are guests or not. + +Guest users can also upgrade their account by going through the ordinary +``register`` flow, but specifying the additional POST parameter +``guest_access_token`` containing the guest's access token. They are also +required to specify the ``username`` parameter to the value of the local part of +their username, which is otherwise optional. + +This module does not fully factor in federation; it relies on individual +homeservers properly adhering to the rules set out in this module, rather than +allowing all homeservers to enforce the rules on each other. + +Events +------ +{{m_room_guest_access_event}} + +Client behaviour +---------------- +The following API endpoints are allowed to be accessed by guest accounts for +retrieving events: + +* `GET /rooms/:room_id/state <#get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-state>`_ +* `GET /rooms/:room_id/state/:event_type/:state_key <#get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-state-eventtype-statekey>`_ +* `GET /rooms/:room_id/messages <#get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-messages>`_ +* `GET /rooms/:room_id/initialSync <#get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-initialsync>`_ +* `GET /sync <#get-matrix-client-%CLIENT_MAJOR_VERSION%-sync>`_ +* `GET /events`__ as used for room previews. + +__ `peeking_events_api`_ + +The following API endpoints are allowed to be accessed by guest accounts for +sending events: + +* `POST /rooms/:room_id/join <#post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-join>`_ +* `POST /rooms/:room_id/leave <#post-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-leave>`_ +* `PUT /rooms/:room_id/send/m.room.message/:txn_id <#put-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-send-eventtype-txnid>`_ +* `PUT /sendToDevice/{eventType}/{txnId} <#put-matrix-client-%CLIENT_MAJOR_VERSION%-sendtodevice-eventtype-txnid>`_ + +The following API endpoints are allowed to be accessed by guest accounts for +their own account maintenance: + +* `PUT /profile/:user_id/displayname <#put-matrix-client-%CLIENT_MAJOR_VERSION%-profile-userid-displayname>`_ +* `GET /devices <#get-matrix-client-%CLIENT_MAJOR_VERSION%-devices>`_ +* `GET /devices/{deviceId} <#get-matrix-client-%CLIENT_MAJOR_VERSION%-devices-deviceid>`_ +* `PUT /devices/{deviceId} <#put-matrix-client-%CLIENT_MAJOR_VERSION%-devices-deviceid>`_ + +The following API endpoints are allowed to be accessed by guest accounts for +end-to-end encryption: + +* `POST /keys/upload <#post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-upload>`_ +* `POST /keys/query <#post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-query>`_ +* `POST /keys/claim <#post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-claim>`_ + +Server behaviour +---------------- +Servers MUST only allow guest users to join rooms if the ``m.room.guest_access`` +state event is present on the room, and has the ``guest_access`` value +``can_join``. If the ``m.room.guest_access`` event is changed to stop this from +being the case, the server MUST set those users' ``m.room.member`` state to +``leave``. + +Security considerations +----------------------- +Each homeserver manages its own guest accounts itself, and whether an account +is a guest account or not is not information passed from server to server. +Accordingly, any server participating in a room is trusted to properly enforce +the permissions outlined in this section. + +Homeservers may want to enable protections such as captchas for guest +registration to prevent spam, denial of service, and similar attacks. diff --git a/specification/modules/history_visibility.rst b/specification/modules/history_visibility.rst index 371282bd8d9..476ea889fad 100644 --- a/specification/modules/history_visibility.rst +++ b/specification/modules/history_visibility.rst @@ -1,26 +1,101 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Room History Visibility ------------------------ +======================= .. _module:history-visibility: -Whether a member of a room can see the events that happened in a room from -before they joined the room is controlled by the ``history_visibility`` key -of the ``m.room.history_visibility`` state event. The valid values for -``history_visibility`` are: +This module adds support for controlling the visibility of previous events in a +room. + +In all cases except ``world_readable``, a user needs to join a room to view events in that room. Once they +have joined a room, they will gain access to a subset of events in the room. How +this subset is chosen is controlled by the ``m.room.history_visibility`` event +outlined below. After a user has left a room, they may see any events which they +were allowed to see before they left the room, but no events received after they +left. + +The four options for the ``m.room.history_visibility`` event are: + +- ``world_readable`` - All events while this is the + ``m.room.history_visibility`` value may be shared by any participating + homeserver with anyone, regardless of whether they have ever joined the room. +- ``shared`` - Previous events are always accessible to newly joined members. All + events in the room are accessible, even those sent when the member was not a part + of the room. +- ``invited`` - Events are accessible to newly joined members from the point + they were invited onwards. Events stop being accessible when the member's state + changes to something other than ``invite`` or ``join``. +- ``joined`` - Events are accessible to newly joined members from the point + they joined the room onwards. Events stop being accessible when the member's state + changes to something other than ``join``. + +.. WARNING:: + These options are applied at the point an event is *sent*. Checks are + performed with the state of the ``m.room.history_visibility`` event when the + event in question is added to the DAG. This means clients cannot + retrospectively choose to show or hide history to new users if the setting at + that time was more restrictive. + +Events +------ + +{{m_room_history_visibility_event}} -- ``shared`` -- ``invited`` -- ``joined`` +Client behaviour +---------------- -By default if no ``history_visibility`` is set it is assumed to be ``shared``. +Clients that implement this module MUST present to the user the possible options +for setting history visibility when creating a room. -The rules governing whether a user is allowed to see an event depend solely on -the state of the room at that event: +Clients may want to display a notice that their events may be read by non-joined +people if the value is set to ``world_readable``. + +Server behaviour +---------------- + +By default if no ``history_visibility`` is set, or if the value is not understood, the visibility is assumed to be +``shared``. The rules governing whether a user is allowed to see an event depend +on the state of the room *at that event*. + +1. If the ``history_visibility`` was set to ``world_readable``, allow. +2. If the user's ``membership`` was ``join``, allow. +3. If ``history_visibility`` was set to ``shared``, and the user joined the + room at any point after the event was sent, allow. +4. If the user's ``membership`` was ``invite``, and the ``history_visibility`` + was set to ``invited``, allow. +5. Otherwise, deny. + +For ``m.room.history_visibility`` events themselves, the user should be allowed +to see the event if the ``history_visibility`` before *or* after the event +would allow them to see it. (For example, a user should be able to see +``m.room.history_visibility`` events which change the ``history_visibility`` +from ``world_readable`` to ``joined`` *or* from ``joineded`` to +``world_readable``, even if that user was not a member of the room.) + +Likewise, for the user's own ``m.room.member`` events, the user should be +allowed to see the event if their ``membership`` before *or* after the event +would allow them to see it. (For example, a user can always see +``m.room.member`` events which set their membership to ``join``, or which +change their membership from ``join`` to any other value, even if +``history_visibility`` is ``joined``.) + +Security considerations +----------------------- -1. If the user was joined, allow. -2. If the user was invited and the ``history_visibility`` was set to - ``invited`` or ``shared``, allow. -3. If the user was neither invited nor joined but the ``history_visibility`` - was set to ``shared``, allow. -4. Otherwise, deny. +The default value for ``history_visibility`` is ``shared`` for +backwards-compatibility reasons. Clients need to be aware that by not setting +this event they are exposing all of their room history to anyone in the room. diff --git a/specification/modules/instant_messaging.rst b/specification/modules/instant_messaging.rst index 43a06aa1374..342050a7fe7 100644 --- a/specification/modules/instant_messaging.rst +++ b/specification/modules/instant_messaging.rst @@ -1,8 +1,26 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Instant Messaging ================= .. _module:im: +This module adds support for sending human-readable messages to a room. It also +adds support for associating human-readable information with the room itself +such as a room name and topic. + Events ------ @@ -10,20 +28,270 @@ Events {{m_room_message_feedback_event}} +Usage of this event is discouraged for several reasons: + - The number of feedback events will grow very quickly with the number of users + in the room. This event provides no way to "batch" feedback, unlike the + `receipts module`_. + - Pairing feedback to messages gets complicated when paginating as feedback + arrives before the message it is acknowledging. + - There are no guarantees that the client has seen the event ID being + acknowledged. + + +.. _`receipts module`: `module:receipts`_ + {{m_room_name_event}} {{m_room_topic_event}} -m.room.message msgtypes ------------------------ - -.. TODO-spec - How a client should handle unknown message types. +{{m_room_avatar_event}} +m.room.message msgtypes +~~~~~~~~~~~~~~~~~~~~~~~ Each `m.room.message`_ MUST have a ``msgtype`` key which identifies the type of message being sent. Each type has their own required and optional keys, as -outlined below. +outlined below. If a client cannot display the given ``msgtype`` then it SHOULD +display the fallback plain text ``body`` key instead. {{msgtype_events}} + +Client behaviour +---------------- + +Clients SHOULD verify the structure of incoming events to ensure that the +expected keys exist and that they are of the right type. Clients can discard +malformed events or display a placeholder message to the user. Redacted +``m.room.message`` events MUST be removed from the client. This can either be +replaced with placeholder text (e.g. "[REDACTED]") or the redacted message can +be removed entirely from the messages view. + +Events which have attachments (e.g. ``m.image``, ``m.file``) SHOULD be +uploaded using the `content repository module`_ where available. The +resulting ``mxc://`` URI can then be used in the ``url`` key. + +Clients MAY include a client generated thumbnail image for an attachment under +a ``info.thumbnail_url`` key. The thumbnail SHOULD also be a ``mxc://`` URI. +Clients displaying events with attachments can either use the client generated +thumbnail or ask its homeserver to generate a thumbnail from the original +attachment using the `content repository module`_. + +.. _`content repository module`: `module:content`_ + +Recommendations when sending messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the event of send failure, clients SHOULD retry requests using an +exponential-backoff algorithm for a +certain amount of time T. It is recommended that T is no longer than 5 minutes. +After this time, the client should stop retrying and mark the message as "unsent". +Users should be able to manually resend unsent messages. + +Users may type several messages at once and send them all in quick succession. +Clients SHOULD preserve the order in which they were sent by the user. This +means that clients should wait for the response to the previous request before +sending the next request. This can lead to head-of-line blocking. In order to +reduce the impact of head-of-line blocking, clients should use a queue per room +rather than a global queue, as ordering is only relevant within a single room +rather than between rooms. + +Local echo +~~~~~~~~~~ + +Messages SHOULD appear immediately in the message view when a user presses the +"send" button. This should occur even if the message is still sending. This is +referred to as "local echo". Clients SHOULD implement "local echo" of messages. +Clients MAY display messages in a different format to indicate that the server +has not processed the message. This format should be removed when the server +responds. + +Clients need to be able to match the message they are sending with the same +message which they receive from the event stream. The echo of the same message +from the event stream is referred to as "remote echo". Both echoes need to be +identified as the same message in order to prevent duplicate messages being +displayed. Ideally this pairing would occur transparently to the user: the UI +would not flicker as it transitions from local to remote. Flickering cannot be +fully avoided in the current client-server API. Two scenarios need to be +considered: + +- The client sends a message and the remote echo arrives on the event stream + *after* the request to send the message completes. +- The client sends a message and the remote echo arrives on the event stream + *before* the request to send the message completes. + +In the first scenario, the client will receive an event ID when the request to +send the message completes. This ID can be used to identify the duplicate event +when it arrives on the event stream. However, in the second scenario, the event +arrives before the client has obtained an event ID. This makes it impossible to +identify it as a duplicate event. This results in the client displaying the +message twice for a fraction of a second before the the original request to send +the message completes. Once it completes, the client can take remedial actions +to remove the duplicate event by looking for duplicate event IDs. A future version +of the client-server API will resolve this by attaching the transaction ID of the +sending request to the event itself. + + +Calculating the display name for a user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Clients may wish to show the human-readable display name of a room member as +part of a membership list, or when they send a message. However, different +members may have conflicting display names. Display names MUST be disambiguated +before showing them to the user, in order to prevent spoofing of other users. + +To ensure this is done consistently across clients, clients SHOULD use the +following algorithm to calculate a disambiguated display name for a given user: + +1. Inspect the ``m.room.member`` state event for the relevant user id. +2. If the ``m.room.member`` state event has no ``displayname`` field, or if + that field has a ``null`` value, use the raw user id as the display + name. Otherwise: +3. If the ``m.room.member`` event has a ``displayname`` which is unique among + members of the room with ``membership: join`` or ``membership: invite``, use + the given ``displayname`` as the user-visible display name. Otherwise: +4. The ``m.room.member`` event has a non-unique ``displayname``. This should be + disambiguated using the user id, for example "display name + (@id:homeserver.org)". + + .. TODO-spec + what does it mean for a ``displayname`` to be 'unique'? Are we + case-sensitive? Do we care about homograph attacks? See + https://matrix.org/jira/browse/SPEC-221. + +Developers should take note of the following when implementing the above +algorithm: + +* The user-visible display name of one member can be affected by changes in the + state of another member. For example, if ``@user1:matrix.org`` is present in + a room, with ``displayname: Alice``, then when ``@user2:example.com`` joins + the room, also with ``displayname: Alice``, *both* users must be given + disambiguated display names. Similarly, when one of the users then changes + their display name, there is no longer a clash, and *both* users can be given + their chosen display name. Clients should be alert to this possibility and + ensure that all affected users are correctly renamed. + +* The display name of a room may also be affected by changes in the membership + list. This is due to the room name sometimes being based on user display + names (see `Calculating the display name for a room`_). + +* If the entire membership list is searched for clashing display names, this + leads to an O(N^2) implementation for building the list of room members. This + will be very inefficient for rooms with large numbers of members. It is + recommended that client implementations maintain a hash table mapping from + ``displayname`` to a list of room members using that name. Such a table can + then be used for efficient calculation of whether disambiguation is needed. + + +Displaying membership information with messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Clients may wish to show the display name and avatar URL of the room member who +sent a message. This can be achieved by inspecting the ``m.room.member`` state +event for that user ID (see `Calculating the display name for a user`_). + +When a user paginates the message history, clients may wish to show the +**historical** display name and avatar URL for a room member. This is possible +because older ``m.room.member`` events are returned when paginating. This can +be implemented efficiently by keeping two sets of room state: old and current. +As new events arrive and/or the user paginates back in time, these two sets of +state diverge from each other. New events update the current state and paginated +events update the old state. When paginated events are processed sequentially, +the old state represents the state of the room *at the time the event was sent*. +This can then be used to set the historical display name and avatar URL. + + +Calculating the display name for a room +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Clients may wish to show a human-readable name for a room. There are a number +of possibilities for choosing a useful name. To ensure that rooms are named +consistently across clients, clients SHOULD use the following algorithm to +choose a name: + +1. If the room has an `m.room.name`_ state event with a non-empty ``name`` + field, use the name given by that field. + +#. If the room has an `m.room.canonical_alias`_ state event with a non-empty + ``alias`` field, use the alias given by that field as the name. + +#. If neither of the above conditions are met, a name should be composed based + on the members of the room. Clients should consider `m.room.member`_ events + for users other than the logged-in user, with ``membership: join`` or + ``membership: invite``. + + .. _active_members: + + i. If there is only one such event, the display name for the room should be + the `disambiguated display name`_ of the corresponding user. + + #. If there are two such events, they should be lexicographically sorted by + their ``state_key`` (i.e. the corresponding user IDs), and the display + name for the room should be the `disambiguated display name`_ of both + users: " and ", or a localised variant thereof. + + #. If there are three or more such events, the display name for the room + should be based on the disambiguated display name of the user + corresponding to the first such event, under a lexicographical sorting + according to their ``state_key``. The display name should be in the + format " and others" (or a localised variant thereof), where N + is the number of `m.room.member`_ events with ``membership: join`` or + ``membership: invite``, excluding the logged-in user and "user1". + + For example, if Alice joins a room, where Bob (whose user id is + ``@superuser:example.com``), Carol (user id ``@carol:example.com``) and + Dan (user id ``@dan:matrix.org``) are in conversation, Alice's + client should show the room name as "Carol and 2 others". + + .. TODO-spec + Sorting by user_id certainly isn't ideal, as IDs at the start of the + alphabet will end up dominating room names: they will all be called + "Arathorn and 15 others". Furthermore - user_ids are not necessarily + ASCII, which means we need to either specify a collation order, or specify + how to choose one. + + Ideally we might sort by the time when the user was first invited to, or + first joined the room. But we don't have this information. + + See https://matrix.org/jira/browse/SPEC-267 for further discussion. + +#. If the room has no valid ``m.room.name`` or ``m.room.canonical_alias`` + event, and no active members other than the current user, clients should + consider ``m.room.member`` events with ``membership: leave``. If such events + exist, a display name such as "Empty room (was and others)" (or + a localised variant thereof) should be used, following similar rules as for + active members (see `above `_). + +#. A complete absence of room name, canonical alias, and room members is likely + to indicate a problem with creating the room or synchronising the state + table; however clients should still handle this situation. A display name + such as "Empty room" (or a localised variant thereof) should be used in this + situation. + +.. _`disambiguated display name`: `Calculating the display name for a user`_ + +Clients SHOULD NOT use `m.room.aliases`_ events as a source for room names, as +it is difficult for clients to agree on the best alias to use, and aliases can +change unexpectedly. + +.. TODO-spec + How can we make this less painful for clients to implement, without forcing + an English-language implementation on them all? See + https://matrix.org/jira/browse/SPEC-425. + +Server behaviour +---------------- + +Homeservers SHOULD reject ``m.room.message`` events which don't have a +``msgtype`` key, or which don't have a textual ``body`` key, with an HTTP status +code of 400. + +Security considerations +----------------------- + +Messages sent using this module are not encrypted, although end to end encryption is in development (see `E2E module`_). + +Clients should sanitise **all displayed keys** for unsafe HTML to prevent Cross-Site +Scripting (XSS) attacks. This includes room names and topics. + +.. _`E2E module`: `module:e2e`_ diff --git a/specification/modules/presence.rst b/specification/modules/presence.rst index 79151c4fe62..822b298d405 100644 --- a/specification/modules/presence.rst +++ b/specification/modules/presence.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Presence ======== @@ -5,10 +19,10 @@ Presence Each user has the concept of presence information. This encodes: - * Whether the user is currently online - * How recently the user was last active (as seen by the server) - * Whether a given client considers the user to be currently idle - * Arbitrary information about the user's current status (e.g. "in a meeting"). +* Whether the user is currently online +* How recently the user was last active (as seen by the server) +* Whether a given client considers the user to be currently idle +* Arbitrary information about the user's current status (e.g. "in a meeting"). This information is collated from both per-device (``online``, ``idle``, ``last_active``) and per-user (status) data, aggregated by the user's homeserver @@ -20,18 +34,16 @@ membership of a room. A presence list is a list of user IDs whose presence the user wants to follow. To be added to this list, the user being added must be invited by the list owner who must accept the invitation. - + User's presence state is represented by the ``presence`` key, which is an enum of one of the following: - - ``online`` : The default state when the user is connected to an event - stream. - - ``unavailable`` : The user is not reachable at this time e.g. they are - idle. - - ``offline`` : The user is not connected to an event stream or is - explicitly suppressing their profile information from being sent. - - ``free_for_chat`` : The user is generally willing to receive messages - moreso than default. +- ``online`` : The default state when the user is connected to an event + stream. +- ``unavailable`` : The user is not reachable at this time e.g. they are + idle. +- ``offline`` : The user is not connected to an event stream or is + explicitly suppressing their profile information from being sent. Events ------ @@ -44,69 +56,46 @@ Client behaviour Clients can manually set/get their presence/presence list using the HTTP APIs listed below. -{{presence_http_api}} - -Idle timeout -~~~~~~~~~~~~ - -Clients SHOULD implement an "idle timeout". This is a timer which fires after -a period of inactivity on the client. The definition of inactivity varies -depending on the client. For example, web implementations may determine -inactivity to be not moving the mouse for a certain period of time. When this -timer fires it should set the presence state to ``unavailable``. When the user -becomes active again (e.g. by moving the mouse) the client should set the -presence state to ``online``. A timeout value between 1 and 5 minutes is -recommended. +{{presence_cs_http_api}} Server behaviour ---------------- -Each user's home server stores a "presence list" per user. Once a user accepts +Each user's homeserver stores a "presence list" per user. Once a user accepts a presence list, both user's HSes must track the subscription. -Propagating profile information -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Because the profile display name and avatar information are likely to be used in -many places of a client's display, changes to these fields SHOULD cause an -automatic propagation event to occur, informing likely-interested parties of the -new values. One of these change mechanisms SHOULD be via ``m.presence`` events. -These events should set ``displayname`` and ``avatar_url`` to the new values -along with the presence-specific keys. This SHOULD be done automatically by the -home server when a user successfully changes their display name or avatar URL. - -.. admonition:: Rationale - - The intention for sending this information in ``m.presence`` is so that any - "user list" can display the *current* name/presence for a user ID outside the - scope of a room e.g. for a user page. This is bundled into a single event for - several reasons. The user's display name can change per room. This - event provides the "canonical" name for the user. In addition, the name is - bundled into a single event for the ease of client implementations. If this - was not done, the client would need to search all rooms for their own - membership event to pull out the display name. - - Last active ago ~~~~~~~~~~~~~~~ -The server maintains a timestamp of the last time it saw a -pro-active event from the user. A pro-active event may be sending a message to a -room or changing presence state to a higher level of availability. Levels of -availability are defined from low to high as follows: +The server maintains a timestamp of the last time it saw a pro-active event from +the user. A pro-active event may be sending a message to a room or changing +presence state to ``online``. This timestamp is presented via a key called +``last_active_ago`` which gives the relative number of milliseconds since the +pro-active event. + +To reduce the number of presence updates sent to clients the server may include +a ``currently_active`` boolean field when the presence state is ``online``. When +true, the server will not send further updates to the last active time until an +update is sent to the client with either a) ``currently_active`` set to false or +b) a presence state other than ``online``. During this period clients must +consider the user to be currently active, irrespective of the last active time. + +The last active time must be up to date whenever the server gives a presence +event to the client. The ``currently_active`` mechanism should purely be used by +servers to stop sending continuous presence updates, as opposed to disabling +last active tracking entirely. Thus clients can fetch up to date last active +times by explicitly requesting the presence for a given user. - - ``offline`` - - ``unavailable`` - - ``online`` - - ``free_for_chat`` +Idle timeout +~~~~~~~~~~~~ -Based on this list, changing state from ``unavailable`` to ``online`` counts as -a pro-active event, whereas ``online`` to ``unavailable`` does not. This -timestamp is presented via a key called ``last_active_ago`` which gives the -relative number of milliseconds since the pro-active event. +The server will automatically set a user's presence to ``unavailable`` if their +last active time was over a threshold value (e.g. 5 minutes). Clients can +manually set a user's presence to ``unavailable``. Any activity that bumps the +last active time on any of the user's clients will cause the server to +automatically set their presence to ``online``. Security considerations ----------------------- - + Presence information is shared with all users who share a room with the target user. In large public rooms this could be undesirable. - diff --git a/specification/modules/push.rst b/specification/modules/push.rst index 6e9c8536a8b..9bb65b9656d 100644 --- a/specification/modules/push.rst +++ b/specification/modules/push.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Push Notifications ================== @@ -11,7 +25,7 @@ Push Notifications | | | | +-------------------+ | +----------------+ | | +---------------+ | | | | | | | | | | | - | Matrix Home Server+-----> Push Gateway +------> Push Provider | | + | Matrix homeserver +-----> Push Gateway +------> Push Provider | | | | | | | | | | | | +-^-----------------+ | +----------------+ | | +----+----------+ | | | | | | | @@ -37,19 +51,19 @@ The above diagram shows the flow of push notifications being sent to a handset where push notifications are submitted via the handset vendor, such as Apple's APNS or Google's GCM. This happens as follows: - 1. The client app signs in to a homeserver. - 2. The client app registers with its vendor's Push Provider and - obtains a routing token of some kind. - 3. The mobile app uses the Client/Server API to add a 'pusher', providing the - URL of a specific Push Gateway which is configured for that - application. It also provides the routing token it has acquired from the - Push Provider. - 4. The homeserver starts sending HTTP requests to the Push Gateway using the - supplied URL. The Push Gateway relays this notification to - the Push Provider, passing the routing token along with any - necessary private credentials the provider requires to send push - notifications. - 5. The Push Provider sends the notification to the device. +1. The client app signs in to a homeserver. +2. The client app registers with its vendor's Push Provider and + obtains a routing token of some kind. +3. The mobile app uses the Client/Server API to add a 'pusher', providing the + URL of a specific Push Gateway which is configured for that + application. It also provides the routing token it has acquired from the + Push Provider. +4. The homeserver starts sending HTTP requests to the Push Gateway using the + supplied URL. The Push Gateway relays this notification to + the Push Provider, passing the routing token along with any + necessary private credentials the provider requires to send push + notifications. +5. The Push Provider sends the notification to the device. Definitions for terms used in this section are below: @@ -89,10 +103,19 @@ Client behaviour Clients MUST configure a Pusher before they will receive push notifications. There is a single API endpoint for this, as described below. -{{pusher_http_api}} +{{pusher_cs_http_api}} .. _pushers: `def:pushers`_ +Listing Notifications +~~~~~~~~~~~~~~~~~~~~~ + +A client can retrieve a list of events that it has been notified about. This +may be useful so that users can see a summary of what important messages they +have received. + +{{notifications_cs_http_api}} + Push Rules ~~~~~~~~~~ A push rule is a single rule that states under what *conditions* an event should @@ -122,21 +145,14 @@ Underride rules ``underride`` These are identical to ``override`` rules, but have a lower priority than ``content``, ``room`` and ``sender`` rules. -Push rules may be either global or device-specific. Device specific rules only -affect delivery of notifications via pushers with a matching ``profile_tag``. -All device-specific rules have a higher priority than global rules. This means -that the full list of rule kinds, in descending priority order, is as follows: - - * Device-specific Override - * Device-specific Content - * Device-specific Room - * Device-specific Sender - * Device-specific Underride - * Global Override - * Global Content - * Global Room - * Global Sender - * Global Underride +This means that the full list of rule kinds, in descending priority order, is +as follows: + +* Global Override +* Global Content +* Global Room +* Global Sender +* Global Underride Rules with the same ``kind`` can specify an ordering priority. This determines which rule is selected in the event of multiple matches. For example, a rule @@ -182,14 +198,16 @@ tweaks are defined: ``sound`` A string representing the sound to be played when this notification arrives. - A value of ``default`` means to play a default sound. + A value of ``default`` means to play a default sound. A device may choose to + alert the user by some other means if appropriate, eg. vibration. ``highlight`` A boolean representing whether or not this message should be highlighted in the UI. This will normally take the form of presenting the message in a different colour and/or style. The UI might also be adjusted to draw - particular attention to the room in which the event occurred. The ``value`` - may be omitted from the highlight tweak, in which case it should default to - ``true``. + particular attention to the room in which the event occurred. If a + ``highlight`` tweak is given with no value, its value is defined to be + ``true``. If no highlight tweak is given at all then the value of + ``highlight`` is defined to be false. Tweaks are passed transparently through the homeserver so client applications and Push Gateways may agree on additional tweaks. For example, a tweak may be @@ -202,57 +220,140 @@ than "user-defined rules". The ``rule_id`` for all server-default rules MUST start with a dot (".") to identify them as "server-default". The following server-default rules are specified: -``.m.rule.contains_user_name`` - Matches any message whose content is unencrypted and contains the local part - of the user's Matrix ID, separated by word boundaries. - Definition (as a ``content`` rule):: +Default Override Rules +^^^^^^^^^^^^^^^^^^^^^^ + +``.m.rule.master`` +`````````````````` +Matches all events, this can be enabled to turn off all push notifications +other than those generated by override rules set by the user. By default this +rule is disabled. + +Definition + +.. code:: json { - "rule_id": ".m.rule.contains_user_name" - "pattern": "[the local part of the user's Matrix ID]", + "rule_id": ".m.rule.master", + "default": true, + "enabled": false, + "conditions": [], "actions": [ - "notify", + "dont_notify" + ] + } + +``.m.rule.suppress_notices`` +```````````````````````````` +Matches messages with a ``msgtype`` of ``notice``. This should be an +``override`` rule so that it takes priority over ``content`` / ``sender`` / +``room`` rules. + +Definition: + +.. code:: json + + { + "rule_id": ".m.rule.suppress_notices", + "default": true, + "enabled": true, + "conditions": [ { - "set_tweak": "sound", - "value": "default" + "kind": "event_match", + "key": "content.msgtype", + "pattern": "m.notice", } ], + "actions": [ + "dont_notify", + ] } -``.m.rule.contains_display_name`` - Matches any message whose content is unencrypted and contains the user's - current display name in the room in which it was sent. +``.m.rule.invite_for_me`` +````````````````````````` +Matches any invites to a new room for this user. - Definition (this rule can only be an ``override`` or ``underride`` rule):: +Definition: + +.. code:: json { - "rule_id": ".m.rule.contains_display_name" + "rule_id": ".m.rule.invite_for_me", + "default": true, + "enabled": true, "conditions": [ { - "kind": "contains_display_name" + "key": "type", + "kind": "event_match", + "pattern": "m.room.member" + }, + { + "key": "content.membership", + "kind": "event_match", + "pattern": "invite" + }, + { + "key": "state_key", + "kind": "event_match", + "pattern": "[the user's Matrix ID]" } ], "actions": [ - "notify", + "notify", { "set_tweak": "sound", "value": "default" + }, + { + "set_tweak": "highlight", + "value": false + } + ] + } + +``.m.rule.member_event`` +```````````````````````` + +Matches any ``m.room.member_event``. + +Definition: + +.. code:: json + + { + "rule_id": ".m.rule.member_event", + "default": true, + "enabled": true, + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.room.member" } ], + "actions": [ + "dont_notify" + ] } -``.m.rule.room_one_to_one`` - Matches any message sent in a room with exactly two members. - Definition (this rule can only be an ``override`` or ``underride`` rule):: +``.m.rule.contains_display_name`` +````````````````````````````````` +Matches any message whose content is unencrypted and contains the user's +current display name in the room in which it was sent. + +Definition: + +.. code:: json { - "rule_id": ".m.rule.room_two_members" + "rule_id": ".m.rule.contains_display_name", + "default": true, + "enabled": true, "conditions": [ { - "is": "2", - "kind": "room_member_count" + "kind": "contains_display_name" } ], "actions": [ @@ -260,46 +361,133 @@ server-default rules are specified: { "set_tweak": "sound", "value": "default" + }, + { + "set_tweak": "highlight" } - ], + ] } -``.m.rule.suppress_notices`` - Matches messages with a ``msgtype`` of ``notice``. This should be an - ``override`` rule so that it takes priority over ``content`` / ``sender`` / - ``room`` rules. - Definition:: +Default Content Rules +^^^^^^^^^^^^^^^^^^^^^ + +``.m.rule.contains_user_name`` +`````````````````````````````` +Matches any message whose content is unencrypted and contains the local part +of the user's Matrix ID, separated by word boundaries. + +Definition (as a ``content`` rule): + +.. code:: json { - 'rule_id': '.m.rule.suppress_notices', - 'conditions': [ + "rule_id": ".m.rule.contains_user_name", + "default": true, + "enabled": true, + "pattern": "[the local part of the user's Matrix ID]", + "actions": [ + "notify", { - 'kind': 'event_match', - 'key': 'content.msgtype', - 'pattern': 'm.notice', + "set_tweak": "sound", + "value": "default" } - ], - 'actions': [ - 'dont-notify', ] } - -``.m.rule.fallback`` - Matches any message. Used to define the behaviour of messages that match no - other rules. If homeservers define this it should be the lowest priority - ``underride`` rule. - Definition:: +Default Underride Rules +^^^^^^^^^^^^^^^^^^^^^^^ + +``.m.rule.call`` +```````````````` +Matches any incoming VOIP call. + +Definition: + +.. code:: json { - "rule_id": ".m.rule.fallback" - "conditions": [], + "rule_id": ".m.rule.call", + "default": true, + "enabled": true, + "conditions": [ + { + "key": "type", + "kind": "event_match", + "pattern": "m.call.invite" + } + ], "actions": [ - "notify" + "notify", + { + "set_tweak": "sound", + "value": "ring" + }, + { + "set_tweak": "highlight", + "value": false + } + ] + }, + +``.m.rule.room_one_to_one`` +``````````````````````````` +Matches any message sent in a room with exactly two members. + +Definition: + +.. code:: json + + { + "rule_id": ".m.rule.room_one_to_one", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "room_member_count", + "is": "2" + } ], + "actions": [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak": "highlight", + "value": false + } + ] } +``.m.rule.message`` +``````````````````` +Matches all chat messages. + +Definition: + +.. code:: json + + { + "rule_id": ".m.rule.message", + "default": true, + "enabled": true, + "conditions": [ + { + "kind": "event_match", + "key": "type", + "pattern": "m.room.message" + } + ], + "actions": [ + "notify", + { + "set_tweak": "highlight", + "value": false + } + ] + } Conditions @@ -315,16 +503,11 @@ rule determines its behaviour. The following conditions are defined: ``event_match`` This is a glob pattern match on a field of the event. Parameters: - * ``key``: The dot-separated field of the event to match, e.g. ``content.body`` - * ``pattern``: The glob-style pattern to match against. Patterns with no - special glob characters should be treated as having asterisks - prepended and appended when testing the condition. -``profile_tag`` - Matches the ``profile_tag`` of the device that the notification would be - delivered to. Parameters: - - * ``profile_tag``: The profile_tag to match with. + * ``key``: The dot-separated field of the event to match, e.g. ``content.body`` + * ``pattern``: The glob-style pattern to match against. Patterns with no + special glob characters should be treated as having asterisks + prepended and appended when testing the condition. ``contains_display_name`` This matches unencrypted messages where ``content.body`` contains the owner's @@ -334,10 +517,11 @@ rule determines its behaviour. The following conditions are defined: ``room_member_count`` This matches the current number of members in the room. Parameters: - * ``is``: A decimal integer optionally prefixed by one of, ``==``, ``<``, - ``>``, ``>=`` or ``<=``. A prefix of ``<`` matches rooms where the member - count is strictly less than the given number and so forth. If no prefix is - present, this parameter defaults to ``==``. + + * ``is``: A decimal integer optionally prefixed by one of, ``==``, ``<``, + ``>``, ``>=`` or ``<=``. A prefix of ``<`` matches rooms where the member + count is strictly less than the given number and so forth. If no prefix is + present, this parameter defaults to ``==``. Push Rules: API ~~~~~~~~~~~~~~~ @@ -345,7 +529,15 @@ Push Rules: API Clients can retrieve, add, modify and remove push rules globally or per-device using the APIs below. -{{pushrules_http_api}} +{{pushrules_cs_http_api}} + + +Push Rules: Events +~~~~~~~~~~~~~~~~~~ + +When a user changes their push rules a ``m.push_rules`` event is sent to all +clients in the ``account_data`` section of their next ``/sync`` request. The +content of the event is the current push rules for the user. Examples ++++++++ @@ -353,14 +545,14 @@ Examples To create a rule that suppresses notifications for the room with ID ``!dj234r78wl45Gh4D:matrix.org``:: - curl -X PUT -H "Content-Type: application/json" "http://localhost:8008/_matrix/client/api/v1/pushrules/global/room/%21dj234r78wl45Gh4D%3Amatrix.org?access_token=123456" -d \ + curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/%CLIENT_MAJOR_VERSION%/pushrules/global/room/%21dj234r78wl45Gh4D%3Amatrix.org?access_token=123456" -d \ '{ "actions" : ["dont_notify"] }' To suppress notifications for the user ``@spambot:matrix.org``:: - curl -X PUT -H "Content-Type: application/json" "http://localhost:8008/_matrix/client/api/v1/pushrules/global/sender/%40spambot%3Amatrix.org?access_token=123456" -d \ + curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/%CLIENT_MAJOR_VERSION%/pushrules/global/sender/%40spambot%3Amatrix.org?access_token=123456" -d \ '{ "actions" : ["dont_notify"] }' @@ -368,7 +560,7 @@ To suppress notifications for the user ``@spambot:matrix.org``:: To always notify for messages that contain the work 'cake' and set a specific sound (with a rule_id of ``SSByZWFsbHkgbGlrZSBjYWtl``):: - curl -X PUT -H "Content-Type: application/json" "http://localhost:8008/_matrix/client/api/v1/pushrules/global/content/SSByZWFsbHkgbGlrZSBjYWtl?access_token=123456" -d \ + curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/%CLIENT_MAJOR_VERSION%/pushrules/global/content/SSByZWFsbHkgbGlrZSBjYWtl?access_token=123456" -d \ '{ "pattern": "cake", "actions" : ["notify", {"set_sound":"cakealarm.wav"}] @@ -377,7 +569,7 @@ sound (with a rule_id of ``SSByZWFsbHkgbGlrZSBjYWtl``):: To add a rule suppressing notifications for messages starting with 'cake' but ending with 'lie', superseding the previous rule:: - curl -X PUT -H "Content-Type: application/json" "http://localhost:8008/_matrix/client/api/v1/pushrules/global/content/U3BvbmdlIGNha2UgaXMgYmVzdA?access_token=123456&before=SSByZWFsbHkgbGlrZSBjYWtl" -d \ + curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/%CLIENT_MAJOR_VERSION%/pushrules/global/content/U3BvbmdlIGNha2UgaXMgYmVzdA?access_token=123456&before=SSByZWFsbHkgbGlrZSBjYWtl" -d \ '{ "pattern": "cake*lie", "actions" : ["notify"] @@ -387,7 +579,7 @@ To add a custom sound for notifications messages containing the word 'beer' in any rooms with 10 members or fewer (with greater importance than the room, sender and content rules):: - curl -X PUT -H "Content-Type: application/json" "http://localhost:8008/_matrix/client/api/v1/pushrules/global/override/U2VlIHlvdSBpbiBUaGUgRHVrZQ?access_token=123456" -d \ + curl -X PUT -H "Content-Type: application/json" "https://example.com/_matrix/client/%CLIENT_MAJOR_VERSION%/pushrules/global/override/U2VlIHlvdSBpbiBUaGUgRHVrZQ?access_token=123456" -d \ '{ "conditions": [ {"kind": "event_match", "key": "content.body", "pattern": "beer" }, @@ -402,12 +594,6 @@ sender and content rules):: Server behaviour ---------------- -This describes the format used by "HTTP" pushers to send notifications of -events to Push Gateways. If the endpoint returns an HTTP error code, the -homeserver SHOULD retry for a reasonable amount of time using exponential-backoff. - -{{push_notifier_http_api}} - Push Gateway behaviour ---------------------- @@ -418,12 +604,12 @@ client app and its' push gateway to agree on. As APNS requires that the sender has a private key owned by the app developer, each app must have its own push gateway. It is recommended that: - * The APNS token be base64 encoded and used as the pushkey. - * A different app_id be used for apps on the production and sandbox - APS environments. - * APNS push gateways do not attempt to wait for errors from the APNS - gateway before returning and instead to store failures and return - 'rejected' responses next time that pushkey is used. +* The APNS token be base64 encoded and used as the pushkey. +* A different app_id be used for apps on the production and sandbox + APS environments. +* APNS push gateways do not attempt to wait for errors from the APNS + gateway before returning and instead to store failures and return + 'rejected' responses next time that pushkey is used. Security considerations ----------------------- diff --git a/specification/modules/receipts.rst b/specification/modules/receipts.rst index a8ad3cd3451..a6d8cbf772c 100644 --- a/specification/modules/receipts.rst +++ b/specification/modules/receipts.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Receipts ======== @@ -24,8 +38,8 @@ single ``event_id``. Client behaviour ---------------- -In v1 ``/initialSync``, receipts are listed in a separate top level ``receipts`` -key. In v2 ``/sync``, receipts are contained in the ``ephemeral`` block for a +In ``/initialSync``, receipts are listed in a separate top level ``receipts`` +key. In ``/sync``, receipts are contained in the ``ephemeral`` block for a room. New receipts that come down the event streams are deltas which update existing mappings. Clients should replace older receipt acknowledgements based on ``user_id`` and ``receipt_type`` pairs. For example:: @@ -52,7 +66,7 @@ dismissing a notification in order for the event to count as "read". A client can update the markers for its user by interacting with the following HTTP APIs. -{{v2_receipts_http_api}} +{{receipts_cs_http_api}} Server behaviour ---------------- diff --git a/specification/modules/room_previews.rst b/specification/modules/room_previews.rst new file mode 100644 index 00000000000..f40c89728dd --- /dev/null +++ b/specification/modules/room_previews.rst @@ -0,0 +1,58 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Room Previews +============= + +.. _module:room-previews: + +It is sometimes desirable to offer a preview of a room, where a user can "lurk" +and read messages posted to the room, without joining the room. This can be +particularly effective when combined with `Guest Access`_. + +Previews are implemented via the ``world_readable`` `Room History Visibility`_. +setting, along with a special version of the +`GET /events <#get-matrix-client-%CLIENT_MAJOR_VERSION%-events>`_ endpoint. + +Client behaviour +---------------- +A client wishing to view a room without joining it should call +`GET /rooms/:room_id/initialSync <#get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-initialsync>`_, +followed by `GET /events`__. Clients will need to do this +in parallel for each room they wish to view. + +__ `peeking_events_api`_ + +Clients can of course also call other endpoints such as +`GET /rooms/:room_id/messages <#get-matrix-client-%CLIENT_MAJOR_VERSION%-rooms-roomid-messages>`_ +and `GET /search <#get-matrix-client-%CLIENT_MAJOR_VERSION%-search>`_ to access +events outside the ``/events`` stream. + +.. _peeking_events_api: + +{{peeking_events_cs_http_api}} + +Server behaviour +---------------- +For clients which have not joined a room, servers are required to only return +events where the room state at the event had the ``m.room.history_visibility`` +state event present with ``history_visibility`` value ``world_readable``. + +Security considerations +----------------------- +Clients may wish to display to their users that rooms which are +``world_readable`` *may* be showing messages to non-joined users. There is no +way using this module to find out whether any non-joined guest users *do* see +events in the room, or to list or count any lurking users. + diff --git a/specification/modules/search.rst b/specification/modules/search.rst new file mode 100644 index 00000000000..08926552340 --- /dev/null +++ b/specification/modules/search.rst @@ -0,0 +1,109 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Server Side Search +================== + +.. _module:search: + +The search API allows clients to perform full text search across events in all +rooms that the user has been in, including those that they have left. Only +events that the user is allowed to see will be searched, e.g. it won't include +events in rooms that happened after you left. + +Client behaviour +---------------- +There is a single HTTP API for performing server-side search, documented below. + +{{search_cs_http_api}} + +Search Categories +----------------- + +The search API allows clients to search in different categories of items. +Currently the only specified category is ``room_events``. + +``room_events`` +~~~~~~~~~~~~~~~ + +This category covers all events that the user is allowed to see, including +events in rooms that they have left. The search is performed on certain keys of +certain event types. + +The supported keys to search over are: + +- ``content.body`` in ``m.room.message`` +- ``content.name`` in ``m.room.name`` +- ``content.topic`` in ``m.room.topic`` + +The search will *not* include rooms that are end to end encrypted. + +The results include a ``rank`` key that can be used to sort the results by +relevancy. The higher the ``rank`` the more relevant the result is. + +The value of ``count`` gives an approximation of the total number of +results. Homeservers may give an estimate rather than an exact value for this +field. + +Ordering +-------- + +The client can specify the ordering that the server returns results in. The two +allowed orderings are: + +- ``rank``, which returns the most relevant results first. +- ``recent``, which returns the most recent results first. + +The default ordering is ``rank``. + +Groups +------ + +The client can request that the results are returned along with grouping +information, e.g. grouped by ``room_id``. In this case the response will +contain a group entry for each distinct value of ``room_id``. Each group entry +contains at least a list of the ``event_ids`` that are in that group, as well +as potentially other metadata about the group. + +The current required supported groupings are: + +- ``room_id`` +- ``sender`` + + +Pagination +---------- + +The server may return a ``next_batch`` key at various places in the response. +These are used to paginate the results. To fetch more results, the client +should send the *same* request to the server with a ``next_batch`` query +parameter set to that of the token. + +The scope of the pagination is defined depending on where the ``next_batch`` +token was returned. For example, using a token inside a group will return more +results from within that group. + +The currently supported locations for the ``next_batch`` token are: + +- ``search_categories..next_batch`` +- ``search_categories..groups...next_batch`` + +A server need not support pagination, even if there are more matching results. +In that case, they must not return a ``next_batch`` token in the response. + + +Security considerations +----------------------- +The server must only return results that the user has permission to see. + diff --git a/specification/modules/send_to_device.rst b/specification/modules/send_to_device.rst new file mode 100644 index 00000000000..232becae90f --- /dev/null +++ b/specification/modules/send_to_device.rst @@ -0,0 +1,150 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +.. _module:to_device: +.. _`to-device`: + +Send-to-Device messaging +======================== + +This module provides a means by which clients can exchange signalling messages +without them being stored permanently as part of a shared communication +history. A message is delivered exactly once to each client device. + +The primary motivation for this API is exchanging data that is meaningless or +undesirable to persist in the room DAG - for example, one-time authentication +tokens or key data. It is not intended for conversational data, which should be +sent using the normal |/rooms//send|_ API for consistency throughout +Matrix. + +Client behaviour +---------------- +To send a message to other devices, a client should call |/sendToDevice|_. +Only one message can be sent to each device per transaction, and they must all +have the same event type. The device ID in the request body can be set to ``*`` +to request that the message be sent to all known devices. + +If there are send-to-device messages waiting for a client, they will be +returned by |/sync|_, as detailed in |Extensions|_. Clients should +inspect the ``type`` of each returned event, and ignore any they do not +understand. + +.. |Extensions| replace:: Extensions to /sync +.. _Extensions: `send_to_device_sync`_ + +Server behaviour +---------------- +Servers should store pending messages for local users until they are +successfully delivered to the destination device. When a client calls |/sync|_ +with an access token which corresponds to a device with pending messages, the +server should list the pending messages, in order of arrival, in the response +body. + +When the client calls ``/sync`` again with the ``next_batch`` token from the +first response, the server should infer that any send-to-device messages in +that response have been delivered successfully, and delete them from the store. + +If there is a large queue of send-to-device messages, the server should +limit the number sent in each ``/sync`` response. 100 messages is recommended +as a reasonable limit. + +If the client sends messages to users on remote domains, those messages should +be sent on to the remote servers via +`federation`_. + +.. _`federation`: ../server_server/latest.html#send-to-device-messages + +.. TODO-spec: + + * Is a server allowed to delete undelivered messages? After how long? What + about if the device is deleted? + + * If the destination HS doesn't support the ``m.direct_to_device`` EDU, it + will just get dumped. Should we indicate that to the client? + + +Protocol definitions +-------------------- + +{{to_device_cs_http_api}} + +.. TODO-spec: + + * What should a server do if the user id or device id is unknown? Presumably + it shouldn't reject the request outright, because some of the destinations + may be valid. Should we add something to the response? + +.. anchor for link from /sync api spec +.. |send_to_device_sync| replace:: Send-to-Device messaging +.. _send_to_device_sync: + +Extensions to /sync +~~~~~~~~~~~~~~~~~~~ + +This module adds the following properties to the |/sync|_ response: + +.. todo: generate this from a swagger definition? + +========= ========= ======================================================= +Parameter Type Description +========= ========= ======================================================= +to_device ToDevice Optional. Information on the send-to-device messages + for the client device. +========= ========= ======================================================= + +``ToDevice`` + +========= ========= ============================================= +Parameter Type Description +========= ========= ============================================= +events [Event] List of send-to-device messages +========= ========= ============================================= + +``Event`` + +================ ============ ================================================== +Parameter Type Description +================ ============ ================================================== +content EventContent The content of this event. The fields in this + object will vary depending on the type of event. +sender string The Matrix user ID of the user who sent this + event. +type string The type of event. +================ ============ ================================================== + + +Example response: + +.. code:: json + + { + "next_batch": "s72595_4483_1934", + "rooms": {"leave": {}, "join": {}, "invite": {}}, + "to_device": { + "events": [ + { + "sender": "@alice:example.com", + "type": "m.new_device", + "content": { + "device_id": "XYZABCDE", + "rooms": ["!726s6s6q:example.com"] + } + } + ] + } + } + + +.. |/sendToDevice| replace:: ``/sendToDevice`` +.. _/sendToDevice: #put-matrix-client-%CLIENT_MAJOR_VERSION%-sendtodevice-eventtype-txnid diff --git a/specification/modules/tags.rst b/specification/modules/tags.rst new file mode 100644 index 00000000000..f0a7fa527f9 --- /dev/null +++ b/specification/modules/tags.rst @@ -0,0 +1,62 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Room Tagging +============ + +.. _module:tagging: + +Users can add tags to rooms. Tags are short strings used to label rooms, e.g. +"work", "family". A room may have multiple tags. Tags are only visible to the +user that set them but are shared across all their devices. + +Events +------ + +The tags on a room are received as single ``m.tag`` event in the +``account_data`` section of a room in a ``/sync``. + +The ``m.tag`` can also be received in a ``/events`` response or in the +``account_data`` section of a room in ``/initialSync``. ``m.tag`` +events appearing in ``/events`` will have a ``room_id`` with the room +the tags are for. + +Each tag has an associated JSON object with information about the tag, e.g how +to order the rooms with a given tag. + +Ordering information is given under the ``order`` key as a string. The string +are compared lexicographically by unicode codepoint to determine which should +displayed first. So a room with a tag with an ``order`` key of ``"apples"`` +would appear before a room with a tag with an ``order`` key of ``"oranges"``. +If a room has a tag without an ``order`` key then it should appear after the +rooms with that tag that have an ``order`` key. + +The name of a tag MUST not exceed 255 bytes. + +The name of a tag should be human readable. When displaying tags for a room a +client should display this human readable name. When adding a tag for a room +a client may offer a list to choose from that includes all the tags that the +user has previously set on any of their rooms. + +Two special names are listed in the specification: + +* ``m.favourite`` +* ``m.lowpriority`` + +{{m_tag_event}} + +Client Behaviour +---------------- + +{{tags_cs_http_api}} diff --git a/specification/modules/third_party_invites.rst b/specification/modules/third_party_invites.rst index a9883db5317..9ea0eb0b216 100644 --- a/specification/modules/third_party_invites.rst +++ b/specification/modules/third_party_invites.rst @@ -1,27 +1,44 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Third party invites =================== -.. _module:third_party_invites: +.. _module:third-party-invites: This module adds in support for inviting new members to a room where their Matrix user ID is not known, instead addressing them by a third party identifier such as an email address. - There are two flows here; one if a Matrix user ID is known for the third party identifier, and one if not. Either way, the client calls ``/invite`` with the details of the third party identifier. The homeserver asks the identity server whether a Matrix user ID is known for -that identifier. If it is, an invite is simply issued for that user. +that identifier: + +- If it is, an invite is simply issued for that user. -If it is not, the homeserver asks the identity server to record the details of -the invitation, and to notify the client of this pending invitation if it gets -a binding for this identifier in the future. The identity server returns a token -and public key to the homeserver. +- If it is not, the homeserver asks the identity server to record the details of + the invitation, and to notify the invitee's homeserver of this pending invitation if it gets + a binding for this identifier in the future. The identity server returns a token + and public key to the inviting homeserver. -If a client then tries to join the room in the future, it will be allowed to if -it presents both the token, and a signature of that token from the identity -server which can be verified with the public key. +When the invitee's homeserver receives the notification of the binding, it +should insert an ``m.room.member`` event into the room's graph for that user, +with ``content.membership`` = ``invite``, as well as a +``content.third_party_invite`` property which contains proof that the invitee +does indeed own that third party identifier. Events ------ @@ -33,14 +50,18 @@ Client behaviour A client asks a server to invite a user by their third party identifier. +{{third_party_membership_cs_http_api}} + Server behaviour ---------------- -All homeservers MUST verify that sig(``token``, ``public_key``) = ``signature``. +All homeservers MUST verify the signature in the event's +``content.third_party_invite.signed`` object. -If a client of the current homeserver is joining by an -``m.room.third_party_invite``, that homesever MUST validate that the public -key used for signing is still valid, by checking ``key_validity_url``. It does +When a homeserver inserts an ``m.room.member`` ``invite`` event into the graph +because of an ``m.room.third_party_invite`` event, +that homesever MUST validate that the public +key used for signing is still valid, by checking ``key_validity_url`` from the ``m.room.third_party_invite``. It does this by making an HTTP GET request to ``key_validity_url``: .. TODO: Link to identity server spec when it exists @@ -83,24 +104,24 @@ membership is questionable. For example: - If room R has two participating homeservers, H1, H2 +#. Room R has two participating homeservers, H1, H2 - And user A on H1 invites a third party identifier to room R +#. User A on H1 invites a third party identifier to room R - H1 asks the identity server for a binding to a Matrix user ID, and has none, - so issues an ``m.room.third_party_invite`` event to the room. +#. H1 asks the identity server for a binding to a Matrix user ID, and has none, + so issues an ``m.room.third_party_invite`` event to the room. - When the third party user validates their identity, they are told about the - invite, and ask their homeserver, H3, to join the room. +#. When the third party user validates their identity, their homeserver H3 + is notified and attempts to issue an ``m.room.member`` event to participate + in the room. - H3 validates that sign(``token``, ``public_key``) = ``signature``, and may check - ``key_validity_url``. +#. H3 validates the signature given to it by the identity server. - H3 then asks H1 to join it to the room. H1 *must* validate that - sign(``token``, ``public_key``) = ``signature`` *and* check ``key_validity_url``. +#. H3 then asks H1 to join it to the room. H1 *must* validate the ``signed`` + property *and* check ``key_validity_url``. - Having validated these things, H1 writes the join event to the room, and H3 - begins participating in the room. H2 *must* accept this event. +#. Having validated these things, H1 writes the invite event to the room, and H3 + begins participating in the room. H2 *must* accept this event. The reason that no other homeserver may reject the event based on checking ``key_validity_url`` is that we must ensure event acceptance is deterministic. @@ -111,3 +132,29 @@ This relies on participating servers trusting each other, but that trust is already implied by the server-server protocol. Also, the public key signature verification must still be performed, so the attack surface here is minimized. +Security considerations +----------------------- + +There are a number of privary and trust implications to this module. + +It is important for user privacy that leaking the mapping between a matrix user +ID and a third party identifier is hard. In particular, being able to look up +all third party identifiers from a matrix user ID (and accordingly, being able +to link each third party identifier) should be avoided wherever possible. +To this end, the third party identifier is not put in any event, rather an +opaque display name provided by the identity server is put into the events. +Clients should not remember or display third party identifiers from invites, +other than for the use of the inviter themself. + +Homeservers are not required to trust any particular identity server(s). It is +generally a client's responsibility to decide which identity servers it trusts, +not a homeserver's. Accordingly, this API takes identity servers as input from +end users, and doesn't have any specific trusted set. It is possible some +homeservers may want to supply defaults, or reject some identity servers for +*its* users, but no homeserver is allowed to dictate which identity servers +*other* homeservers' users trust. + +There is some risk of denial of service attacks by flooding homeservers or +identity servers with many requests, or much state to store. Defending against +these is left to the implementer's discretion. + diff --git a/specification/modules/typing_notifications.rst b/specification/modules/typing_notifications.rst index d2253632718..964b804a237 100644 --- a/specification/modules/typing_notifications.rst +++ b/specification/modules/typing_notifications.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Typing Notifications ==================== @@ -5,10 +19,8 @@ Typing Notifications Users may wish to be informed when another user is typing in a room. This can be achieved using typing notifications. These are ephemeral events scoped to a -``room_id``. This means they do not form part of the `Event Graph`_ but still -have a ``room_id`` key. - -.. _Event Graph: `sect:event-graph`_ +``room_id``. This means they do not form part of the +`Event Graph `_ but still have a ``room_id`` key. Events ------ @@ -34,29 +46,7 @@ expected timeout runs out is recommended. When the user stops typing, the state change of the ``boolean`` to ``false`` should trigger another HTTP request to inform the server that the user has stopped typing. -{{typing_http_api}} - -Server behaviour ----------------- - -Servers MUST emit typing EDUs in a different form to ``m.typing`` events which -are shown to clients. This form looks like:: - - { - "type": "m.typing", - "content": { - "room_id": "!room-id-here:matrix.org", - "user_id": "@user-id-here:matrix.org", - "typing": true/false - } - } - -This does not contain timing information so it is up to originating homeservers -to ensure they eventually send "stop" notifications. - -.. TODO - ((This will eventually need addressing, as part of the wider typing/presence - timer addition work)) +{{typing_cs_http_api}} Security considerations ----------------------- diff --git a/specification/modules/voip_events.rst b/specification/modules/voip_events.rst index a7b02538c84..fab5651702a 100644 --- a/specification/modules/voip_events.rst +++ b/specification/modules/voip_events.rst @@ -1,3 +1,17 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + Voice over IP ============= @@ -5,10 +19,11 @@ Voice over IP This module outlines how two users in a room can set up a Voice over IP (VoIP) call to each other. Voice and video calls are built upon the WebRTC 1.0 standard. -Call signalling is achieved by sending `message events`_ to the room. As a result, -this means that clients MUST only send call events to rooms with exactly two -participants as currently the WebRTC standard is based around two-party -communication. +Call signalling is achieved by sending `message events`_ to the room. In this +version of the spec, only two-party communication is supported (e.g. between two +peers, or between a peer and a multi-point conferencing unit). +This means that clients MUST only send call events to rooms with exactly two +participants. .. _message events: `sect:events`_ @@ -62,19 +77,19 @@ As calls are "placed" to rooms rather than users, the glare resolution algorithm outlined below is only considered for calls which are to the same room. The algorithm is as follows: - - If an ``m.call.invite`` to a room is received whilst the client is - **preparing to send** an ``m.call.invite`` to the same room: +- If an ``m.call.invite`` to a room is received whilst the client is + **preparing to send** an ``m.call.invite`` to the same room: - * the client should cancel its outgoing call and instead - automatically accept the incoming call on behalf of the user. + * the client should cancel its outgoing call and instead + automatically accept the incoming call on behalf of the user. - - If an ``m.call.invite`` to a room is received **after the client has sent** - an ``m.call.invite`` to the same room and is waiting for a response: +- If an ``m.call.invite`` to a room is received **after the client has sent** + an ``m.call.invite`` to the same room and is waiting for a response: - * the client should perform a lexicographical comparison of the call IDs of - the two calls and use the *lesser* of the two calls, aborting the - greater. If the incoming call is the lesser, the client should accept - this call on behalf of the user. + * the client should perform a lexicographical comparison of the call IDs of + the two calls and use the *lesser* of the two calls, aborting the + greater. If the incoming call is the lesser, the client should accept + this call on behalf of the user. The call setup should appear seamless to the user as if they had simply placed @@ -89,7 +104,7 @@ The homeserver MAY provide a TURN server which clients can use to contact the remote party. The following HTTP API endpoints will be used by clients in order to get information about the TURN server. -{{voip_http_api}} +{{voip_cs_http_api}} Security considerations diff --git a/specification/push_gateway.rst b/specification/push_gateway.rst new file mode 100644 index 00000000000..29a41bf7c28 --- /dev/null +++ b/specification/push_gateway.rst @@ -0,0 +1,70 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Push Gateway API +================ + +Clients may want to receive push notifications when events are received at +the homeserver. This is managed by a distinct entity called the Push Gateway. + +.. contents:: Table of Contents +.. sectnum:: + +Specification version +--------------------- + +This version of the specification is generated from +`matrix-doc `_ as of Git commit +`{{git_version}} `_. + +Overview +-------- + +A client's homeserver forwards information about received events to the push +gateway. The gateway then submits a push notification to the push notification +provider (e.g. APNS, GCM). + + +:: + + +--------------------+ +-------------------+ + Matrix HTTP | | | | + Notification Protocol | App Developer | | Device Vendor | + | | | | + +-------------------+ | +----------------+ | | +---------------+ | + | | | | | | | | | | + | Matrix homeserver +-----> Push Gateway +------> Push Provider | | + | | | | | | | | | | + +-^-----------------+ | +----------------+ | | +----+----------+ | + | | | | | | + Matrix | | | | | | + Client/Server API + | | | | | + | | +--------------------+ +-------------------+ + | +--+-+ | + | | <-------------------------------------------+ + +---+ | + | | Provider Push Protocol + +----+ + + Mobile Device or Client + + +Homeserver behaviour +-------------------- + +This describes the format used by "HTTP" pushers to send notifications of +events to Push Gateways. If the endpoint returns an HTTP error code, the +homeserver SHOULD retry for a reasonable amount of time using exponential backoff. + +{{push_notifier_push_http_api}} diff --git a/specification/server_server_api.rst b/specification/server_server_api.rst new file mode 100644 index 00000000000..eb863e74305 --- /dev/null +++ b/specification/server_server_api.rst @@ -0,0 +1,1308 @@ +.. Copyright 2016 OpenMarket Ltd +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Federation API +============== + +Matrix homeservers use the Federation APIs (also known as server-server APIs) +to communicate with each other. Homeservers use these APIs to push messages to +each other in real-time, to request historic messages from each other, and to +query profile and presence information about users on each other's servers. + +The APIs are implemented using HTTPS GETs and PUTs between each of the +servers. These HTTPS requests are strongly authenticated using public key +signatures at the TLS transport layer and using public key signatures in +HTTP Authorization headers at the HTTP layer. + +There are three main kinds of communication that occur between homeservers: + +Persisted Data Units (PDUs): + These events are broadcast from one homeserver to any others that have + joined the same room (identified by Room ID). They are persisted in + long-term storage and record the history of messages and state for a + room. + + Like email, it is the responsibility of the originating server of a PDU + to deliver that event to its recipient servers. However PDUs are signed + using the originating server's private key so that it is possible to + deliver them through third-party servers. + +Ephemeral Data Units (EDUs): + These events are pushed between pairs of homeservers. They are not + persisted and are not part of the history of a room, nor does the + receiving homeserver have to reply to them. + +Queries: + These are single request/response interactions between a given pair of + servers, initiated by one side sending an HTTPS GET request to obtain some + information, and responded by the other. They are not persisted and contain + no long-term significant history. They simply request a snapshot state at + the instant the query is made. + + +EDUs and PDUs are further wrapped in an envelope called a Transaction, which is +transferred from the origin to the destination homeserver using an HTTPS PUT +request. + +.. contents:: Table of Contents +.. sectnum:: + +Specification version +--------------------- + +This version of the specification is generated from +`matrix-doc `_ as of Git commit +`{{git_version}} `_. + +Server Discovery +---------------- + +Resolving Server Names +~~~~~~~~~~~~~~~~~~~~~~ + +Each matrix homeserver is identified by a server name consisting of a DNS name +and an optional TLS port. + +.. code:: + + server_name = dns_name [ ":" tls_port] + dns_name = + tls_port = *DIGIT + +.. ** + +If the port is present then the server is discovered by looking up an AAAA or +A record for the DNS name and connecting to the specified TLS port. If the port +is absent then the server is discovered by looking up a ``_matrix._tcp`` SRV +record for the DNS name. If this record does not exist then the server is +discovered by looking up an AAAA or A record on the DNS name and taking the +default fallback port number of 8448. +Homeservers may use SRV records to load balance requests between multiple TLS +endpoints or to failover to another endpoint if an endpoint fails. + +Retrieving Server Keys +~~~~~~~~~~~~~~~~~~~~~~ + +Version 2 ++++++++++ + +Each homeserver publishes its public keys under ``/_matrix/key/v2/server/``. +Homeservers query for keys by either getting ``/_matrix/key/v2/server/`` +directly or by querying an intermediate notary server using a +``/_matrix/key/v2/query`` API. Intermediate notary servers query the +``/_matrix/key/v2/server/`` API on behalf of another server and sign the +response with their own key. A server may query multiple notary servers to +ensure that they all report the same public keys. + +This approach is borrowed from the `Perspectives Project`_, but modified to +include the NACL keys and to use JSON instead of XML. It has the advantage of +avoiding a single trust-root since each server is free to pick which notary +servers they trust and can corroborate the keys returned by a given notary +server by querying other servers. + +.. _Perspectives Project: http://perspectives-project.org/ + +Publishing Keys +^^^^^^^^^^^^^^^ + +Homeservers publish the allowed TLS fingerprints and signing keys in a JSON +object at ``/_matrix/key/v2/server/{key_id}``. The response contains a list of +``verify_keys`` that are valid for signing federation requests made by the +server and for signing events. It contains a list of ``old_verify_keys`` +which are only valid for signing events. Finally the response contains a list +of TLS certificate fingerprints to validate any connection made to the server. + +A server may have multiple keys active at a given time. A server may have any +number of old keys. It is recommended that servers return a single JSON +response listing all of its keys whenever any ``key_id`` is requested to reduce +the number of round trips needed to discover the relevant keys for a server. +However a server may return a different responses for a different ``key_id``. + +The ``tls_certificates`` contain a list of hashes of the X.509 TLS certificates +currently used by the server. The list must include SHA-256 hashes for every +certificate currently in use by the server. These fingerprints are valid until +the millisecond POSIX timestamp in ``valid_until_ts``. + +The ``verify_keys`` can be used to sign requests and events made by the server +until the millisecond POSIX timestamp in ``valid_until_ts``. If a homeserver +receives an event with a ``origin_server_ts`` after the ``valid_until_ts`` then +it should request that ``key_id`` for the originating server to check whether +the key has expired. + +The ``old_verify_keys`` can be used to sign events with an ``origin_server_ts`` +before the ``expired_ts``. The ``expired_ts`` is a millisecond POSIX timestamp +of when the originating server stopped using that key. + +Intermediate notary servers should cache a response for half of its remaining +life time to avoid serving a stale response. Originating servers should avoid +returning responses that expire in less than an hour to avoid repeated requests +for an about to expire certificate. Requesting servers should limit how +frequently they query for certificates to avoid flooding a server with requests. + +If a server goes offline intermediate notary servers should continue to return +the last response they received from that server so that the signatures of old +events sent by that server can still be checked. + +==================== =================== ====================================== + Key Type Description +==================== =================== ====================================== +``server_name`` String DNS name of the homeserver. +``verify_keys`` Object Public keys of the homeserver for + verifying digital signatures. +``old_verify_keys`` Object The public keys that the server used + to use and when it stopped using them. +``signatures`` Object Digital signatures for this object + signed using the ``verify_keys``. +``tls_fingerprints`` Array of Objects Hashes of X.509 TLS certificates used + by this this server encoded as `Unpadded Base64`_. +``valid_until_ts`` Integer POSIX timestamp when the list of valid + keys should be refreshed. +==================== =================== ====================================== + + +.. code:: json + + { + "old_verify_keys": { + "ed25519:auto1": { + "expired_ts": 922834800000, + "key": "Base+64+Encoded+Old+Verify+Key" + } + }, + "server_name": "example.org", + "signatures": { + "example.org": { + "ed25519:auto2": "Base+64+Encoded+Signature" + } + }, + "tls_fingerprints": [ + { + "sha256": "Base+64+Encoded+SHA-256-Fingerprint" + } + ], + "valid_until_ts": 1052262000000, + "verify_keys": { + "ed25519:auto2": { + "key": "Base+64+Encoded+Signature+Verification+Key" + } + } + } + +Querying Keys Through Another Server +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Servers may offer a query API ``_matrix/key/v2/query/`` for getting the keys +for another server. This API can be used to GET at list of JSON objects for a +given server or to POST a bulk query for a number of keys from a number of +servers. Either way the response is a list of JSON objects containing the +JSON published by the server under ``_matrix/key/v2/server/`` signed by +both the originating server and by this server. + +The ``minimum_valid_until_ts`` is a millisecond POSIX timestamp indicating +when the returned certificate will need to be valid until to be useful to the +requesting server. This can be set using the maximum ``origin_server_ts`` of +an batch of events that a requesting server is trying to validate. This allows +an intermediate notary server to give a prompt cached response even if the +originating server is offline. + +This API can return keys for servers that are offline be using cached responses +taken from when the server was online. Keys can be queried from multiple +servers to mitigate against DNS spoofing. + +Requests: + +.. code:: + + GET /_matrix/key/v2/query/${server_name}/${key_id}/?minimum_valid_until_ts=${minimum_valid_until_ts} HTTP/1.1 + + POST /_matrix/key/v2/query HTTP/1.1 + Content-Type: application/json + + { + "server_keys": { + "$server_name": { + "$key_id": { + "minimum_valid_until_ts": $posix_timestamp + } + } + } + } + + +Response: + +.. code:: + + HTTP/1.1 200 OK + Content-Type: application/json + { + "server_keys": [ + # List of responses with same format as /_matrix/key/v2/server + # signed by both the originating server and this server. + ] + } + +Version 1 ++++++++++ +.. WARNING:: + Version 1 of key distribution is obsolete + + +Homeservers publish their TLS certificates and signing keys in a JSON object +at ``/_matrix/key/v1``. + +==================== =================== ====================================== + Key Type Description +==================== =================== ====================================== +``server_name`` String DNS name of the homeserver. +``verify_keys`` Object Public keys of the homeserver for + verifying digital signatures. +``signatures`` Object Digital signatures for this object + signed using the ``verify_keys``. +``tls_certificate`` String The X.509 TLS certificate used by this + this server encoded as `Unpadded Base64`_. +==================== =================== ====================================== + +.. code:: json + + { + "server_name": "example.org", + "signatures": { + "example.org": { + "ed25519:auto": "Base+64+Encoded+Signature" + } + }, + "tls_certificate": "Base+64+Encoded+DER+Encoded+X509+TLS+Certificate" + "verify_keys": { + "ed25519:auto": "Base+64+Encoded+Signature+Verification+Key" + } + } + +When fetching the keys for a server the client should check that the TLS +certificate in the JSON matches the TLS server certificate for the connection +and should check that the JSON signatures are correct for the supplied +``verify_keys`` + +Transactions +------------ +.. WARNING:: + This section may be misleading or inaccurate. + +The transfer of EDUs and PDUs between homeservers is performed by an exchange +of Transaction messages, which are encoded as JSON objects, passed over an HTTP +PUT request. A Transaction is meaningful only to the pair of homeservers that +exchanged it; they are not globally-meaningful. + +Each transaction has: + - An opaque transaction ID. + - A timestamp (UNIX epoch time in milliseconds) generated by its origin + server. + - An origin and destination server name. + - A list of "previous IDs". + - A list of PDUs and EDUs - the actual message payload that the Transaction + carries. + +Transaction Fields +~~~~~~~~~~~~~~~~~~ + +==================== =================== ====================================== + Key Type Description +==================== =================== ====================================== +``origin`` String DNS name of homeserver making this + transaction. +``origin_server_ts`` Integer Timestamp in milliseconds on + originating homeserver when this + transaction started. +``previous_ids`` List of Strings List of transactions that were sent + immediately prior to this transaction. +``pdus`` List of Objects List of persistent updates to rooms. +``edus`` List of Objects List of ephemeral messages. +==================== =================== ====================================== + +.. code:: json + + { + "transaction_id":"916d630ea616342b42e98a3be0b74113", + "ts":1404835423000, + "origin":"red", + "prev_ids":["e1da392e61898be4d2009b9fecce5325"], + "pdus":[...], + "edus":[...] + } + +The ``prev_ids`` field contains a list of previous transaction IDs that the +``origin`` server has sent to this ``destination``. Its purpose is to act as a +sequence checking mechanism - the destination server can check whether it has +successfully received that Transaction, or ask for a re-transmission if not. + +The ``pdus`` field of a transaction is a list, containing zero or more PDUs.[*] +Each PDU is itself a JSON object containing a number of keys, the exact details +of which will vary depending on the type of PDU. Similarly, the ``edus`` field +is another list containing the EDUs. This key may be entirely absent if there +are no EDUs to transfer. + +(* Normally the PDU list will be non-empty, but the server should cope with +receiving an "empty" transaction, as this is useful for informing peers of other +transaction IDs they should be aware of. This effectively acts as a push +mechanism to encourage peers to continue to replicate content.) + +PDUs +---- + +All PDUs have: + +- An ID to identify the PDU itself +- A room ID that it relates to +- A declaration of their type +- A list of other PDU IDs that have been seen recently in that room (regardless + of which origin sent them) + + +Required PDU Fields +~~~~~~~~~~~~~~~~~~~ + +==================== ================== ======================================= + Key Type Description +==================== ================== ======================================= +``context`` String Room identifier +``user_id`` String The ID of the user sending the PDU +``origin`` String DNS name of homeserver that created + this PDU +``pdu_id`` String Unique identifier for PDU on the + originating homeserver +``origin_server_ts`` Integer Timestamp in milliseconds on origin + homeserver when this PDU was created. +``pdu_type`` String PDU event type +``content`` Object The content of the PDU. +``prev_pdus`` List of (String, The originating homeserver, PDU ids and + String, Object) hashes of the most recent PDUs the + Triplets homeserver was aware of for the room + when it made this PDU +``depth`` Integer The maximum depth of the previous PDUs + plus one +``is_state`` Boolean True if this PDU is updating room state +==================== ================== ======================================= + +.. code:: json + + { + "context":"#example:green.example.com", + "origin":"green.example.com", + "pdu_id":"a4ecee13e2accdadf56c1025af232176", + "origin_server_ts":1404838188000, + "pdu_type":"m.room.message", + "prev_pdus":[ + ["blue.example.com","99d16afbc8", + {"sha256":"abase64encodedsha256hashshouldbe43byteslong"}] + ], + "hashes":{"sha256":"thishashcoversallfieldsincasethisisredacted"}, + "signatures":{ + "green.example.com":{ + "ed25519:key_version:":"these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus" + } + }, + "is_state":false, + "content": {...} + } + +In contrast to Transactions, it is important to note that the ``prev_pdus`` +field of a PDU refers to PDUs that any origin server has sent, rather than +previous IDs that this ``origin`` has sent. This list may refer to other PDUs +sent by the same origin as the current one, or other origins. + +Because of the distributed nature of participants in a Matrix conversation, it +is impossible to establish a globally-consistent total ordering on the events. +However, by annotating each outbound PDU at its origin with IDs of other PDUs +it has received, a partial ordering can be constructed allowing causality +relationships to be preserved. A client can then display these messages to the +end-user in some order consistent with their content and ensure that no message +that is semantically in reply of an earlier one is ever displayed before it. + +State Update PDU Fields +~~~~~~~~~~~~~~~~~~~~~~~ + +PDUs fall into two main categories: those that deliver Events, and those that +synchronise State. For PDUs that relate to State synchronisation, additional +keys exist to support this: + +======================== ============ ========================================= + Key Type Description +======================== ============ ========================================= +``state_key`` String Combined with the ``pdu_type`` this + identifies the which part of the room + state is updated +``required_power_level`` Integer The required power level needed to + replace this update. +``prev_state_id`` String The PDU id of the update this replaces. +``prev_state_origin`` String The homeserver of the update this + replaces. +``user_id`` String The user updating the state. +======================== ============ ========================================= + +.. code:: json + + {..., + "is_state":true, + "state_key":TODO-doc + "required_power_level":TODO-doc + "prev_state_id":TODO-doc + "prev_state_origin":TODO-doc + } + + +EDUs +---- + +EDUs, by comparison to PDUs, do not have an ID, a room ID, or a list of +"previous" IDs. The only mandatory fields for these are the type, origin and +destination homeserver names, and the actual nested content. + +======================== ============ ========================================= + Key Type Description +======================== ============ ========================================= +``edu_type`` String The type of the ephemeral message. +``content`` Object Content of the ephemeral message. +======================== ============ ========================================= + +.. code:: json + + { + "edu_type":"m.presence", + "origin":"blue", + "destination":"orange", + "content":{...} + } + + +Protocol URLs +------------- + +.. WARNING:: + This section may be misleading or inaccurate. + +All these URLs are name-spaced within a prefix of:: + + /_matrix/federation/v1/... + +For active pushing of messages representing live activity "as it happens":: + + PUT .../send// + Body: JSON encoding of a single Transaction + Response: TODO-doc + +The transaction_id path argument will override any ID given in the JSON body. +The destination name will be set to that of the receiving server itself. Each +embedded PDU in the transaction body will be processed. + + +To fetch all the state of a given room:: + + GET .../state// + Response: JSON encoding of a single Transaction containing multiple PDUs + +Retrieves a snapshot of the entire current state of the given room. The +response will contain a single Transaction, inside which will be a list of PDUs +that encode the state. + + +To fetch a particular event:: + + GET .../event// + Response: JSON encoding of a partial Transaction containing the event + +Retrieves a single event. The response will contain a partial Transaction, +having just the ``origin``, ``origin_server_ts`` and ``pdus`` fields; the +event will be encoded as the only PDU in the ``pdus`` list. + + +To backfill events on a given room:: + + GET .../backfill// + Query args: v, limit + Response: JSON encoding of a single Transaction containing multiple PDUs + +Retrieves a sliding-window history of previous PDUs that occurred on the given +room. Starting from the PDU ID(s) given in the "v" argument, the PDUs that +preceded it are retrieved, up to a total number given by the "limit" argument. + + +To stream events all the events:: + + GET .../pull/ + Query args: origin, v + Response: JSON encoding of a single Transaction consisting of multiple PDUs + +Retrieves all of the transactions later than any version given by the "v" +arguments. + + +To make a query:: + + GET .../query/ + Query args: as specified by the individual query types + Response: JSON encoding of a response object + +Performs a single query request on the receiving homeserver. The Query Type +part of the path specifies the kind of query being made, and its query +arguments have a meaning specific to that kind of query. The response is a +JSON-encoded object whose meaning also depends on the kind of query. + + +To join a room:: + + GET .../make_join// + Response: JSON encoding of a join proto-event + + PUT .../send_join// + Response: JSON encoding of the state of the room at the time of the event + +Performs the room join handshake. For more information, see "Joining Rooms" +below. + +Joining Rooms +------------- + +When a new user wishes to join room that the user's homeserver already knows +about, the homeserver can immediately determine if this is allowable by +inspecting the state of the room, and if it is acceptable, it can generate, +sign, and emit a new ``m.room.member`` state event adding the user into that +room. When the homeserver does not yet know about the room it cannot do this +directly. Instead, it must take a longer multi-stage handshaking process by +which it first selects a remote homeserver which is already participating in +that room, and uses it to assist in the joining process. This is the remote +join handshake. + +This handshake involves the homeserver of the new member wishing to join +(referred to here as the "joining" server), the directory server hosting the +room alias the user is requesting to join with, and a homeserver where existing +room members are already present (referred to as the "resident" server). + +In summary, the remote join handshake consists of the joining server querying +the directory server for information about the room alias; receiving a room ID +and a list of join candidates. The joining server then requests information +about the room from one of the residents. It uses this information to construct +a ``m.room.member`` event which it finally sends to a resident server. + +Conceptually these are three different roles of homeserver. In practice the +directory server is likely to be resident in the room, and so may be selected +by the joining server to be the assisting resident. Likewise, it is likely that +the joining server picks the same candidate resident for both phases of event +construction, though in principle any valid candidate may be used at each time. +Thus, any join handshake can potentially involve anywhere from two to four +homeservers, though most in practice will use just two. + +:: + + Client Joining Directory Resident + Server Server Server + + join request --> + | + directory request -------> + <---------- directory response + | + make_join request -----------------------> + <------------------------------- make_join response + | + send_join request -----------------------> + <------------------------------- send_join response + | + <---------- join response + +The first part of the handshake usually involves using the directory server to +request the room ID and join candidates. This is covered in more detail on the +directory server documentation, below. In the case of a new user joining a +room as a result of a received invite, the joining user's homeserver could +optimise this step away by picking the origin server of that invite message as +the join candidate. However, the joining server should be aware that the origin +server of the invite might since have left the room, so should be prepared to +fall back on the regular join flow if this optimisation fails. + +Once the joining server has the room ID and the join candidates, it then needs +to obtain enough information about the room to fill in the required fields of +the ``m.room.member`` event. It obtains this by selecting a resident from the +candidate list, and requesting the ``make_join`` endpoint using a ``GET`` +request, specifying the room ID and the user ID of the new member who is +attempting to join. + +The resident server replies to this request with a JSON-encoded object having a +single key called ``event``; within this is an object whose fields contain some +of the information that the joining server will need. Despite its name, this +object is not a full event; notably it does not need to be hashed or signed by +the resident homeserver. The required fields are: + +==================== ======== ============ + Key Type Description +==================== ======== ============ +``type`` String The value ``m.room.member`` +``auth_events`` List An event-reference list containing the + authorization events that would allow this member + to join +``content`` Object The event content +``depth`` Integer (this field must be present but is ignored; it + may be 0) +``origin`` String The name of the resident homeserver +``origin_server_ts`` Integer A timestamp added by the resident homeserver +``prev_events`` List An event-reference list containing the immediate + predecessor events +``room_id`` String The room ID of the room +``sender`` String The user ID of the joining member +``state_key`` String The user ID of the joining member +==================== ======== ============ + +The ``content`` field itself must be an object, containing: + +============== ====== ============ + Key Type Description +============== ====== ============ +``membership`` String The value ``join`` +============== ====== ============ + +The joining server now has sufficient information to construct the real join +event from these protoevent fields. It copies the values of most of them, +adding (or replacing) the following fields: + +==================== ======= ============ + Key Type Description +==================== ======= ============ +``event_id`` String A new event ID specified by the joining homeserver +``origin`` String The name of the joining homeserver +``origin_server_ts`` Integer A timestamp added by the joining homeserver +==================== ======= ============ + +This will be a true event, so the joining server should apply the event-signing +algorithm to it, resulting in the addition of the ``hashes`` and ``signatures`` +fields. + +To complete the join handshake, the joining server must now submit this new +event to an resident homeserver, by using the ``send_join`` endpoint. This is +invoked using the room ID and the event ID of the new member event. + +The resident homeserver then accepts this event into the room's event graph, +and responds to the joining server with the full set of state for the newly- +joined room. This is returned as a two-element list, whose first element is the +integer 200, and whose second element is an object which contains the +following keys: + +============== ===== ============ + Key Type Description +============== ===== ============ +``auth_chain`` List A list of events giving the authorization chain for this + join event +``state`` List A complete list of the prevailing state events at the + instant just before accepting the new ``m.room.member`` + event +============== ===== ============ + +.. TODO-spec + - (paul) I don't really understand why the full auth_chain events are given + here. What purpose does it serve expanding them out in full, when surely + they'll appear in the state anyway? + +Backfilling +----------- + +Once a homeserver has joined a room, it receives all the events emitted by +other homeservers in that room, and is thus aware of the entire history of the +room from that moment onwards. Since users in that room are able to request the +history by the ``/messages`` client API endpoint, it's possible that they might +step backwards far enough into history before the homeserver itself was a +member of that room. + +To cover this case, the federation API provides a server-to-server analog of +the ``/messages`` client API, allowing one homeserver to fetch history from +another. This is the ``/backfill`` API. + +To request more history, the requesting homeserver picks another homeserver +that it thinks may have more (most likely this should be a homeserver for some +of the existing users in the room at the earliest point in history it has +currently), and makes a ``/backfill`` request. The parameters of this request +give an event ID that the requesting homeserver wishes to obtain, and a number +specifying how many more events of history before that one to return at most. + +The response to this request is an object with the following keys: + +==================== ======== ============ + Key Type Description +==================== ======== ============ +``pdus`` List A list of events +``origin`` String The name of the resident homeserver +``origin_server_ts`` Integer A timestamp added by the resident homeserver +==================== ======== ============ + +The list of events given in ``pdus`` is returned in reverse chronological +order; having the most recent event first (i.e. the event whose event ID is +that requested by the requestor in the ``v`` parameter). + +.. TODO-spec + Specify (or remark that it is unspecified) how the server handles divergent + history. DFS? BFS? Anything weirder? + +Inviting to a room +------------------ + +When a user wishes to invite an other user to a local room and the other user +is on a different server, the inviting server will send a request to the invited +server:: + + PUT .../invite/{roomId}/{eventId} + +The required fields in the JSON body are: + +==================== ======== ============ + Key Type Description +==================== ======== ============ +``room_id`` String The room ID of the room. Must be the same as the + room ID specified in the path. +``event_id`` String The ID of the event. Must be the same as the event + ID specified in the path. +``type`` String The value ``m.room.member``. +``auth_events`` List An event-reference list containing the IDs of the + authorization events that would allow this member + to be invited in the room. +``content`` Object The content of the event. +``depth`` Integer The depth of the event. +``origin`` String The name of the inviting homeserver. +``origin_server_ts`` Integer A timestamp added by the inviting homeserver. +``prev_events`` List An event-reference list containing the IDs of the + immediate predecessor events. +``sender`` String The Matrix ID of the user who sent the original + `m.room.third_party_invite`. +``state_key`` String The Matrix ID of the invited user. +``signatures`` Object The signature of the event from the origin server. +``unsigned`` Object An object containing the properties that aren't + part of the signature's computation. +==================== ======== ============ + +Where the ``content`` key contains the content for the ``m.room.member`` event +specified in the `Client-Server API`_. Note that the ``membership`` property of +the content must be ``invite``. + +Upon receiving this request, the invited homeserver will append its signature to +the event and respond to the request with the following JSON body:: + + [ + 200, + "event": {...} + ] + +Where ``event`` contains the event signed by both homeservers, using the same +JSON keys as the initial request on ``/invite/{roomId}/{eventId}``. Note that, +except for the ``signatures`` object (which now contains an additional signature), +all of the event's keys remain the same as in the event initially provided. + +This response format is due to a typo in Synapse, the first implementation of +Matrix's APIs, and is preserved to maintain compatibility. + +Now that the event has been signed by both the inviting homeserver and the +invited homeserver, it can be sent to all of the users in the room. + +Third-party invites +------------------- + +When an user wants to invite another user in a room but doesn't know the Matrix +ID to invite, they can do so using a third-party identifier (e.g. an e-mail or a +phone number). + +This identifier and its bindings to Matrix IDs are verified by an identity server +implementing the `Identity Service API`_. + +Cases where an association exists for a third-party identifier +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the third-party identifier is already bound to a Matrix ID, a lookup request +on the identity server will return it. The invite is then processed by the inviting +homeserver as a standard ``m.room.member`` invite event. This is the simplest case. + +Cases where an association doesn't exist for a third-party identifier +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the third-party identifier isn't bound to any Matrix ID, the inviting +homeserver will request the identity server to store an invite for this identifier +and to deliver it to whoever binds it to its Matrix ID. It will also send a +``m.room.third_party_invite`` event in the room to specify a display name, a token +and public keys the identity server provided as a response to the invite storage +request. + +When a third-party identifier with pending invites gets bound to a Matrix ID, +the identity server will send a ``POST`` request to the ID's homeserver as described +in the `Invitation Storage`_ section of the Identity Service API. + +The following process applies for each invite sent by the identity server: + +The invited homeserver will create a ``m.room.member`` invite event containing +a special ``third_party_invite`` section containing the token and a signed object, +both provided by the identity server. + +If the invited homeserver is in the room the invite came from, it can auth the +event and send it. + +However, if the invited homeserver isn't in the room the invite came from, it +will need to request the room's homeserver to auth the event:: + + PUT .../exchange_third_party_invite/{roomId} + +Where ``roomId`` is the ID of the room the invite is for. + +The required fields in the JSON body are: + +==================== ======= ================================================== + Key Type Description +==================== ======= ================================================== +``type`` String The event type. Must be `m.room.member`. +``room_id`` String The ID of the room the event is for. Must be the + same as the ID specified in the path. +``sender`` String The Matrix ID of the user who sent the original + `m.room.third_party_invite`. +``state_key`` String The Matrix ID of the invited user. +``content`` Object The content of the event. +==================== ======= ================================================== + +Where the ``content`` key contains the content for the ``m.room.member`` event +as described in the `Client-Server API`_. Its ``membership`` key must be +``invite`` and its content must include the ``third_party_invite`` object. + +The inviting homeserver will then be able to authenticate the event. It will send +a fully authenticated event to the invited homeserver as described in the `Inviting +to a room`_ section above. + +Once the invited homeserver responded with the event to which it appended its +signature, the inviting homeserver will respond with ``200 OK`` and an empty body +(``{}``) to the initial request on ``/exchange_third_party_invite/{roomId}`` and +send the now verified ``m.room.member`` invite event to the room's members. + +Verifying the invite +++++++++++++++++++++ + +When a homeserver receives a ``m.room.member`` invite event for a room it's in +with a ``third_party_invite`` object, it must verify that the association between +the third-party identifier initially invited to the room and the Matrix ID that +claims to be bound to it has been verified without having to rely on a third-party +server. + +To do so, it will fetch from the room's state events the ``m.room.third_party_invite`` +event for which the state key matches with the value for the ``token`` key in the +``third_party_invite`` object from the ``m.room.member`` event's content to fetch the +public keys initially delivered by the identity server that stored the invite. + +It will then use these keys to verify that the ``signed`` object (in the +``third_party_invite`` object from the ``m.room.member`` event's content) was +signed by the same identity server. + +Since this ``signed`` object can only be delivered once in the ``POST`` request +emitted by the identity server upon binding between the third-party identifier +and the Matrix ID, and contains the invited user's Matrix ID and the token +delivered when the invite was stored, this verification will prove that the +``m.room.member`` invite event comes from the user owning the invited third-party +identifier. + +Authentication +-------------- + +Request Authentication +~~~~~~~~~~~~~~~~~~~~~~ + +Every HTTP request made by a homeserver is authenticated using public key +digital signatures. The request method, target and body are signed by wrapping +them in a JSON object and signing it using the JSON signing algorithm. The +resulting signatures are added as an Authorization header with an auth scheme +of X-Matrix. Note that the target field should include the full path starting with +``/_matrix/...``, including the ``?`` and any query parameters if present, but +should not include the leading ``https:``, nor the destination server's +hostname. + +Step 1 sign JSON: + +.. code:: + + { + "method": "GET", + "uri": "/target", + "origin": "origin.hs.example.com", + "destintation": "destination.hs.example.com", + "content": { JSON content ... }, + "signatures": { + "origin.hs.example.com": { + "ed25519:key1": "ABCDEF..." + } + } + } + +Step 2 add Authorization header: + +.. code:: + + GET /target HTTP/1.1 + Authorization: X-Matrix origin=origin.example.com,key="ed25519:key1",sig="ABCDEF..." + Content-Type: application/json + + { JSON content ... } + + +Example python code: + +.. code:: python + + def authorization_headers(origin_name, origin_signing_key, + destination_name, request_method, request_target, + content_json=None): + request_json = { + "method": request_method, + "uri": request_target, + "origin": origin_name, + "destination": destination_name, + } + + if content_json is not None: + request["content"] = content_json + + signed_json = sign_json(request_json, origin_name, origin_signing_key) + + authorization_headers = [] + + for key, sig in signed_json["signatures"][origin_name].items(): + authorization_headers.append(bytes( + "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % ( + origin_name, key, sig, + ) + )) + + return ("Authorization", authorization_headers) + +Response Authentication +~~~~~~~~~~~~~~~~~~~~~~~ + +Responses are authenticated by the TLS server certificate. A homeserver should +not send a request until it has authenticated the connected server to avoid +leaking messages to eavesdroppers. + +Client TLS Certificates +~~~~~~~~~~~~~~~~~~~~~~~ + +Requests are authenticated at the HTTP layer rather than at the TLS layer +because HTTP services like Matrix are often deployed behind load balancers that +handle the TLS and these load balancers make it difficult to check TLS client +certificates. + +A homeserver may provide a TLS client certificate and the receiving homeserver +may check that the client certificate matches the certificate of the origin +homeserver. + +Server-Server Authorization +--------------------------- + +.. TODO-doc + - PDU signing (see the Event signing section earlier) + - State conflict resolution (see below) + +State Conflict Resolution +------------------------- +.. NOTE:: + This section is a work in progress. + +.. TODO-doc + - How do conflicts arise (diagrams?) + - How are they resolved (incl tie breaks) + - How does this work with deleting current state + - How do we reject invalid federation traffic? + + [[TODO(paul): At this point we should probably have a long description of how + State management works, with descriptions of clobbering rules, power levels, etc + etc... But some of that detail is rather up-in-the-air, on the whiteboard, and + so on. This part needs refining. And writing in its own document as the details + relate to the server/system as a whole, not specifically to server-server + federation.]] + +Presence +-------- +The server API for presence is based entirely on exchange of the following +EDUs. There are no PDUs or Federation Queries involved. + +Performing a presence update and poll subscription request:: + + EDU type: m.presence + + Content keys: + push: (optional): list of push operations. + Each should be an object with the following keys: + user_id: string containing a User ID + presence: "offline"|"unavailable"|"online"|"free_for_chat" + status_msg: (optional) string of free-form text + last_active_ago: milliseconds since the last activity by the user + + poll: (optional): list of strings giving User IDs + + unpoll: (optional): list of strings giving User IDs + +The presence of this combined message is two-fold: it informs the recipient +server of the current status of one or more users on the sending server (by the +``push`` key), and it maintains the list of users on the recipient server that +the sending server is interested in receiving updates for, by adding (by the +``poll`` key) or removing them (by the ``unpoll`` key). The ``poll`` and +``unpoll`` lists apply *changes* to the implied list of users; any existing IDs +that the server sent as ``poll`` operations in a previous message are not +removed until explicitly requested by a later ``unpoll``. + +On receipt of a message containing a non-empty ``poll`` list, the receiving +server should immediately send the sending server a presence update EDU of its +own, containing in a ``push`` list the current state of every user that was in +the original EDU's ``poll`` list. + +Sending a presence invite:: + + EDU type: m.presence_invite + + Content keys: + observed_user: string giving the User ID of the user whose presence is + requested (i.e. the recipient of the invite) + observer_user: string giving the User ID of the user who is requesting to + observe the presence (i.e. the sender of the invite) + +Accepting a presence invite:: + + EDU type: m.presence_accept + + Content keys - as for m.presence_invite + +Rejecting a presence invite:: + + EDU type: m.presence_deny + + Content keys - as for m.presence_invite + +.. TODO-doc + - Explain the timing-based round-trip reduction mechanism for presence + messages + - Explain the zero-byte presence inference logic + See also: docs/client-server/model/presence + +Profiles +-------- + +The server API for profiles is based entirely on the following Federation +Queries. There are no additional EDU or PDU types involved, other than the +implicit ``m.presence`` and ``m.room.member`` events (see section below). + +Querying profile information:: + + Query type: profile + + Arguments: + user_id: the ID of the user whose profile to return + field: (optional) string giving a field name + + Returns: JSON object containing the following keys: + displayname: string of free-form text + avatar_url: string containing an HTTP-scheme URL + +If the query contains the optional ``field`` key, it should give the name of a +result field. If such is present, then the result should contain only a field +of that name, with no others present. If not, the result should contain as much +of the user's profile as the homeserver has available and can make public. + +Directory +--------- + +The server API for directory queries is also based on Federation Queries. + +Querying directory information:: + + Query type: directory + + Arguments: + room_alias: the room alias to query + + Returns: JSON object containing the following keys: + room_id: string giving the underlying room ID the alias maps to + servers: list of strings giving the join candidates + +The list of join candidates is a list of server names that are likely to hold +the given room; these are servers that the requesting server may wish to use as +resident servers as part of the remote join handshake. This list may or may not +include the server answering the query. + +Send-to-device messaging +------------------------ + +.. TODO: add modules to the federation spec and make this a module + +The server API for send-to-device messaging is based on the following +EDU. There are no PDUs or Federation Queries involved. + +Each send-to-device message should be sent to the destination server using +the following EDU:: + + EDU type: m.direct_to_device + + Content keys: + sender: user ID of the sender + + type: event type for the message + + message_id: unique id for the message: used for idempotence + + messages: The messages to send. A map from user ID, to a map from device ID + to message body. The device ID may also be *, meaning all known devices + for the user. + + +Signing Events +-------------- + +Signing events is complicated by the fact that servers can choose to redact +non-essential parts of an event. + +Before signing the event, the ``unsigned`` and ``signature`` members are +removed, it is encoded as `Canonical JSON`_, and then hashed using SHA-256. The +resulting hash is then stored in the event JSON in a ``hash`` object under a +``sha256`` key. + +.. code:: python + + def hash_event(event_json_object): + + # Keys under "unsigned" can be modified by other servers. + # They are useful for conveying information like the age of an + # event that will change in transit. + # Since they can be modifed we need to exclude them from the hash. + unsigned = event_json_object.pop("unsigned", None) + + # Signatures will depend on the current value of the "hashes" key. + # We cannot add new hashes without invalidating existing signatures. + signatures = event_json_object.pop("signatures", None) + + # The "hashes" key might contain multiple algorithms if we decide to + # migrate away from SHA-2. We don't want to include an existing hash + # output in our hash so we exclude the "hashes" dict from the hash. + hashes = event_json_object.pop("hashes", {}) + + # Encode the JSON using a canonical encoding so that we get the same + # bytes on every server for the same JSON object. + event_json_bytes = encode_canonical_json(event_json_bytes) + + # Add the base64 encoded bytes of the hash to the "hashes" dict. + hashes["sha256"] = encode_base64(sha256(event_json_bytes).digest()) + + # Add the "hashes" dict back the event JSON under a "hashes" key. + event_json_object["hashes"] = hashes + if unsigned is not None: + event_json_object["unsigned"] = unsigned + return event_json_object + +The event is then stripped of all non-essential keys both at the top level and +within the ``content`` object. Any top-level keys not in the following list +MUST be removed: + +.. code:: + + auth_events + depth + event_id + hashes + membership + origin + origin_server_ts + prev_events + prev_state + room_id + sender + signatures + state_key + type + +A new ``content`` object is constructed for the resulting event that contains +only the essential keys of the original ``content`` object. If the original +event lacked a ``content`` object at all, a new empty JSON object is created +for it. + +The keys that are considered essential for the ``content`` object depend on the +the ``type`` of the event. These are: + +.. code:: + + type is "m.room.aliases": + aliases + + type is "m.room.create": + creator + + type is "m.room.history_visibility": + history_visibility + + type is "m.room.join_rules": + join_rule + + type is "m.room.member": + membership + + type is "m.room.power_levels": + ban + events + events_default + kick + redact + state_default + users + users_default + +The resulting stripped object with the new ``content`` object and the original +``hashes`` key is then signed using the JSON signing algorithm outlined below: + +.. code:: python + + def sign_event(event_json_object, name, key): + + # Make sure the event has a "hashes" key. + if "hashes" not in event_json_object: + event_json_object = hash_event(event_json_object) + + # Strip all the keys that would be removed if the event was redacted. + # The hashes are not stripped and cover all the keys in the event. + # This means that we can tell if any of the non-essential keys are + # modified or removed. + stripped_json_object = strip_non_essential_keys(event_json_object) + + # Sign the stripped JSON object. The signature only covers the + # essential keys and the hashes. This means that we can check the + # signature even if the event is redacted. + signed_json_object = sign_json(stripped_json_object) + + # Copy the signatures from the stripped event to the original event. + event_json_object["signatures"] = signed_json_oject["signatures"] + return event_json_object + +Servers can then transmit the entire event or the event with the non-essential +keys removed. If the entire event is present, receiving servers can then check +the event by computing the SHA-256 of the event, excluding the ``hash`` object. +If the keys have been redacted, then the ``hash`` object is included when +calculating the SHA-256 instead. + +New hash functions can be introduced by adding additional keys to the ``hash`` +object. Since the ``hash`` object cannot be redacted a server shouldn't allow +too many hashes to be listed, otherwise a server might embed illict data within +the ``hash`` object. For similar reasons a server shouldn't allow hash values +that are too long. + +.. TODO + [[TODO(markjh): We might want to specify a maximum number of keys for the + ``hash`` and we might want to specify the maximum output size of a hash]] + [[TODO(markjh) We might want to allow the server to omit the output of well + known hash functions like SHA-256 when none of the keys have been redacted]] + +.. _`Invitation storage`: ../identity_service/unstable.html#invitation-storage +.. _`Identity Service API`: ../identity_service/unstable.html +.. _`Client-Server API`: ../client_server/unstable.html#m-room-member +.. _`Inviting to a room`: #inviting-to-a-room +.. _`Canonical JSON`: ../appendices.html#canonical-json +.. _`Unpadded Base64`: ../appendices.html#unpadded-base64 diff --git a/specification/targets.yaml b/specification/targets.yaml index c2b6eeda0fa..0d64815b183 100644 --- a/specification/targets.yaml +++ b/specification/targets.yaml @@ -1,17 +1,41 @@ targets: - main: # arbitrary name to identify this build target - files: # the sort order of files to cat - - 0-intro.rst - - 1-client_server_api.rst - - { 1: 0-events.rst } - - { 1: 0-event_signing.rst } - - 2-modules.rst - - { 1: 0-feature_profiles.rst } - - { 1: "group:modules" } # reference a group of files - - 3-application_service_api.rst - - 4-server_server_api.rst - - 5-identity_servers.rst - - 6-appendices.rst + index: + files: + - index.rst + intro: + files: + - intro.rst + client_server: + files: + - client_server_api.rst + - { 1: events.rst } + - { 1: modules.rst } + - { 2: feature_profiles.rst } + - { 2: "group:modules" } # reference a group of files + version_label: "%CLIENT_RELEASE_LABEL%" + application_service: + files: + - application_service_api.rst + version_label: unstable + server_server: + files: + - server_server_api.rst + version_label: "%SERVER_RELEASE_LABEL%" + identity_service: + files: + - identity_service_api.rst + version_label: unstable + push_gateway: + files: + - push_gateway.rst + version_label: unstable + appendices: + files: + - appendices.rst + - appendices/base64.rst + - appendices/signing_json.rst + - appendices/threat_model.rst + - appendices/test_vectors.rst groups: # reusable blobs of files when prefixed with 'group:' modules: - modules/instant_messaging.rst @@ -20,12 +44,24 @@ groups: # reusable blobs of files when prefixed with 'group:' - modules/receipts.rst - modules/presence.rst - modules/content_repo.rst + - modules/send_to_device.rst + - modules/device_management.rst - modules/end_to_end_encryption.rst - modules/history_visibility.rst - modules/push.rst - modules/third_party_invites.rst + - modules/search.rst + - modules/guest_access.rst + - modules/room_previews.rst + - modules/tags.rst + - modules/account_data.rst + - modules/admin.rst + - modules/event_context.rst + - modules/cas_login.rst + - modules/dm.rst -title_styles: ["=", "-", "~", "+", "^", "`"] + +title_styles: ["=", "-", "~", "+", "^", "`", "@", ":"] # The templating system doesn't know the right title style to use when generating # RST. These symbols are 'relative' to say "make a sub-title" (-1), "make a title diff --git a/supporting-docs/examples/application-services.rst b/supporting-docs/examples/application-services.rst index bde4531bf5e..fae3613d797 100644 --- a/supporting-docs/examples/application-services.rst +++ b/supporting-docs/examples/application-services.rst @@ -6,15 +6,15 @@ This file contains examples of some application service IRC Bridge ---------- Pre-conditions: - - Server admin stores the AS token "T_a" on the home server. - - Home server has a token "T_h". - - Home server has the domain "hsdomain.com" + - Server admin stores the AS token "T_a" on the homeserver. + - Homeserver has a token "T_h". + - Homeserver has the domain "hsdomain.com" 1. Application service registration :: - AS -> HS: Registers itself with the home server + AS -> HS: Registers itself with the homeserver POST /register { url: "https://someapp.com/matrix", diff --git a/supporting-docs/guides/2015-08-10-client-server.rst b/supporting-docs/guides/2015-08-10-client-server.rst deleted file mode 100644 index 2800584ca02..00000000000 --- a/supporting-docs/guides/2015-08-10-client-server.rst +++ /dev/null @@ -1,652 +0,0 @@ ---- -layout: post -title: Client-Server API -categories: guides ---- -| - -.. figure:: http://matrix.org/matrix.png - :width: 305px - :height: 130px - :alt: [matrix] - :align: center - - -How to use the client-server API -================================ - -.. NOTE:: - The git version of this document is {% project_version %} - -This guide focuses on how the client-server APIs *provided by the reference -home server* can be used. Since this is specific to a home server -implementation, there may be variations in relation to registering/logging in -which are not covered in extensive detail in this guide. - -If you haven't already, get a home server up and running on -``http://localhost:8008``. - - -Accounts -======== -Before you can send and receive messages, you must **register** for an account. -If you already have an account, you must **login** into it. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/register_login - -Registration ------------- -The aim of registration is to get a user ID and access token which you will need -when accessing other APIs:: - - curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register" - - { - "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc", - "home_server": "localhost", - "user_id": "@example:localhost" - } - -NB: If a ``user`` is not specified, one will be randomly generated for you. -If you do not specify a ``password``, you will be unable to login to the account -if you forget the ``access_token``. - -Implementation note: The matrix specification does not enforce how users -register with a server. It just specifies the URL path and absolute minimum -keys. The reference home server uses a username/password to authenticate user, -but other home servers may use different methods. This is why you need to -specify the ``type`` of method. - -Login ------ -The aim when logging in is to get an access token for your existing user ID:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/login" - - { - "flows": [ - { - "type": "m.login.password" - } - ] - } - - curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login" - - { - "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd", - "home_server": "localhost", - "user_id": "@example:localhost" - } - -Implementation note: Different home servers may implement different methods for -logging in to an existing account. In order to check that you know how to login -to this home server, you must perform a ``GET`` first and make sure you -recognise the login type. If you do not know how to login, you can -``GET /login/fallback`` which will return a basic webpage which you can use to -login. The reference home server implementation support username/password login, -but other home servers may support different login methods (e.g. OAuth2). - - -Communicating -============= - -In order to communicate with another user, you must **create a room** with that -user and **send a message** to that room. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/create_room_send_msg - -Creating a room ---------------- -If you want to send a message to someone, you have to be in a room with them. To -create a room:: - - curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN" - - { - "room_alias": "#tutorial:localhost", - "room_id": "!CvcvRuDYDzTOzfKKgh:localhost" - } - -The "room alias" is a human-readable string which can be shared with other users -so they can join a room, rather than the room ID which is a randomly generated -string. You can have multiple room aliases per room. - -.. TODO(kegan) - How to add/remove aliases from an existing room. - - -Sending messages ----------------- -You can now send messages to this room:: - - curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN" - - { - "event_id": "YUwRidLecu" - } - -The event ID returned is a unique ID which identifies this message. - -NB: There are no limitations to the types of messages which can be exchanged. -The only requirement is that ``"msgtype"`` is specified. The Matrix -specification outlines the following standard types: ``m.text``, ``m.image``, -``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for -more information on these types. - -Users and rooms -=============== - -Each room can be configured to allow or disallow certain rules. In particular, -these rules may specify if you require an **invitation** from someone already in -the room in order to **join the room**. In addition, you may also be able to -join a room **via a room alias** if one was set up. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/room_memberships - -Inviting a user to a room -------------------------- -You can directly invite a user to a room like so:: - - curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN" - -This informs ``@myfriend:localhost`` of the room ID -``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room. - -Joining a room via an invite ----------------------------- -If you receive an invite, you can join the room:: - - curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN" - -NB: Only the person invited (``@myfriend:localhost``) can change the membership -state to ``"join"``. Repeatedly joining a room does nothing. - -Joining a room via an alias ---------------------------- -Alternatively, if you know the room alias for this room and the room config -allows it, you can directly join a room via the alias:: - - curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN" - - { - "room_id": "!CvcvRuDYDzTOzfKKgh:localhost" - } - -You will need to use the room ID when sending messages, not the room alias. - -NB: If the room is configured to be an invite-only room, you will still require -an invite in order to join the room even though you know the room alias. As a -result, it is more common to see a room alias in relation to a public room, -which do not require invitations. - -Getting events -============== -An event is some interesting piece of data that a client may be interested in. -It can be a message in a room, a room invite, etc. There are many different ways -of getting events, depending on what the client already knows. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/event_stream - -Getting all state ------------------ -If the client doesn't know any information on the rooms the user is -invited/joined on, they can get all the user's state for all rooms:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN" - - { - "end": "s39_18_0", - "presence": [ - { - "content": { - "last_active_ago": 1061436, - "user_id": "@example:localhost" - }, - "type": "m.presence" - } - ], - "rooms": [ - { - "membership": "join", - "messages": { - "chunk": [ - { - "content": { - "@example:localhost": 10, - "default": 0 - }, - "event_id": "wAumPSTsWF", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.power_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "join_rule": "public" - }, - "event_id": "jrLVqKHKiI", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.join_rules", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 10 - }, - "event_id": "WpmTgsNWUZ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.add_state_level", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 0 - }, - "event_id": "qUMBJyKsTQ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.send_event_level", - "user_id": "@example:localhost" - }, - { - "content": { - "ban_level": 5, - "kick_level": 5 - }, - "event_id": "YAaDmKvoUW", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.ops_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "RJbPMtCutf", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409665586730, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "body": "hello", - "hsob_ts": 1409665660439, - "msgtype": "m.text" - }, - "event_id": "YUwRidLecu", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "ts": 1409665660439, - "type": "m.room.message", - "user_id": "@example:localhost" - }, - { - "content": { - "membership": "invite" - }, - "event_id": "YjNuBKnPsb", - "membership": "invite", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@myfriend:localhost", - "ts": 1409666426819, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join", - "prev": "join" - }, - "event_id": "KWwdDjNZnm", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666551582, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ], - "end": "s39_18_0", - "start": "t1-11_18_0" - }, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state": [ - { - "content": { - "creator": "@example:localhost" - }, - "event_id": "dMUoqVTZca", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.create", - "user_id": "@example:localhost" - }, - { - "content": { - "@example:localhost": 10, - "default": 0 - }, - "event_id": "wAumPSTsWF", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.power_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "join_rule": "public" - }, - "event_id": "jrLVqKHKiI", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.join_rules", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 10 - }, - "event_id": "WpmTgsNWUZ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.add_state_level", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 0 - }, - "event_id": "qUMBJyKsTQ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.send_event_level", - "user_id": "@example:localhost" - }, - { - "content": { - "ban_level": 5, - "kick_level": 5 - }, - "event_id": "YAaDmKvoUW", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.ops_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "membership": "invite" - }, - "event_id": "YjNuBKnPsb", - "membership": "invite", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@myfriend:localhost", - "ts": 1409666426819, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ] - } - ] - } - -This returns all the room information the user is invited/joined on, as well as -all of the presences relevant for these rooms. This can be a LOT of data. You -may just want the most recent event for each room. This can be achieved by -applying query parameters to ``limit`` this request:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN" - - { - "end": "s39_18_0", - "presence": [ - { - "content": { - "last_active_ago": 1279484, - "user_id": "@example:localhost" - }, - "type": "m.presence" - } - ], - "rooms": [ - { - "membership": "join", - "messages": { - "chunk": [ - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ], - "end": "s39_18_0", - "start": "t10-30_18_0" - }, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state": [ - { - "content": { - "creator": "@example:localhost" - }, - "event_id": "dMUoqVTZca", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.create", - "user_id": "@example:localhost" - }, - { - "content": { - "@example:localhost": 10, - "default": 0 - }, - "event_id": "wAumPSTsWF", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.power_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "join_rule": "public" - }, - "event_id": "jrLVqKHKiI", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.join_rules", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 10 - }, - "event_id": "WpmTgsNWUZ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.add_state_level", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 0 - }, - "event_id": "qUMBJyKsTQ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.send_event_level", - "user_id": "@example:localhost" - }, - { - "content": { - "ban_level": 5, - "kick_level": 5 - }, - "event_id": "YAaDmKvoUW", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.ops_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "membership": "invite" - }, - "event_id": "YjNuBKnPsb", - "membership": "invite", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@myfriend:localhost", - "ts": 1409666426819, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ] - } - ] - } - -Getting live state ------------------- -Once you know which rooms the client has previously interacted with, you need to -listen for incoming events. This can be done like so:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN" - - { - "chunk": [], - "end": "s39_18_0", - "start": "s39_18_0" - } - -This will block waiting for an incoming event, timing out after several seconds. -Even if there are no new events (as in the example above), there will be some -pagination stream response keys. The client should make subsequent requests -using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from`` -query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access -_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the -client reopens your app after a period of inactivity, you can resume from where -you got up to in the event stream. If it has been a long period of inactivity, -there may be LOTS of events waiting for the user. In this case, you may wish to -get all state instead and then resume getting live state from a newer end token. - -NB: The timeout can be changed by adding a ``timeout`` query parameter, which is -in milliseconds. A timeout of 0 will not block. - - -Example application -------------------- -The following example demonstrates registration and login, live event streaming, -creating and joining rooms, sending messages, getting member lists and getting -historical messages for a room. This covers most functionality of a messaging -application. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/example_app diff --git a/supporting-docs/guides/2015-08-14-getting_involved.md b/supporting-docs/guides/2015-08-14-getting_involved.md index 162ee7b8631..ff6637c736f 100644 --- a/supporting-docs/guides/2015-08-14-getting_involved.md +++ b/supporting-docs/guides/2015-08-14-getting_involved.md @@ -13,21 +13,18 @@ You have a few options when it comes to getting involved: if you just want to us | - - -## Access Matrix via a public webclient/server - -The easiest thing to do if you want to just have a play, is to use our reference webclient and create a user on the matrix.org homeserver. You do that by visiting http://matrix.org/beta/, selecting "Create account" and choosing your userID and password on the next page. You can also add your email, but this is optional (adding it will make it easier for your friends to find your Matrix user in the future). +We very much welcome [contributions](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst) to any of our projects, which you can find in our [github space](https://github.com/matrix-org/). -| +| -At the bottom of the account creation page, you can pick the homeserver and identity server you want to use. In this case, we are using matrix.org's homeserver, so just leave it as-is. Your full Matrix-userID will be formed partly from the hostname your server is running as, and partly from an userID you specify when you create the account. For example, if you put bob as your userID, your full Matrix-userID will be @bob:matrix.org ("bob on matrix.org"). + -| +## Access Matrix via a public webclient/server -You can use multiple clients with the same user, so you might want to also look at our [Android](https://github.com/matrix-org/matrix-android-console) or [iOS](https://github.com/matrix-org/matrix-ios-console) clients for your mobile phone! - -| +The easiest thing to do if you want to just have a play, is to use the [Riot.im +webclient](https://riot.im). You can use it as a guest, or register for an +account. For details of other clients, see +[https://matrix.org/blog/try-matrix-now](https://matrix.org/blog/try-matrix-now). @@ -37,9 +34,10 @@ You can clone our open source projects and run clients and servers yourself. Her ### Running your own client: -You can run your own Matrix client; there are [numerous clients available](https://matrix.org/blog/try-matrix-now/). You can take Matrix.org's [reference client](https://github.com/matrix-org/matrix-angular-sdk) and use it as-is - or modify it any way you want! Since it's written in JavaScript, running a client is [really easy](https://github.com/matrix-org/matrix-angular-sdk#running)! - -| +You can run your own Matrix client; there are [numerous clients +available](https://matrix.org/blog/try-matrix-now/). You can easily [run your +own copy](https://github.com/vector-im/vector-web#getting-started) of the +Riot.im web client. ### Running your own homeserver: @@ -65,16 +63,23 @@ You can also implement your own client or server - after all, Matrix is at its c ### Write your own client: -The [client-server API spec](http://matrix.org/docs/howtos/client-server.html) describes what API calls are available to clients, but a quick step-by-step guide would include: +The [client-server API +spec](http://matrix.org/docs/spec/client_server/latest.html) describes what API +calls are available to clients, and there is a [HOWTO +guide](http://matrix.org/docs/guides/client-server.html) which provides an +introduction to using the API along with some common operations. A quick +step-by-step guide would include: | 1. Get a user either by registering your user in an existing client or running the [new-user script](https://github.com/matrix-org/synapse/blob/master/scripts/register_new_matrix_user) if you are running your own Synapse homeserver. -2. Assuming the homeserver you are using allows logins by password, log in via the login API: -``` -curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login" -``` +2. Assuming the homeserver you are using allows logins by password, log in via the login API: + + ``` + curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login" + ``` + 3. If successful, this returns the following, including an `access_token`: { @@ -83,38 +88,49 @@ curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpa "user_id": "@example:localhost" } -4. This ``access_token`` will be used for authentication for the rest of your API calls. Potentially the next step you want is to make a call to the initialSync API and get the last x events from each room your user is in (via the limit parameter): -``` -curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN" -``` +4. This ``access_token`` will be used for authentication for the rest of your API calls. Potentially the next step you want is to make a call to the sync API and get the last few events from each room your user is in: -5. In addition to the last x events for all the rooms the user is interested in, this returns all the presences relevant for these rooms. Once you know which rooms the client has previously interacted with, you need to listen for incoming events. This can be done like so: -``` -curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN" -``` + ``` + curl -XGET "http://localhost:8008/_matrix/client/r0/sync?access_token=YOUR_ACCESS_TOKEN" + ``` -6. This request will block waiting for an incoming event, timing out after several seconds if there is no event, returning something like this: +5. The above will return something like this: { - "chunk": [], - "end": "s39_18_0", - "start": "s39_18_0" + "next_batch": "s72595_4483_1934", + "rooms": { + "join": { + "!726s6s6q:example.com": { + "state": { + "events": [ + ... + ] + }, + "timeline": { + "events": [ + ... + ] + } + }, + ... + } + } } -7. Even if there are no new events (as in the example above), there will be some pagination stream response keys. The client should make subsequent requests using the value of the "end" key (in this case s39_18_0) as the from query parameter e.g. -``` -http://localhost:8008/_matrix/client/api/v1/events?access _token=YOUR_ACCESS_TOKEN&from=s39_18_0 -``` -8. This ensures that you only get new events. Now you have initial rooms and presence, and a stream of events - a good client should be able to process all these events and present them to the user. And potentially you might want to add functionality to generate events as well (such as messages from the user, for example) - again please consult the [client-server API spec](http://matrix.org/docs/howtos/client-server.html)! + You can then use the "next_batch" token to start listening for new events like so: + + ``` + curl -XGET "http://localhost:8008/_matrix/client/r0/sync?access_token=YOUR_ACCESS_TOKEN&since=s72595_4483_1934" + ``` -| +6. This request will block waiting for an incoming event, timing out after several seconds if there is no event. This ensures that you only get new events. Now you have the initial room states, and a stream of events - a good client should be able to process all these events and present them to the user. And potentially you might want to add functionality to generate events as well (such as messages from the user, for example)! ### Write your own server: -We are still working on the server-server spec, so the best thing to do if you are interested in writing a server, is to come talk to us in [#matrix:matrix.org](https://matrix.org/beta/#/room/%23matrix:matrix.org). - -If you are interested in how federation works, please see the [federation API chapter in the spec](http://matrix.org/docs/spec/#federation-api). +We are still working on the server-server spec, so the best thing to do if you are interested in writing a server, is to come talk to us in [#matrix:matrix.org](https://matrix.to/#/#matrix:matrix.org). + +If you are interested in how federation works, please see the [Server-Server API spec](http://matrix.org/docs/spec/server_server/unstable.html). | diff --git a/supporting-docs/guides/2015-08-19-faq.md b/supporting-docs/guides/2015-08-19-faq.md index ff30827c779..78600ab169f 100644 --- a/supporting-docs/guides/2015-08-19-faq.md +++ b/supporting-docs/guides/2015-08-19-faq.md @@ -4,7 +4,7 @@ title: FAQ date: 2015-08-19 16:58:07 categories: guides --- - + # FAQ {:.no_toc} @@ -19,7 +19,9 @@ Categories [Standard](#standard) -[Implementations](#implementations) +[Servers](#servers) + +[Clients](#clients) | @@ -38,7 +40,7 @@ FAQ Content Matrix is an open standard for interoperable, decentralised, real-time communication over IP. It can be used to power Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication - or anywhere -you need a standard HTTP API for publishing and subscribing to +you need a standard HTTP API for publishing and subscribing to data whilst tracking the conversation history. | @@ -78,7 +80,7 @@ Matrix provides: - The actual ecosystem and community of everyone running Matrix servers and services - Loads of 3rd party contributions of clients, SDKs, servers and services. -You can find the full list of Matrix enabled projects at https://matrix.org/blog/try-matrix-now. +You can find the full list of Matrix enabled projects at [https://matrix.org/blog/try-matrix-now](https://matrix.org/blog/try-matrix-now). ##### What does this mean for users? @@ -90,24 +92,40 @@ number to discover people to talk to. ##### What kind of company is Matrix.org? -Matrix is an open initiative which acts as a neutral custodian of the -Matrix standard. It's not actually incorporated anywhere at the moment -but we are looking at the best legal structure for the future (and as -of October 2015 we have hopefully found one). Whatever the legal -structure, we are committed to keeping the Matrix project open. +Matrix.org is an open initiative which acts as a neutral and independent custodian +of the Matrix standard. As of Sept 2017 we are finally in the process of incorporating +it as a dedicated non-profit entity (most likely a limited by guarantee UK private company +called the Matrix.org Foundation). ##### Who is funding Matrix.org? -Most of the current core contributors to Matrix work at -[Amdocs](http://amdocs.com), who have kindly given us permission to work -on Matrix as an independent non-profit initiative. Other contributors +Matrix.org is currently (Sept 2017) funded by the community, through a +combination of community support (via +[Patreon](https://patreon.com/matrixdotorg), +[Liberapay](https://liberapay.org/matrixdotorg), Bitcoin and Ethereum), +corporate sponsorship, and grant funding. Current Elliptic-level supporters on +Patreon and corporate sponsors can be found on our [supporters +page](https://matrix.org/blog/supporters). If you would like to support the core +Matrix team as a member of the community, please visit our +[Patreon](https://patreon.com/matrixdotorg) or +[Liberapay](https://liberapay.org/matrixdotorg) pages, or you can send us +Bitcoin at 1LxowEgsquZ3UPZ68wHf8v2MDZw82dVmAE or Ethereum at ETH +0xA5f9a4f9E024F6D727f7afdA9257e22329A97485. If you would like to sponsor the +team as a corporation, or are interested in paying for prioritised or custom +development, please [get in touch](mailto:matthew@matrix.org). + +For the first three years of Matrix's development (2014-2017), most of the core +contributors worked for [Amdocs](https://www.amdocs.com), who paid for them to +work fulltime on Matrix. In July 2017, Amdocs considered the project to be +sufficiently successful that it could now self-support and so stopped funding. +The majority of the core team is now employed by New Vector, an independent company +set up to hire the team and support Matrix's development. Other contributors are funded by their own employers or donate their own time to the project. ##### Who is building Matrix? -The core team is ~10 people with extensive experience in building custom -VoIP and Messaging apps for mobile network operators. Most of us have -day jobs at [Amdocs](http://amdocs.com) or [OpenMarket](http://openmarket.com), +The core team is ~12 people with extensive experience in building custom +VoIP and Messaging apps for mobile network operators. Most of us work for New Vector, but there are an increasing number of contributors from other companies and folks all over the internet. @@ -215,22 +233,24 @@ bridges as the project progresses. The Matrix team used XMPP (Openfire, ejabberd, spectrum, asmack, XMPPFramework) for IM before starting to experiment with open HTTP APIs as an alternative in around 2012. The main issues with XMPP that -drove us in this direction were: +drove us in this direction **as of 2012** were: - Not particularly web-friendly - you can't easily speak XMPP from a - web browser. (N.B. Nowadays you have options like XMPP-FTW and - Stanza.io that help loads with letting browsers talk XMPP). + web browser. *N.B. Nowadays you have options like XMPP-FTW and + Stanza.io that help loads with letting browsers talk XMPP* - Single logical server per MUC is a single point of control and - availability. (MUCs can be distributed over multiple physical + availability. *MUCs can be distributed over multiple physical servers, but they still sit behind a single logical JID and domain. FMUC improves this with a similar approach to Matrix, but as of Oct - 2015 there are no open source implementations.) + 2015 there are no open source implementations. The MIX XMPP extension + also aims to address this limitation*. - History synchronisation is very much a second class citizen feature -- Stanzas aren't framed or reliably delivered without extensions. (See +- Bridging to other protocols and defragmenting existing communication + apps and networks is very much a second class citizen feature +- Stanzas aren't framed or reliably delivered without extensions. *See [wiki.xmpp.org](http://wiki.xmpp.org/web/Myths#Myth_Four:_XMPP_is_unreliable_without_a_bunch_of_extensions.) - for an XMPP take on this) -- Multiple device support is limited. (Apparently Carbons and MAM help - with this) + for an XMPP take on this* +- Multiple device support is limited. *Carbons and MAM aim to resolve this* - Baseline feature set is so minimal that fragmentation of features between clients and servers is common, especially as interoperability profiles for features have fallen behind (as of July 2015) @@ -238,13 +258,13 @@ drove us in this direction were: count X.509 certs, which [are questionable](http://www.thoughtcrime.org/blog/ssl-and-the-future-of-authenticity/)) - Not particularly well designed for mobile use cases: push; - bandwidth-efficient transports. (Since the time of writing a [Push + bandwidth-efficient transports. *Since the time of writing a [Push XEP has appeared](http://xmpp.org/extensions/xep-0357.html), and [wiki.xmpp.org](http://wiki.xmpp.org/web/Myths#Myth_Three:_It.27s_too_bandwidth-inefficient_for_mobile.) - claims that XMPP runs "fine" over a 9600bps + 30s latency link.) + states that XMPP is usable over a 9600bps + 30s latency link.* -The whole subject of XMPP vs Matrix seems to bring out the worst in -people. Rather than fighting over which open interoperable communication +This said, the whole area of XMPP vs Matrix is quite subjective. +Rather than fighting over which open interoperable communication standard works the best, we should just collaborate and bridge everything together. The more federation and interoperability the better. @@ -261,7 +281,7 @@ functionality. If XMPP does what you need it to do, then we're genuinely happy for you :) Meanwhile, rather than competing, an XMPP Bridge like [Skaverat's xmpptrix beta](https://github.com/SkaveRat/xmpptrix) or [jfred's matrix-xmpp-bridge](https://github.com/jfrederickson/matrix-xmpp-bridge) -or Matrix.org's own [matrix-appservice-purple](https://github.com/matrix-org/matrix-appservice-purple) +or Matrix.org's own [purple-matrix](https://github.com/matrix-org/purple-matrix/) has potential to let both environments coexist and make the most of each other's benefits. @@ -278,18 +298,18 @@ conversation history rather than just decentralised chat servers. On the other hand, Matrix doesn't provide the metadata protection guarantees that GNUnet/PSYC aims for. -| +| See [http://about.psyc.eu/Matrix](http://about.psyc.eu/Matrix) for PSYC's views on Matrix. ##### What is the difference between Matrix and Tox? -Tox.im looks to be a very cool clone of Skype - a fully decentralised +Tox.chat looks to be a very cool clone of Skype - a fully decentralised peer-to-peer network.  Matrix is deliberately not a 'pure' peer-to-peer system; instead each user has a well-defined homeserver which stores his data and that he can depend upon.  Matrix provides HTTP APIs; -Tox.im provides C APIs. As of October 2015 Tox doesn't seem to have an +Tox.chat provides C APIs. As of October 2015 Tox doesn't seem to have an answer yet for decentralised conversation history storage. ##### How does Matrix compare with something like Trillian or Pidgin? @@ -311,7 +331,7 @@ a non-Matrix client from one of the networks which has been bridged in). ##### What Matrix compliant apps are there? -Quite a few, ranging from the glossy mass-market to the geeky command-line. There's even an emacs macro. Check out [https://matrix.org/blog/try-matrix-now] for the current +Quite a few, ranging from the glossy mass-market to the geeky command-line. There's even an emacs macro. Check out [https://matrix.org/blog/try-matrix-now](https://matrix.org/blog/try-matrix-now) for the current list of Matrix enabled projects. ##### What bridges to other networks are available? @@ -319,12 +339,12 @@ list of Matrix enabled projects. The number of 'bridges' which integrate existing communication networks into Matrix are growing on a daily basis - both written by the Matrix core team and contributed by the wider community. The full list can be seen at -https://matrix.org/blog/try-matrix-now, but the core ones as of Oct 2015 include: +[https://matrix.org/blog/try-matrix-now](https://matrix.org/blog/try-matrix-now), but the core ones as of Oct 2015 include: * [matrix-appservice-irc](https://github.com/matrix-org/matrix-appservice-irc) - an increasingly comprehensive Matrix\<-\>IRC bridge * [matrix-appservice-verto](https://github.com/matrix-org/matrix-appservice-verto) - links from Matrix to FreeSWITCH via the Verto protocol * [matrix-appservice-slack](https://github.com/matrix-org/matrix-appservice-slack) - a basic bridge to Slack - * [matrix-appservice-purple](https://github.com/matrix-org/matrix-appservice-purple) - lets you access any of the 20+ protocols supported by + * [node-purple](https://github.com/matrix-org/node-purple) - lets you access any of the 20+ protocols supported by [libpurple](https://developer.pidgin.im/wiki/WhatIsLibpurple), including Skype, Lync, XMPP, etc) * [matrix-appservice-bridge](https://github.com/matrix-org/matrix-appservice-bridge) - a general NodeJS framework for writing bridges @@ -352,7 +372,7 @@ standard body to maintain it going forwards. ##### How do I get an account and get started? -The quickest way is to pick a client from https://matrix.org/blog/try-matrix-now and sign up. +The quickest way is to pick a client from [https://matrix.org/blog/try-matrix-now](https://matrix.org/blog/try-matrix-now) and sign up. Please note that you can point clients to access any homeserver - you don't have to use matrix.org, although as of day 1, matrix.org is the only communal homeserver available. @@ -364,7 +384,7 @@ letting the user interact with users and rooms anywhere within the Matrix federation.  Text and image messages are supported, and basic voice-only VoIP calling via WebRTC is supported in one-to-one rooms. (As of October 2015, experimental multi-way calling is also available -on Vector.im). +on Riot.im). ##### How do I connect my homeserver to the public Matrix network? @@ -386,7 +406,7 @@ If you already have communication infrastructure set up (XMPP, custom HTTP, or w then you'll want to run a bridge to expose it to the wider Matrix ecosystem. See [matrix-appservice-bridge HOWTO](https://github.com/matrix-org/matrix-appservice-bridge/blob/master/HOWTO.md) for a guide of how to write bridges using the matrix-appservice-bridge framework, or co-opt one -from the list at https://matrix.org/blog/try-matrix-now. +from the list at [https://matrix.org/blog/try-matrix-now](https://matrix.org/blog/try-matrix-now). [Application Service API](/docs/spec/#application-service-api) gives the details of the API that bridges have to implement. @@ -399,7 +419,7 @@ to write a client. ##### How can I help out with this? -Come say hi on #matrix:matrix.org! Install synapse and tell us how you get on. Critique the spec.  Write +Come say hi on [\#matrix:matrix.org](https://matrix.to/#/#matrix:matrix.org)! Install synapse and tell us how you get on. Critique the spec.  Write clients. Write bridges! Run bridges! Nose around in [Jira](https://matrix.org/jira) and send us some pull requests on github to fix some bugs or add some features! You could even try to write a homeserver (but be warned, Matrix's architecture makes homeservers orders of @@ -410,19 +430,19 @@ full details on how to contribute to the project. All are welcome! ##### Where can I get support? -\#matrix:matrix.org aka \#matrix on irc.freenode.is your best bet. +[\#matrix:matrix.org](https://matrix.to/#/#matrix:matrix.org) aka \#matrix on irc.freenode.is your best bet. ##### How do I register custom matrix event types? We're not yet managing a registry of custom matrix event types.  If you have any particularly good ones you want to tell the world about, please -use the [mailing list](/mailman/listinfo/matrix-users) for now. +let us know on #matrix-dev:matrix.org. ##### How mature is this? We started working on Matrix in July 2014, and opened it to the public in September 2014. We got all the core features in place in December 2014 -and entered beta, and since then have been iterating away on the beta refining the +and entered beta, and since then have been iterating away on the beta refining the architecture and APIs, fixing bugs and scalability, and adding new features, clients, bridges etc. @@ -445,6 +465,14 @@ Sure. An ever increasing number of protocols are being bridged into Matrix, so i A user's clients connect to a single homeserver, which stores the communication history and account information for that user, and shares data with the wider Matrix ecosystem by synchronising communication history with other homeservers. +##### What is a MXID? + +Matrix user IDs (MXID) are unique user IDs. They are in the format ```@username:homeserver.tld``` (this format is used to avoid confusing them with email addresses). They are intended to be fairly hidden (although right now they are not) - instead you will find and identify other users via 3PIDs. + +##### What is a 3PID? + +Third-party IDs (3PIDs) are IDs from other systems or contexts, such as email addresses, social network accounts and phone numbers. + ##### What is an identity server? Users in Matrix are identified internally via their matrix user ID (MXID). However, existing 3rd party ID (3PID) namespaces such as email addresses or phone numbers should be used publically to identify Matrix users, at least for invitation purposes. A Matrix "Identity" describes both the user ID and any other existing IDs from third party namespaces linked to their account. @@ -466,13 +494,23 @@ The precise architecture of identity servers is currently in flux and subject to Each homeserver stores the communication history and account information for all of its clients, and shares data with the wider Matrix ecosystem by synchronising communication history with other homeservers and their clients. Clients typically communicate with each other by emitting events in the context of a virtual room. Room data is replicated across all of the homeservers *whose users are participating in a given room*. -##### What is a 3PID? +##### What are redactions? -Third-party IDs (3PIDs) are IDs from other systems or contexts, such as email addresses, social network accounts and phone numbers. +Since events are extensible it is possible for malicious users and/or servers to add keys that are, for example offensive or illegal. Since some events cannot be simply deleted (e.g. membership events) we instead 'redact' events, essentially stripping the event of all keys that are not required by the protocol. Redacting an event cannot be undone, allowing server owners to also delete the offending content from the databases. ##### How do you do VoIP calls on Matrix? -Voice (and video) over Matrix uses the WebRTC 1.0 standard to transfer call media (i.e. the actual voice and video traffic). Matrix is used to signal the establishment and termination of the call by sending call events, like any other event. Currently calls are only supported in rooms with exactly two participants - however, one of those participants may be a conferencing bridge. We're looking at better ways to do group calling. +Voice (and video) over Matrix uses the WebRTC 1.0 standard to transfer call media (i.e. the actual voice and video traffic). Matrix is used to signal the establishment and termination of the call by sending call events, like any other event. + +##### Are VoIP calls encrypted? + +WebRTC encrypts the media that's being sent. The signalling events that set up (and end) the call are encrypted if the room they were sent in has enabled encryption. + +##### Do I need a TURN server? + +VoIP calls should work if both parties are on public networks. However, in practice one (or both) devices are often behind NAT, and so having a [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) server is important to help set up the call. + +See [this guide](https://github.com/matrix-org/synapse/blob/master/docs/turn-howto.rst) for setting up a TURN server with Synapse. ##### Can I log into other homeservers with my username and password? @@ -484,17 +522,27 @@ The Apache Licence is a permissive licence. We want the Matrix protocol itself t ##### Can I write a Matrix homeserver? -Yes. Matrix is just a spec, so implementations of the spec are very welcome! It should be noted that as of October 2015, changes are still being made to the spec, so if you want to write a Matrix homeserver, it is strongly recommended that you chat to the Matrix.org devs in #matrix:matrix.org first! You can also read about the [Federation API here]( https://github.com/matrix-org/matrix-doc/blob/master/specification/30_server_server_api.rst). +Yes. Matrix is just a spec, so implementations of the spec are very welcome! It should be noted that as of October 2015, changes are still being made to the spec, so if you want to write a Matrix homeserver, it is strongly recommended that you chat to the Matrix.org devs in [\#matrix:matrix.org](https://matrix.to/#/#matrix:matrix.org) first! You can also read about the [Federation API here](https://matrix.org/docs/spec/server_server/unstable.html). ##### How secure is this? -Server-server traffic is mandatorily TLS from the outset. Server-client traffic mandates transport layer encryption other than for tinkering. Servers maintain a public/private key pair, and sign the integrity of all messages in the context of the historical conversation, preventing tampering. Server keys are distributed using a PERSPECTIVES-style system. +Server-server traffic is mandatorily TLS from the outset. Server-client traffic mandates transport layer encryption other than for tinkering. Servers maintain a public/private key pair, and sign the integrity of all messages in the context of the historical conversation, preventing tampering. Server keys are distributed using a [Perspectives](https://perspectives-project.org/)-style system. -End-to-end encryption is coming shortly to clients for both 1:1 and group chats to protect user data stored on servers, using the [Olm](https://matrix.org/git/olm) cryptographic ratchet implementation. As of October 2015 this is blocked on implementing the necessary key distribution and fingerprint management. +End-to-end encryption is now available in the various [Riot.im](https://Riot.im) builds! This allows you to encrypt both 1:1 and group chats to protect user data stored on servers, using the [Olm](https://matrix.org/git/olm) cryptographic ratchet implementation. Read more on the [blog post](https://matrix.org/blog/2016/11/21/matrixs-olm-end-to-end-encryption-security-assessment-released-and-implemented-cross-platform-on-riot-at-last/) that announced the feature! Privacy of metadata is not currently protected from server administrators - a malicious homeserver administrator can see who is talking to who and when, but not what is being said (once E2E encryption is enabled). See [this presentation from Jardin Entropique](http://matrix.org/~matthew/2015-06-26%20Matrix%20Jardin%20Entropique.pdf) for a more comprehensive discussion of privacy in Matrix. -### Implementations +##### What is Perspectives? + +Rather than relying on Certificate Authorities (CAs) as in traditional SSL, a [Perspectives](https://perspectives-project.org/)-style system uses a more decentralized model for verifying keys. Perspectives uses notary servers to verify that the same key is seen across the network, making a man-in-the-middle attack much harder since an attacker must insert itself into multiple places. For federation in Matrix, each Home Server acts as a notary. When one Home Server connects to another Home Server that uses a key that it doesn't recognize, it contacts other Home Servers to ensure that they all see the same key from that Home Server. + +##### Why HTTP? Doesn't HTTP suck? Why don't you use websockets/CoAP/HTTP2/etc? + +HTTP is indeed not the most efficient transport, but it is ubiquitous, very well understood and has numerous implementations on almost every platform and language. It also has a simple upgrade path to HTTP/2, which is relatively bandwidth and round-trip efficient. + +It has thus been chosen as the mandatory baseline of the exchange, but it is still entirely possible to use more fancy protocols for communication between clients and server (see for example this [websocket transport draft](https://github.com/matrix-org/matrix-doc/blob/master/drafts/websockets.rst)), and it's also possible in the future that negotiation of more efficient protocols will be added for the federation between servers, with HTTP+JSON remaining as the compability baseline. + +### Servers ##### What is Synapse? @@ -535,29 +583,84 @@ Synapse will use as much RAM as you give it in order to cache conversations in R | -For better performance, one should back Synapse with a Postgres database rather than the default SQLite - see https://github.com/matrix-org/synapse/tree/master/README.rst#using-postgresql for details. +For better performance, one should back Synapse with a Postgres database rather than the default SQLite - see [https://github.com/matrix-org/synapse/tree/master/README.rst#using-postgresql](https://github.com/matrix-org/synapse/tree/master/README.rst#using-postgresql) for details. ##### Why is Synapse in Python/Twisted? -This is because both provide a mature and well known event-driven async IO framework for writing serverside code. Whilst this has been okay for our initial experimentation and proof of concept, it's likely that future homeserver work will be written in a more strongly typed language (e.g. Go). +This is because both provide a mature and well known event-driven async IO framework for writing serverside code. Whilst this has been okay for our initial experimentation and proof of concept, it's likely that future homeserver work will be written in a more strongly typed language (e.g. Go). ##### Why aren't you using an ORM layer like SqlAlchemy in Synapse? Synapse is *very* database dependent (as of Oct 2015; this is improving in the near future however), and we like having the flexibility to sculpt our own queries. +##### Will Synapse share my chat data with other servers in the federation? + +Data is only shared between servers of participating users of a room. If all users in a room are on your server, no data is shared with other servers. + +##### Why can't I rename my homeserver? + +Currently, the homeserver name is assumed never to change. This means that if you rename your server, other servers will think it's a different server. + +Perhaps in the future we will add an API for changing the homeserver name, but for now this is not supported. + +### Clients + ##### Where can I find a mobile app? -The "Matrix Console" reference apps (ugly, geeky and powerful - intended for early adopter powerusers) can be downloaded from the [Google Play store](https://play.google.com/store/apps/details?id=org.matrix.androidsdk.alpha) - and [Apple store](https://itunes.apple.com/gb/app/matrix-console/id970074271). +Riot is available for Android and iOS. -| +The iOS version can be downloaded from the [Apple store](https://itunes.apple.com/us/app/vector.im/id1083446067). + +The Android version can be downloaded from the [Google Play store](https://play.google.com/store/apps/details?id=im.vector.alpha) or [F-Droid](https://f-droid.org/repository/browse/?fdid=im.vector.alpha). If you are not sure which one to choose, install Riot from the [Google Play store](https://play.google.com/store/apps/details?id=im.vector.alpha). -For the Android app, you can also install the latest development version -built by [Jenkins](http://www.matrix.org/jenkins/job/AndroidConsoleDevelop/lastBuild/artifact/console/build/outputs/apk/console-alpha-debug.apk). +For the Android app, you can also install the latest development version +built by [Jenkins](http://matrix.org/jenkins/job/VectorAndroidDevelop). Use it at your own risk and only if you know what you are doing. + +##### I installed Riot via F-Droid, why is it draining my battery? + +The F-Droid release of Riot does not use [Google Cloud Messaging](https://developers.google.com/cloud-messaging/). This allows users that do not have or want Google Services installed to use Riot. + +The drawback is that Riot has to pull for new messages, which can drain your battery. To counter this, you can change the delay between polls in the settings. Higher delay means better battery life (but may delay receiving messages). You can also disable the background sync entirely (which means that you won't get any notifications at all). + +If you don't mind using Google Services, you might be better off installing the [Google Play store](https://play.google.com/store/apps/details?id=im.vector.alpha) version. ##### Where can I find a web app? -As of Oct 2015, the best web app options are to use [Vector.im](https://vector.im) - a glossy web client written on top of [matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk), or the original [AngularJS based client](https://matrix.org/beta), which has serious performance problems and is not currently being maintained. In future a "Matrix Console" reference web app built on matrix-react-sdk will be released by matrix.org to complement the mobile apps above. +You can use [Riot.im](https://Riot.im) - a glossy web client written on top of [matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk). + +You can also run Vector, the code that Riot.im uses, on your own server. It's a static web application, just download the [last release](https://github.com/vector-im/vector-web/) and unpack it. + +##### Where can I find a desktop client? + +You can use the desktop build of [Riot.im](https://riot.im/desktop.html). + +There are also other desktop clients - check the list of clients on [matrix.org](http://matrix.org/docs/projects/try-matrix-now.html#clients). + +##### Why can't end-to-end encryption be turned off? + +When encryption is enabled in a room, a flag is set in the room state, so that +all clients know to encrypt any messages they send. The room state stores +information about the room like the topic, the avatar, and the membership list. + +Imagine if encryption could be turned off the same way as it is turned +on. Anyone with admin rights in the room could clear the flag and then messages +would start being transmitted unencrypted. It would be very easy for a user to +miss the change in configuration, and accidentally send a sensitive message +without encryption. + +Worse yet, anyone with sysadmin access to a server could also clear the flag +(remember that the main reason for using e2e encryption is that we don't trust +the sysadmins), and could then easily read any sensitive content which was +sent. + +The solution we have taken for now is to make clients ignore any requests to +disable encryption. We might experiment with ways to improve this in the future +- for instance, by alerting the user next time they try to send a message in +the room if encryption has been disabled. + +##### Why isn't end-to-end encryption enabled by default? + +We are deliberately keeping E2E opt-in during the beta as there is a small risk of undecryptable messages, and we don’t want to lull folks into a false sense of security. As soon as we are out of beta, we will turn E2E on for any room with private history by default. Another consideration is to give other clients a chance to catch up with E2E support before it's used by default. | @@ -565,13 +668,11 @@ As of Oct 2015, the best web app options are to use [Vector.im](https://vector.i This FAQ is a constant work in progress - patches and pull requests are *very* welcome to help us improve it. Some of the frequent questions where we need to write an answer include: - * How do I rename servers? * How do I change the TLS key of my server? * How do I maintain my synapse's DB (e.g. prune old conversations)? * How do I maintain my synapse's content repository (e.g. prune old content)? - * What are redactions? - * Why is the spec so big, especially relative to the XMPP baseline spec? - * How do I contribute to the spec? + * Why is the spec so big, especially relative to the XMPP baseline spec? + * How do I contribute to the spec? * What is the privacy policy on Matrix.org? * How precisely does E2E work? * How does Matrix actually work architecturally? @@ -581,13 +682,10 @@ This FAQ is a constant work in progress - patches and pull requests are *very* w * What's on the roadmap? * How can I use Matrix to talk on Freenode or other IRC networks? * Where can I learn more about Matrix? (link to PDFs of other presentations etc) - * Why HTTP? Doesn't HTTP suck? - * Why don't you use websockets? * Why is synapse so resource intensive immediately after federating for the first time? * \[your question goes here...\] | Any other questions? Please contact us in -[\#matrix:matrix.org](https://matrix.org/beta/#/room/%23matrix:matrix.org) or the [mailing -lists](/mailman/listinfo/matrix-users)! +[\#matrix:matrix.org](https://matrix.to/#/#matrix:matrix.org). diff --git a/supporting-docs/guides/2015-08-21-application_services.md b/supporting-docs/guides/2015-08-21-application_services.md index dd532d42347..7e52fc85979 100644 --- a/supporting-docs/guides/2015-08-21-application_services.md +++ b/supporting-docs/guides/2015-08-21-application_services.md @@ -6,7 +6,7 @@ categories: guides # Application services -Application services are distinct modules which which sit alongside a home server providing arbitrary extensible functionality decoupled from the home server implementation. Just like the rest of Matrix, they communicate via HTTP using JSON. Application services function in a very similar way to traditional clients, but they are given much more power than a normal client. They can reserve entire namespaces of room aliases and user IDs for their own purposes. They can silently monitor events in rooms, or any events directed at any user ID. This power allows application services to have extremely useful abilities which overall enhance the end user experience. +Application services are distinct modules which which sit alongside a homeserver providing arbitrary extensible functionality decoupled from the homeserver implementation. Just like the rest of Matrix, they communicate via HTTP using JSON. Application services function in a very similar way to traditional clients, but they are given much more power than a normal client. They can reserve entire namespaces of room aliases and user IDs for their own purposes. They can silently monitor events in rooms, or any events directed at any user ID. This power allows application services to have extremely useful abilities which overall enhance the end user experience. | @@ -47,17 +47,17 @@ At present, the IRC application service is in beta, and is being run on #matrix | # What Application services can do for you -Application services have enormous potential for creating new and exciting ways to transform and enhance the core Matrix protocol. For example, you could aggregate information from multiple rooms into a summary room, or create throwaway virtual user accounts to proxy messages for a fixed user ID on-the-fly. As you may expect, all of this power assumes a high degree of trust between application services and home servers. Only home server admins can allow an application service to link up with their home server, and the application service is in no way federated to other home servers. You can think of application services as additional logic on the home server itself, without messing around with the book-keeping that home servers have to do. This makes adding useful functionality very easy. +Application services have enormous potential for creating new and exciting ways to transform and enhance the core Matrix protocol. For example, you could aggregate information from multiple rooms into a summary room, or create throwaway virtual user accounts to proxy messages for a fixed user ID on-the-fly. As you may expect, all of this power assumes a high degree of trust between application services and homeservers. Only homeserver admins can allow an application service to link up with their homeserver, and the application service is in no way federated to other homeservers. You can think of application services as additional logic on the homeserver itself, without messing around with the book-keeping that homeservers have to do. This makes adding useful functionality very easy. | ### Example -The application service (AS) API itself uses webhooks to communicate from the home server to the AS: +The application service (AS) API itself uses webhooks to communicate from the homeserver to the AS: -- Room Alias Query API : The home server hits a URL on your application server to see if a room alias exists. -- User Query API : The home server hits a URL on your application server to see if a user ID exists. -- Push API : The home server hits a URL on your application server to notify you of new events for your users and rooms. +- Room Alias Query API : The homeserver hits a URL on your application server to see if a room alias exists. +- User Query API : The homeserver hits a URL on your application server to see if a user ID exists. +- Push API : The homeserver hits a URL on your application server to notify you of new events for your users and rooms. A very basic application service may want to log all messages in rooms which have an alias starting with "#logged_" (side note: logging won't work if these rooms are using end-to-end encryption). @@ -85,7 +85,7 @@ Set your new application service running on port 5000 with: python app_service.py -The home server needs to know that the application service exists before it will send requests to it. This is done via a registration YAML file which is specified in Synapse's main config file e.g. homeserver.yaml. The server admin needs to add the application service registration configuration file as an entry to this file. +The homeserver needs to know that the application service exists before it will send requests to it. This is done via a registration YAML file which is specified in Synapse's main config file e.g. homeserver.yaml. The server admin needs to add the application service registration configuration file as an entry to this file. # homeserver.yaml app_service_config_files: @@ -95,6 +95,9 @@ NB: Note the "-" at the start; this indicates a list element. The registration f # registration.yaml + # An ID which is unique across all application services on your homeserver. This should never be changed once set. + id: "something-good" + # this is the base URL of the application service url: "http://localhost:5000" @@ -115,7 +118,7 @@ NB: Note the "-" at the start; this indicates a list element. The registration f - exclusive: false regex: "#logged_.*" -**You will need to restart the home server after editing the config file before it will take effect.** +**You will need to restart the homeserver after editing the config file before it will take effect.** | @@ -138,6 +141,6 @@ This makes the application service lazily create a room with the requested alias | -Application services are powerful components which extend the functionality of home servers, but they are limited. They can only ever function in a "passive" way. For example, you cannot implement an application service which censors swear words in rooms, because there is no way to prevent the event from being sent. Aside from the fact that censoring will not work when using end-to-end encryption, all federated home servers would also need to reject the event in order to stop developing an inconsistent event graph. To "actively" monitor events, another component called a "Policy Server" is required, which is beyond the scope of this post.  Also, Application Services can result in a performance bottleneck, as all events on the homeserver must be ordered and sent to the registered application services.  If you are bridging huge amounts of traffic, you may be better off having your bridge directly talk the Server-Server federation API rather than the simpler Application Service API. +Application services are powerful components which extend the functionality of homeservers, but they are limited. They can only ever function in a "passive" way. For example, you cannot implement an application service which censors swear words in rooms, because there is no way to prevent the event from being sent. Aside from the fact that censoring will not work when using end-to-end encryption, all federated homeservers would also need to reject the event in order to stop developing an inconsistent event graph. To "actively" monitor events, another component called a "Policy Server" is required, which is beyond the scope of this post.  Also, Application Services can result in a performance bottleneck, as all events on the homeserver must be ordered and sent to the registered application services.  If you are bridging huge amounts of traffic, you may be better off having your bridge directly talk the Server-Server federation API rather than the simpler Application Service API. I hope this demonstrates how easy it is to create an application service, along with a few ideas of the kinds of things you can do with them. Obvious uses include build protocol bridges, search engines, invisible bots, etc. For more information on the AS HTTP API, check out the new Application Service API section in the spec, or the raw drafts and spec in https://github.com/matrix-org/matrix-doc/. diff --git a/supporting-docs/guides/2016-01-01-index.md b/supporting-docs/guides/2016-01-01-index.md new file mode 100644 index 00000000000..01cf5e02268 --- /dev/null +++ b/supporting-docs/guides/2016-01-01-index.md @@ -0,0 +1,18 @@ +--- +layout: default +categories: guides +--- +
+ +

Guides

+ +

Here is a collection of guides that might help you get involved with Matrix.

+

First, there is the Getting Involved guide, which explains various ways of getting started with Matrix, and the FAQ, which tries to answer all your questions relating to Matrix.

+

The Client-Server API guide explains in detail how to use the CS API, which is useful if you want to write a client (or modify an existing one) - or if you're just interested in how it works "under the hood".

+

If you were using the old v1 CS API, there is also the v1 migration guide which justs lists the changes from v1 to r0.

+

Let's Encrypt Matrix explains how to use Let's Encrypt's certificates with your Synapse installation. This guide was written by William A Stevens.

+

The Application services guide introduces and explains Application services, and what they can be used for.

+

Types of Bridging should be read by all bridge developers to ensure everyone has the same mental map of terminology when implementing bridges.

+

The End-to-end Encryption Implementation Guide is intended for client developers who wish to add support for end-to-end encryption.

+ +
diff --git a/supporting-docs/guides/2016-01-05-code_of_conduct.md b/supporting-docs/guides/2016-01-05-code_of_conduct.md new file mode 100644 index 00000000000..b2ad373a7e6 --- /dev/null +++ b/supporting-docs/guides/2016-01-05-code_of_conduct.md @@ -0,0 +1,85 @@ +--- +layout: post +version: v1.0 +title: Code of Conduct +categories: guides +--- + + + +# Matrix Code of Conduct + +This code of conduct outlines our expectations for participants within the Matrix community, as well as steps for reporting unacceptable behaviour. We are committed to providing a welcoming and inspiring community for all, and expect our code of conduct to be honoured. Anyone who violates this code of conduct may be banned from the community. + +This applies to conversation in the #matrix* rooms (#matrix:matrix.org, #matrix-dev:matrix.org, #matrix-spam:matrix.org) and commits and comments relating to any project in the [matrix-org](https://github.com/matrix-org) github space. + +Our open source community strives to: + +* **Be friendly and patient.** +* **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. +* **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. +* **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behaviour and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. +* **Be careful in the words that we choose**: Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behaviour aren't acceptable. +* **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. + +| + +## Definitions + +Harassment includes, but is not limited to: + +- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation +- Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment +- Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle +- Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop +- Threats of violence, both physical and psychological +- Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm +- Deliberate intimidation +- Stalking or following +- Harassing photography or recording, including logging online activity for harassment purposes +- Sustained disruption of discussion +- Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour +- Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others +- Continued one-on-one communication after requests to cease +- Deliberate “outing” of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse +- Publication of non-harassing private communication + +| + +We will not act on complaints regarding: + +- Good faith and non-malicious conduct whose object is to ameliorate the conditions of disadvantaged individuals or groups including those that are disadvantaged because of race, national or ethnic origin, colour, religion, sex, age or mental or physical disability. +- Reasonable communication of boundaries, such as “leave me alone,” “go away,” or “I’m not discussing this with you” +- Refusal to explain or debate social justice concepts +- Communicating in a ‘tone’ you don’t find congenial +- Criticizing racist, sexist, cissexist, or otherwise oppressive behaviour or assumptions + +| + +### Diversity Statement + +We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. + +Although this list cannot be exhaustive, we explicitly honour diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected +characteristics above, including participants with disabilities. + +| + +### Reporting Issues + +If you experience or witness unacceptable behaviour — or have any other concerns — please report it by contacting us via abuse@matrix.org. All reports will be handled with discretion. In your report please include: + +- Your contact information. +- Names (usernames and nicks, real names, and/or pseudonyms) of any individuals involved. If there are additional witnesses, please +include them as well. Your account of what occurred, and if you believe the incident is ongoing. +- The date and time of the incident (or start of incident). +- Any additional information that may be helpful. + +After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse. + +| + +### Attribution & Acknowledgements + +This Code of Conduct is based on the [TODO Group](https://twitter.com/todogroup)'s [Open Code of Conduct template](https://github.com/todogroup/opencodeofconduct), but with some modifications. + diff --git a/supporting-docs/guides/2016-03-15-lets-encrypt.rst b/supporting-docs/guides/2016-03-15-lets-encrypt.rst new file mode 100644 index 00000000000..824e2ed2074 --- /dev/null +++ b/supporting-docs/guides/2016-03-15-lets-encrypt.rst @@ -0,0 +1,37 @@ +--- +layout: post +title: Let's Encrypt Matrix +categories: guides +--- + +==================== +Let's Encrypt Matrix +==================== + +Let's Encrypt is a free Certificate Authority that makes it easy to secure your server's internet traffic. This makes it really easy to secure your Matrix homeserver, and this guide will explain exactly how you do this. Guide written by William A Stevens - thanks! + +0: Prerequisites +================ +* Install Synapse_. +* Install (or Download) certbot_ + +1: Get certificates +=================== +When executing the Let's Encrypt client, it will ask for the domain name of your server, and your email address. The domain list can include multiple names and should include any domain you want to access the server from. + +Also, the certificates will be in a folder under /etc/letsencrypt (see below) and owned by root. + +:: + +# certbot certonly --standalone + +A note about renewal +-------------------- +These certificates will expire in 3 months. To renew certificates, run ```certbot renew```. It is recommended to create a cronjob, which attempts renewal twice a day. Depending on your distribution, that could be already configured. + +2: Install Certificates +======================= +At the top of your homeserver.yaml there should be two keys, ```tls_certificate_path``` and ```tls_private_key_path```. These should be changed so that instead of pointing to the default keys, they now point to the Let's Encrypt keys. ```tls_certificate_path``` should point to ```/etc/letsencrypt/live/(your domain name)/fullchain.pem```. ```tls_private_key_path``` should point to ```/etc/letsencrypt/live/(your domain name)/privkey.pem```. ```tls_dh_params_path``` can stay the same as before. + +.. _Synapse: https://github.com/matrix-org/synapse/blob/master/README.rst#synapse-installation +.. _certbot: https://certbot.eff.org/ diff --git a/supporting-docs/guides/2016-05-05-client-server-migrating-from-v1.rst b/supporting-docs/guides/2016-05-05-client-server-migrating-from-v1.rst new file mode 100644 index 00000000000..e35191a2c78 --- /dev/null +++ b/supporting-docs/guides/2016-05-05-client-server-migrating-from-v1.rst @@ -0,0 +1,114 @@ +--- +layout: post +title: Migrating from Client Server API v1 +categories: guides +--- + +Migrating from client-server API v1 +=================================== + +This guide assists developers of API clients that target the ``v1`` version of +the API to migrate their code to the later ``r0``. It does not aim to introduce +new concepts that were added in ``r0`` except where these replace things that +were removed since ``v1``. + +Updated Version In Path +======================= + +The new version of the API is ``r0``; this should be used in paths where +``v1`` used to appear. Additionally, the ``/api`` path component has now been +removed. API endpoint paths are now:: + + POST /_matrix/client/r0/register + GET /_matrix/client/r0/login + etc... + +New Registration and Login Endpoints +==================================== + +The ``r0`` version of the ``/register`` and ``/login`` endpoints is different +to the ``v1`` version. See the updated API documentation for details on how the +new API works. In brief, the changes are that the new version returns extra +information in the form of the ``params`` object, and that a sequence of +multiple calls may be statefully chained together by the ``session`` parameter. + +Additionally, whereas in ``v1`` the client performed a ``GET`` request to +discover the list of supported flows for ``/register``, in ``r0`` this is done +by sending a ``POST`` request with an empty data body. The ``/login`` endpoint +continues to use the ``GET`` method as before. + +Deprecated Endpoints +==================== + +The following endpoints are now deprecated and replaced by the ``/sync`` API:: + + /initialSync + /events + /rooms/:roomId/initialSync + +The new ``/sync`` API takes an optional ``since`` parameter to distinguish the +initial sync from subsequent updates for more events. + +The return value takes a different structure to that from the previous +``/initialSync`` API. For full details see the API documentation, but the +following summary may be useful to compare with ``v1``: + + * ``/initialSync`` returned a ``state`` key containing the most recent state + in the room, whereas the new ``/sync`` API's ``state`` corresponds to the + room state at the start of the returned timeline. This makes it easier for + clients to represent state changes that occur within the region of returned + timeline. + + * In ``/events``, if more events occurred since the ``since`` token than the + ``limit`` parameter allowed, then events from the start of this range were + returned and the client had to perform another fetch to incrementally obtain + more of them. In the ``/sync`` API the result always contains the most + recent events, creating a gap if this would be more events than the + requested limit. If this occurs then the client can use the ``prev_batch`` + token as a reference to obtaining more. + + * The ``state`` contained in the response to a ``/sync`` request that has a + ``since`` parameter will contain only keys that have changed since the + basis given in the ``since`` parameter, rather than containing a full set + values. + +The ``/initialSync`` API allowed a parameter called ``limit`` to limit the +number of events returned. To apply this limit to the new ``/sync`` API, you +can supply an ad-hoc filter:: + + GET .../sync?filter={"room":{"timeline":{"limit:$limit}}} + +There is no direct replacement for the per-room ``/rooms/:roomId/initialSync`` +endpoint, but the behaviour can be recreated by applying an ad-hoc filter using +the ``filter`` parameter to ``/sync`` that selects only the required room ID:: + + GET .../sync?filter={"room":{"rooms":[$room_id]}} + +However, the way that the new ``/sync`` API works should remove any need to do +this kind of query, in the situations where the ``v1`` API needed it. +Specifically, on joining a new room the initial information about that room is +sent in the next ``/sync`` batch, so it should not be necessary to query that +one room specially. + +The following endpoint is deprecated and has no direct replacement:: + + /events/:eventId + +However, if the client knows the room ID of the room that the event is in, it +can use the ``/rooms/:roomId/context/:eventId`` request to fetch the event +itself. By giving the ``limit`` parameter of ``0`` the client can save using +extra bandwidth by actually returning additional context events around the +requested one. + +Removed POST Endpoint +===================== + +The room message sending API endpoint in ``v1`` accepted both ``PUT`` and +``POST`` methods, where the client could specify a message ID in the ``PUT`` +path for de-duplication purposes, or have the server allocate one during +``POST``. In ``r0`` this latter form no longer exists. Clients will now have +to generate these IDs locally. + +The following URLs have therefore been removed:: + + POST .../rooms/:roomId/send/:messageType diff --git a/supporting-docs/guides/2016-05-05-client-server.rst b/supporting-docs/guides/2016-05-05-client-server.rst new file mode 100644 index 00000000000..36d5d5af9a4 --- /dev/null +++ b/supporting-docs/guides/2016-05-05-client-server.rst @@ -0,0 +1,394 @@ +--- +layout: post +title: Client Server API +categories: guides +--- + + +.. TODO kegan + Room config (specifically: message history, + public rooms). + +How to use the client-server API +================================ + +.. NOTE:: + The git version of this document is ``{% project_version %}`` + +This guide focuses on how the client-server APIs *provided by the reference +homeserver* can be used. Since this is specific to a homeserver +implementation, there may be variations in relation to registering/logging in +which are not covered in extensive detail in this guide. + +If you haven't already, get a homeserver up and running on +``https://localhost:8448``. + + +Accounts +======== +Before you can send and receive messages, you must **register** for an account. +If you already have an account, you must **login** into it. + +.. NOTE:: + `Try out the fiddle`__ + + .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/register_login + +Registration +------------ +The aim of registration is to get a user ID and access token which you will need +when accessing other APIs:: + + curl -XPOST -d '{"username":"example", "password":"wordpass", "auth": {"type":"m.login.dummy"}}' "https://localhost:8448/_matrix/client/r0/register" + + { + "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc", + "home_server": "localhost", + "user_id": "@example:localhost" + } + +NB: If a ``user`` is not specified, one will be randomly generated for you. +If you do not specify a ``password``, you will be unable to login to the account +if you forget the ``access_token``. + +Implementation note: The matrix specification does not enforce how users +register with a server. It just specifies the URL path and absolute minimum +keys. The reference homeserver uses a username/password to authenticate user, +but other homeservers may use different methods. This is why you need to +specify the ``type`` of method. + +Login +----- +The aim when logging in is to get an access token for your existing user ID:: + + curl -XGET "https://localhost:8448/_matrix/client/r0/login" + + { + "flows": [ + { + "type": "m.login.password" + } + ] + } + + curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "https://localhost:8448/_matrix/client/r0/login" + + { + "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd", + "home_server": "localhost", + "user_id": "@example:localhost" + } + +Implementation note: Different homeservers may implement different methods for +logging in to an existing account. In order to check that you know how to login +to this homeserver, you must perform a ``GET`` first and make sure you +recognise the login type. If you do not know how to login, you can +``GET /login/fallback`` which will return a basic webpage which you can use to +login. The reference homeserver implementation support username/password login, +but other homeservers may support different login methods (e.g. OAuth2). + + +Communicating +============= + +In order to communicate with another user, you must **create a room** with that +user and **send a message** to that room. + +.. NOTE:: + `Try out the fiddle`__ + + .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/create_room_send_msg + +Creating a room +--------------- +If you want to send a message to someone, you have to be in a room with them. To +create a room:: + + curl -XPOST -d '{"room_alias_name":"tutorial"}' "https://localhost:8448/_matrix/client/r0/createRoom?access_token=YOUR_ACCESS_TOKEN" + + { + "room_alias": "#tutorial:localhost", + "room_id": "!asfLdzLnOdGRkdPZWu:localhost" + } + +The "room alias" is a human-readable string which can be shared with other users +so they can join a room, rather than the room ID which is a randomly generated +string. You can have multiple room aliases per room. + +.. TODO(kegan) + How to add/remove aliases from an existing room. + + +Sending messages +---------------- +You can now send messages to this room:: + + curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "https://localhost:8448/_matrix/client/r0/rooms/%21asfLdzLnOdGRkdPZWu:localhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN" + + { + "event_id": "YUwRidLecu" + } + +The event ID returned is a unique ID which identifies this message. + +NB: There are no limitations to the types of messages which can be exchanged. +The only requirement is that ``"msgtype"`` is specified. The Matrix +specification outlines the following standard types: ``m.text``, ``m.image``, +``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for +more information on these types. + +Users and rooms +=============== + +Each room can be configured to allow or disallow certain rules. In particular, +these rules may specify if you require an **invitation** from someone already in +the room in order to **join the room**. In addition, you may also be able to +join a room **via a room alias** if one was set up. + +.. NOTE:: + `Try out the fiddle`__ + + .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/room_memberships + +Inviting a user to a room +------------------------- +You can directly invite a user to a room like so:: + + curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "https://localhost:8448/_matrix/client/r0/rooms/%21asfLdzLnOdGRkdPZWu:localhost/invite?access_token=YOUR_ACCESS_TOKEN" + +This informs ``@myfriend:localhost`` of the room ID +``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room. + +Joining a room via an invite +---------------------------- +If you receive an invite, you can join the room:: + + curl -XPOST -d '{}' "https://localhost:8448/_matrix/client/r0/rooms/%21asfLdzLnOdGRkdPZWu:localhost/join?access_token=YOUR_ACCESS_TOKEN" + +NB: Only the person invited (``@myfriend:localhost``) can change the membership +state to ``"join"``. Repeatedly joining a room does nothing. + +Joining a room via an alias +--------------------------- +Alternatively, if you know the room alias for this room and the room config +allows it, you can directly join a room via the alias:: + + curl -XPOST -d '{}' "https://localhost:8448/_matrix/client/r0/join/%21asfLdzLnOdGRkdPZWu:localhost?access_token=YOUR_ACCESS_TOKEN" + + { + "room_id": "!CvcvRuDYDzTOzfKKgh:localhost" + } + +You will need to use the room ID when sending messages, not the room alias. + +NB: If the room is configured to be an invite-only room, you will still require +an invite in order to join the room even though you know the room alias. As a +result, it is more common to see a room alias in relation to a public room, +which do not require invitations. + +Getting events +============== +An event is some interesting piece of data that a client may be interested in. +It can be a message in a room, a room invite, etc. There are many different ways +of getting events, depending on what the client already knows. + +.. NOTE:: + `Try out the fiddle`__ + + .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/event_stream + +Getting all state +----------------- +If the client doesn't know any information on the rooms the user is +invited/joined on, they can get all the user's state for all rooms:: + + curl -XGET "https://localhost:8448/_matrix/client/r0/sync?access_token=YOUR_ACCESS_TOKEN" + + { + "account_data": { + "events": [ + { + ... + } + ] + }, + "next_batch": "s9_3_0_1_1_1", + "presence": { + "events": [ + { + "content": { + "currently_active": true, + "last_active_ago": 19, + "presence": "online" + }, + "sender": "@example:localhost", + "type": "m.presence" + } + ] + }, + "rooms": { + "invite": {}, + "join": { + "!asfLdzLnOdGRkdPZWu:localhost": { + "account_data": { + "events": [] + }, + "ephemeral": { + "events": [] + }, + "state": { + "events": [] + }, + "timeline": { + "events": [ + { + "content": { + "creator": "@example:localhost" + }, + "event_id": "$14606534990LhqHt:localhost", + "origin_server_ts": 1460653499699, + "sender": "@example:localhost", + "state_key": "", + "type": "m.room.create", + "unsigned": { + "age": 239192 + } + }, + { + "content": { + "avatar_url": null, + "displayname": null, + "membership": "join" + }, + "event_id": "$14606534991nsZKk:localhost", + "membership": "join", + "origin_server_ts": 1460653499727, + "sender": "@example:localhost", + "state_key": "@example:localhost", + "type": "m.room.member", + "unsigned": { + "age": 239164 + } + }, + ... + ], + "limited": false, + "prev_batch": "s9_3_0_1_1_1" + }, + "unread_notifications": {} + } + }, + "leave": {} + } + } + +This returns all the room information the user is invited/joined on, as well as +all of the presences relevant for these rooms. This can be a LOT of data. You +may just want the most recent event for each room. This can be achieved by +applying a filter that asks for a limit of 1 timeline event per room:: + + curl --globoff -XGET "https://localhost:8448/_matrix/client/r0/sync?filter={'room':{'timeline':{'limit':1}}}&access_token=YOUR_ACCESS_TOKEN" + + { + ... + "rooms": { + "invite": {}, + "join": { + "!asfLdzLnOdGRkdPZWu:localhost": { + ... + "timeline": { + "events": [ + { + "content": { + "body": "hello", + "msgtype": "m.text" + }, + "event_id": "$14606535757KCGXo:localhost", + "origin_server_ts": 1460653575105, + "sender": "@example:localhost", + "type": "m.room.message", + "unsigned": { + "age": 800348 + } + } + ], + "limited": true, + "prev_batch": "t8-8_7_0_1_1_1" + }, + "unread_notifications": {} + } + }, + "leave": {} + } + } + +(additionally we have to ask ``curl`` not to try to interpret any ``{}`` +characters in the URL, which is what the ``--globoff`` option is for) + +Getting live state +------------------ +In the response to this ``sync`` request the server includes a token that can +be used to obtain updates since this point under the object key ``next_batch``. +To use this token, specify its value as the ``since`` parameter to another +``/sync`` request.:: + + curl -XGET "https://localhost:8448/_matrix/client/r0/sync?since=s9_7_0_1_1_1&access_token=YOUR_ACCESS_TOKEN" + + { + "account_data": { + "events": [] + }, + "next_batch": "s9_9_0_1_1_1", + "presence": { + "events": [ + { + "content": { + "currently_active": true, + "last_active_ago": 12, + "presence": "online" + }, + "sender": "@example:localhost", + "type": "m.presence" + } + ] + }, + "rooms": { + "invite": {}, + "join": {}, + "leave": {} + } + } + +By default this request will not wait in the server, always returning a value +even if nothing interesting happened. However, by applying the ``timeout`` +query parameter, which gives a duration in miliseconds, we can ask the server +to wait for up to that amount of time before it returns. If no interesting +events have happened since then, the response will be relatively empty.:: + + curl -XGET "https://localhost:8448/_matrix/client/r0/sync?since=s9_13_0_1_1_1&access_token=YOUR_ACCESS_TOKEN" + { + "account_data": { + "events": [] + }, + "next_batch": "s9_13_0_1_1_1", + "presence": { + "events": [] + }, + "rooms": { + "invite": {}, + "join": {}, + "leave": {} + } + } + +Example application +------------------- +The following example demonstrates registration and login, live event streaming, +creating and joining rooms, sending messages, getting member lists and getting +historical messages for a room. This covers most functionality of a messaging +application. + +.. NOTE:: + `Try out the fiddle`__ + + .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/example_app diff --git a/supporting-docs/guides/2016-10-18-e2e_implementation.rst b/supporting-docs/guides/2016-10-18-e2e_implementation.rst new file mode 100644 index 00000000000..5413491656e --- /dev/null +++ b/supporting-docs/guides/2016-10-18-e2e_implementation.rst @@ -0,0 +1,782 @@ +--- +layout: post +title: End-to-End Encryption implementation guide +categories: guides +--- + +Implementing End-to-End Encryption in Matrix clients +==================================================== + +This guide is intended for authors of Matrix clients who wish to add +support for end-to-end encryption. It is highly recommended that readers +be familiar with the Matrix protocol and the use of access tokens before +proceeding. + +.. contents:: + +The libolm library +------------------ + +End-to-end encryption in Matrix is based on the Olm and Megolm +cryptographic ratchets. The recommended starting point for any client +authors is with the `libolm `__ library, +which contains implementations of all of the cryptographic primitives +required. The library itself is written in C/C++, but is architected in +a way which makes it easy to write wrappers for higher-level languages. + +Devices +------- + +We have a particular meaning for “device”. As a user, I might have +several devices (a desktop client, some web browsers, an Android device, +an iPhone, etc). When I first use a client, it should register itself as +a new device. If I log out and log in again as a different user, the +client must register as a new device. Critically, the client must create +a new set of keys (see below) for each “device”. + +The longevity of devices will depend on the client. In the web client, +we create a new device every single time you log in. In a mobile client, +it might be acceptable to reuse the device if a login session expires, +**provided** the user is the same. **Never** share keys between +different users. + +Devices are identified by their ``device_id`` (which is unique within +the scope of a given user). By default, the ``/login`` and ``/register`` +endpoints will auto-generate a ``device_id`` and return it in the +response; a client is also free to generate its own ``device_id`` or, as +above, reuse a device, in which case the client should pass the +``device_id`` in the request body. + +The lifetime of devices and ``access_token``\ s are closely related. In +the simple case where a new device is created each time you log in, +there is a one-to-one mapping between a ``device_id`` and an +``access_token``. If a client reuses a ``device_id`` when logging +in, there will be several ``access_token``\ s associated with a +given ``device_id`` - but still, we would expect only one of these to be +active at once (though we do not currently enforce that in Synapse). + +Keys used in End-to-End encryption +---------------------------------- + +There are a number of keys involved in encrypted communication: a +summary of them follows. + +Ed25519 fingerprint key pair +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Ed25519 is a public-key cryptographic system for signing messages. In +Matrix, each device has an Ed25519 key pair which serves to identify +that device. The private part of the key pair should never leave the +device, but the public part is published to the Matrix network. + +Curve25519 identity key pair +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Curve25519 is a public-key cryptographic system which can be used to +establish a shared secret. In Matrix, each device has a long-lived +Curve25519 identity key which is used to establish Olm sessions with +that device. Again, the private key should never leave the device, but +the public part is signed with the Ed25519 fingerprint key and published +to the network. + +Theoretically we should rotate the Curve25519 identity key from time to +time, but we haven't implemented this yet. + +Curve25519 one-time keys +~~~~~~~~~~~~~~~~~~~~~~~~ + +As well as the identity key, each device creates a number of Curve25519 +key pairs which are also used to establish Olm sessions, but can only be +used once. Once again, the private part remains on the device. + +At startup, Alice creates a number of one-time key pairs, and publishes +them to her homeserver. If Bob wants to establish an Olm session with +Alice, he needs to claim one of Alice’s one-time keys, and creates a new +one of his own. Those two keys, along with Alice’s and Bob’s identity +keys, are used in establishing an Olm session between Alice and Bob. + +Megolm encryption keys +~~~~~~~~~~~~~~~~~~~~~~ + +The Megolm key is used to encrypt group messages (in fact it is used to +derive an AES-256 key, and an HMAC-SHA-256 key). It is initialised with +random data. Each time a message is sent, a hash calculation is done on +the Megolm key to derive the key for the next message. It is therefore +possible to share the current state of the Megolm key with a user, +allowing them to decrypt future messages but not past messages. + +Ed25519 Megolm signing key pair +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a sender creates a Megolm session, he also creates another Ed25519 +signing key pair. This is used to sign messages sent via that Megolm +session, to authenticate the sender. Once again, the private part of the +key remains on the device. The public part is shared with other devices +in the room alongside the encryption key. + +Creating and registering device keys +------------------------------------ + +This process only happens once, when a device first starts. + +It must create the Ed25519 fingerprint key pair and the Curve25519 +identity key pair. This is done by calling ``olm_create_account`` in +libolm. The (base64-encoded) keys are retrieved by calling +``olm_account_identity_keys``. The account should be stored for future +use. + +It should then publish these keys to the homeserver. To do this, it +should construct a JSON object as follows: + +.. code:: json + + { + "algorithms": ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], + "device_id": "", + "keys": { + "curve25519:": "", + "ed25519:": "" + }, + "user_id: " + } + +The object should be formatted as `Canonical +JSON `__, +then signed with ``olm_account_sign``; the signature should be added to +the JSON as ``signatures..ed25519:``. + +The signed JSON is then uploaded via +``POST /_matrix/client/unstable/keys/upload``. + +Creating and registering one-time keys +-------------------------------------- + +At first start, and at regular intervals +thereafter\ [#]_, the client should check how +many one-time keys the homeserver has stored for it, and, if necessary, +generate and upload some more. + +.. [#] Every 10 minutes is suggested. + +The number of one-time keys currently stored is returned by +``POST /_matrix/client/unstable/keys/upload``. (Post an empty JSON object +``{}`` if you don’t want to upload the device keys.) + +The maximum number of active keys supported by libolm is returned by +``olm_account_max_number_of_one_time_keys``. The client should try to +maintain about half this number on the homeserver. + +To generate new one-time keys: + +* Call ``olm_account_generate_one_time_keys`` to generate new keys. + +* Call ``olm_account_one_time_keys`` to retrieve the unpublished keys. This + returns a JSON-formatted object with the single property ``curve25519``, + which is itself an object mapping key id to base64-encoded Curve25519 + key. For example: + + .. code:: json + + { + "curve25519": { + "AAAAAA": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo", + "AAAAAB": "LRvjo46L1X2vx69sS9QNFD29HWulxrmW11Up5AfAjgU" + } + } + +* Each key should be signed with the account key. To do this: + + * Construct a JSON object as follows: + + .. code:: json + + { + "key": "" + } + + * Call ``olm_account_sign`` to calculate the signature. + + * Add the signature should be added to the JSON as + ``signatures..ed25519:``. + + * The complete key object should now look like: + + .. code:: json + + { + "key": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo", + "signatures": { + "@alice:example.com": { + "ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA" + } + } + } + + +* Aggregate all the signed one-time keys into a single JSON object as follows: + + .. code:: json + + { + "one_time_keys": { + "signed_curve25519:": { + "key": "", + "signatures": { + "": { + "ed25519:": "" + } + } + }, + "signed_curve25519:": { + ... + }, + ... + } + } + +* Upload the object via ``POST /_matrix/client/unstable/keys/upload``. + +* Call ``olm_account_mark_keys_as_published`` to tell the olm library not to + return the same keys from a future call to ``olm_account_one_time_keys``. + +Configuring a room to use encryption +------------------------------------ + +To enable encryption in a room, a client should send a state event of +type ``m.room.encryption``, and content ``{ "algorithm": +"m.megolm.v1.aes-sha2" }``. + +.. |m.room.encryption| replace:: ``m.room.encryption`` +.. _`m.room.encryption`: + +Handling an ``m.room.encryption`` state event +--------------------------------------------- + +When a client receives an ``m.room.encryption`` event as above, it +should set a flag to indicate that messages sent in the room should be +encrypted. + +This flag should **not** be cleared if a later ``m.room.encryption`` +event changes the configuration. This is to avoid a situation where a +MITM can simply ask participants to disable encryption. In short: once +encryption is enabled in a room, it can never be disabled. + +The event should contain an ``algorithm`` property which defines which +encryption algorithm should be used for encryption. Currently only +``m.megolm.v1-aes-sha2`` is permitted here. + +The event may also include other settings for how messages sent in the room +should be encrypted (for example, ``rotation_period_ms`` to define how often +the session should be replaced). + +Handling an ``m.room.encrypted`` event +-------------------------------------- + +Encrypted events have a type of ``m.room.encrypted``. They have a +content property ``algorithm`` which gives the encryption algorithm in +use, as well as other properties specific to the algorithm. + +The encrypted payload is a JSON object with the properties ``type`` +(giving the decrypted event type), and ``content`` (giving the decrypted +content). Depending on the algorithm in use, the payload may contain +additional keys. + +There are currently two defined algorithms: + +``m.olm.v1.curve25519-aes-sha2`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Encrypted events using this algorithm should have a ``sender_key`` and a +``ciphertext`` property. + +The ``sender_key`` property of the event content gives the Curve25519 +identity key of the sender. Clients should maintain a list of known Olm +sessions for each device they speak to; it is recommended to index them +by Curve25519 identity key. + +Olm messages are encrypted separately for each recipient device. +``ciphertext`` is an object mapping from the Curve25519 identity key for +the recipient device. The receiving client should, of course, look for +its own identity key in this object. (If it isn't listed, the message +wasn't sent for it, and the client can't decrypt it; it should show an +error instead, or similar). + +This should result in an object with the properties ``type`` and +``body``. Messages of type '0' are 'prekey' messages which are used to +establish a new Olm session between two devices; type '1' are normal +messages which are used once a message has been received on the session. + +When a message (of either type) is received, a client should first +attempt to decrypt it with each of the known sessions for that sender. +There are two steps to this: + +- If (and only if) ``type==0``, the client should call + ``olm_matches_inbound_session`` with the session and ``body``. This + returns a flag indicating whether the message was encrypted using + that session. + +- The client calls ``olm_decrypt``, with the session, ``type``, and + ``body``. If this is successful, it returns the plaintext of the + event. + +If the client was unable to decrypt the message using any known sessions +(or if there are no known sessions yet), **and** the message had type 0, +**and** ``olm_matches_inbound_session`` wasn't true for any existing +sessions, then the client can try establishing a new session. This is +done as follows: + +- Call ``olm_create_inbound_session_from`` using the olm account, and + the ``sender_key`` and ``body`` of the message. + +- If the session was established successfully: + + - call ``olm_remove_one_time_keys`` to ensure that the same + one-time-key cannot be reused. + + - Call ``olm_decrypt`` with the new session + + - Store the session for future use + +At the end of this, the client will hopefully have successfully +decrypted the payload. + +As well as the ``type`` and ``content`` properties, the payload should +contain a number of other properties. Each of these should be checked as +follows [#]_. + +``sender`` + The user ID of the sender. The client should check that this matches the + ``sender`` in the event. + +``recipient`` + The user ID of the recipient. The client should check that this matches the + local user ID. + +``keys`` + an object with a property ``ed25519``, The client should check that the + value of this property matches the sender's fingerprint key when `marking + the event as verified`_\ . + +``recipient_keys`` + an object with a property ``ed25519``. The client should check that the + value of this property matches its own fingerprint key. + +.. [#] These tests prevent an attacker publishing someone else's curve25519 + keys as their own and subsequently claiming to have sent messages which they + didn't. + +``m.megolm.v1.aes-sha2`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +Encrypted events using this algorithm should have ``sender_key``, +``session_id`` and ``ciphertext`` content properties. If the +``room_id``, ``sender_key`` and ``session_id`` correspond to a known +Megolm session (see `below`__), the ciphertext can be +decrypted by passing the ciphertext into ``olm_group_decrypt``. + +__ `m.room_key`_ + +In order to avoid replay attacks a client should remember the megolm +``message_index`` returned by ``olm_group_decrypt`` of each event they decrypt +for each session. If the client decrypts an event with the same +``message_index`` as one that it has already received using that session then +it should treat the message as invalid. + +The client should check that the sender's fingerprint key matches the +``keys.ed25519`` property of the event which established the Megolm session +when `marking the event as verified`_. + +.. _`m.room_key`: + +Handling an ``m.room_key`` event +-------------------------------- + +These events contain key data to allow decryption of other messages. +They are sent to specific devices, so they appear in the ``to_device`` +section of the response to ``GET /_matrix/client/r0/sync``. They will +also be encrypted, so will need decrypting as above before they can be +seen. (These events are generated by other clients - see `starting a megolm +session`_). + +The event content will contain an 'algorithm' property, indicating the +encryption algorithm the key data is to be used for. Currently, this +will always be ``m.megolm.v1.aes-sha2``. + +Room key events for Megolm will also have ``room_id``, ``session_id``, and +``session_key`` keys. They are used to establish a Megolm session. The +``room_id`` identifies which room the session will be used in. The ``room_id``, +together with the ``sender_key`` of the ``room_key`` event before it was +decrypted, and the ``session_id``, uniquely identify a Megolm session. If they +do not represent a known session, the client should start a new inbound Megolm +session by calling ``olm_init_inbound_group_session`` with the ``session_key``. + +The client should remember the value of the keys property of the payload +of the encrypted ``m.room_key`` event and store it with the inbound +session. This is used as above when marking the event as verified. + +.. _`download the device list`: + +Downloading the device list for users in the room +------------------------------------------------- + +Before an encrypted message can be sent, it is necessary to retrieve the +list of devices for each user in the room. This can be done proactively, +or deferred until the first message is sent. The information is also +required to allow users to `verify or block devices`__. + +__ `blocking`_ + +The client should build a JSON query object as follows: + +.. code:: json + + { + "device_keys": { + "": {}, + [...] + } + } + +Each member in the room should be included in the query. This is then +sent via ``POST /_matrix/client/unstable/keys/query.`` + +The result includes, for each listed user id, a map from device ID to an +object containing information on the device, as follows: + +.. code:: json + + { + "algorithms": [...], + "device_id": "", + "keys": { + "curve25519:": "", + "ed25519:": "" + }, + "signatures": { + "": { + "ed25519:": "" + }, + }, + "unsigned": { + "device_display_name": "" + }, + "user_id: " + } + +The client should first check the signature on this object. To do this, +it should remove the ``signatures`` and ``unsigned`` properties, format +the remainder as Canonical JSON, and pass the result into +``olm_ed25519_verify``, using the Ed25519 key for the ``key`` parameter, +and the corresponding signature for the ``signature`` parameter. If the +signature check fails, no further processing should be done on the +device. + +The client must also check that the ``user_id`` and ``device_id`` fields in the +object match those in the top-level map [#]_. + +The client should check if the ``user_id``/``device_id`` correspond to a device +it had seen previously. If it did, the client **must** check that the Ed25519 +key hasn't changed. Again, if it has changed, no further processing should be +done on the device. + +Otherwise the client stores the information about this device. + +.. [#] This prevents a malicious or compromised homeserver replacing the keys + for the device with those of another. + +Sending an encrypted event +-------------------------- + +When sending a message in a room `configured to use encryption`__, a client +first checks to see if it has an active outbound Megolm session. If not, it +first `creates one as per below`__. If an outbound session exists, it should +check if it is time to `rotate`__ it, and create a new one if so. + +__ `Configuring a room to use encryption`_ +__ `Starting a Megolm session`_ +__ `Rotating Megolm sessions`_ + +The client then builds an encryption payload as follows: + +.. code:: json + + { + "type": "", + "content": "", + "room_id": "" + } + +and calls ``olm_group_encrypt`` to encrypt the payload. This is then packaged +into event content as follows: + +.. code:: json + + { + "algorithm": "m.megolm.v1.aes-sha2", + "sender_key": "", + "ciphertext": "", + "session_id": "", + "device_id": "" + } + +Finally, the encrypted event is sent to the room with ``POST +/_matrix/client/r0/rooms//send/m.room.encrypted/``. + +Starting a Megolm session +~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a message is first sent in an encrypted room, the client should +start a new outbound Megolm session. This should **not** be done +proactively, to avoid proliferation of unnecessary Megolm sessions. + +To create the session, the client should call +``olm_init_outbound_group_session``, and store the details of the +outbound session for future use. + +The client should then call ``olm_outbound_group_session_id`` to get the +unique ID of the new session, and ``olm_outbound_group_session_key`` to +retrieve the current ratchet key and index. It should store these +details as an inbound session, just as it would when `receiving them via +an m.room_key event`__. + +__ `m.room_key`_ + +The client must then share the keys for this session with each device in the +room. It must therefore `download the device list`_ if it hasn't already done +so, and for each device in the room which has not been `blocked`__, the client +should: + +__ `blocking`_ + +* Build a content object as follows: + + .. code:: json + + { + "algorithm": "m.megolm.v1.aes-sha2", + "room_id": "", + "session_id": "", + "session_key": "" + } + +- Encrypt the content as an ``m.room_key`` event using Olm, as below. + +Once all of the key-sharing event contents have been assembled, the +events should be sent to the corresponding devices via +``PUT /_matrix/client/unstable/sendToDevice/m.room.encrypted/``. + +Rotating Megolm sessions +~~~~~~~~~~~~~~~~~~~~~~~~ + +Megolm sessions may not be reused indefinitely. + +The number of messages which can be sent before a session should be rotated is +given by the ``rotation_period_msgs`` property of the |m.room.encryption|_ +event, or ``100`` if that property isn't present. + +Similarly, the maximum age of a megolm session is given, in milliseconds, by +the ``rotation_period_ms`` property of the ``m.room.encryption`` +event. ``604800000`` (a week) is the recommended default here. + +Once either the message limit or time limit have been reached, the client +should start a new session before sending any more messages. + + +Encrypting an event with Olm +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Olm is not used for encrypting room events, as it requires a separate +copy of the ciphertext for each device, and because the receiving device +can only decrypt received messages once. However, it is used for +encrypting key-sharing events for Megolm. + +When encrypting an event using Olm, the client should: + +- Build an encryption payload as follows: + + .. code:: json + + { + "type": "", + "content": "", + "sender": "", + "sender_device": "", + "keys": { + "ed25519": "" + }, + "recipient": "", + "recipient_keys": { + "ed25519": "" + }, + } + +- Check if it has an existing Olm session; if it does not, `start a new + one`__. If it has several (as may happen due to + races when establishing sessions), it should use the one with the + first session_id when sorted by their ASCII codepoints (ie, 'A' + would be before 'Z', which would be before 'a'). + + __ `Starting an Olm session`_ + +- Encrypt the payload by calling ``olm_encrypt``. + +- Package the payload into event content as follows: + + .. code:: json + + { + "algorithm": "m.olm.v1.curve25519-aes-sha2", + "sender_key": "", + "ciphertext": "" + } + +Starting an Olm session +~~~~~~~~~~~~~~~~~~~~~~~ + +To start a new Olm session with another device, a client must first +claim one of the other device's one-time keys. To do this, it should +create a query object as follows: + +.. code:: json + + { + "": { + "": "signed_curve25519", + ... + }, + ... + } + +and send this via ``POST /_matrix/client/unstable/keys/claim``. Claims +for multiple devices should be aggregated into a single request. + +This will return a result as follows: + +.. code:: json + + { + "one_time_keys": { + "": { + "": { + "signed_curve25519:": { + "key": "", + "signatures": { + "": { + "ed25519:": "" + } + } + }, + }, + ... + }, + ... + } + } + +The client should first check the signatures on the signed key objects. As with +checking the signatures on the device keys, it should remove the ``signatures`` +and (if present) ``unsigned`` properties, format the remainder as Canonical +JSON, and pass the result into ``olm_ed25519_verify``, using the Ed25519 device +key for the ``key`` parameter. + +Provided the key object passes verification, the client should then pass the +key, along with the Curve25519 Identity key for the remote device, into +``olm_create_outbound_session``. + +Handling membership changes +--------------------------- + +The client should monitor rooms which are configured to use encryption for +membership changes. + +When a member leaves a room, the client should invalidate any active outbound +Megolm session, to ensure that a new session is used next time the user sends a +message. + +When a new member joins a room, the client should first `download the device +list`_ for the new member, if it doesn't already have it. + +After giving the user an opportunity to `block`__ any suspicious devices, the +client should share the keys for the outbound Megolm session with all the new +member's devices. This is done in the same way as `creating a new session`__, +except that there is no need to start a new Megolm session: due to the design +of the Megolm ratchet, the new user will only be able to decrypt messages +starting from the current state. The recommended method is to maintain a list +of members who are waiting for the session keys, and share them when the user +next sends a message. + +__ `blocking`_ +__ `Starting a Megolm session`_ + +Sending New Device announcements +-------------------------------- + +When a user logs in on a new device, it is necessary to make sure that +other devices in any rooms with encryption enabled are aware of the new +device. This is done as follows. + +Once the initial call to the ``/sync`` API completes, the client should +iterate through each room where encryption is enabled. For each user +(including the client's own user), it should build a content object as +follows: + +.. code:: json + + { + "device_id": "", + "rooms": ["", "", ... ] + } + +Once all of these have been constructed, they should be sent to all of the +relevant user's devices (using the wildcard ``*`` in place of the +``device_id``) via ``PUT +/_matrix/client/unstable/sendToDevice/m.new_device/.`` + +Handling an ``m.new_device`` event +---------------------------------- + +As with ``m.room_key`` events, these will appear in the ``to_device`` +section of the ``/sync`` response. + +The client should `download the device list`_ of the sender, to get the details +of the new device. + +The event content will contain a ``rooms`` property, as well as the +``device_id`` of the new device. For each room in the list, the client +should check if encryption is enabled, and if the sender of the event is +a member of that room. If so, the client should share the keys for the +outbound Megolm session with the new device, in the same way as +`handling a new user in the room`__. + +__ `Handling membership changes`_ + +.. _`blocking`: + +Blocking / Verifying devices +---------------------------- + +It should be possible for a user to mark each device belonging to +another user as 'Blocked' or 'Verified'. + +When a user chooses to block a device, this means that no further +encrypted messages should be shared with that device. In short, it +should be excluded when sharing room keys when `starting a new Megolm +session <#_p5d1esx6gkrc>`__. Any active outbound Megolm sessions whose +keys have been shared with the device should also be invalidated so that +no further messages are sent over them. + +Verifying a device involves ensuring that the device belongs to the +claimed user. Currently this must be done by showing the user the +Ed25519 fingerprint key for the device, and prompting the user to verify +out-of-band that it matches the key shown on the other user's device. + +.. _`marking the event as verified`: + +Marking events as 'verified' +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once a device has been verified, it is possible to verify that events +have been sent from a particular device. See the section on `Handling an +m.room.encrypted event`_ for notes on how to do this +for each algorithm. Events sent from a verified device can be decorated +in the UI to show that they have been sent from a verified device. diff --git a/supporting-docs/guides/2017-03-11-types-of-bridging.md b/supporting-docs/guides/2017-03-11-types-of-bridging.md new file mode 100644 index 00000000000..d9ddbd07a9e --- /dev/null +++ b/supporting-docs/guides/2017-03-11-types-of-bridging.md @@ -0,0 +1,79 @@ +--- +layout: post +version: v1.0 +title: Types of Bridging +categories: guides +--- + + +# Types of bridging + +Bridges come in many flavours, and we need consistent terminology within the Matrix community to ensure everyone (users, developers, core team) is on the same page. This post is primarily intended for bridge developers to refer to when building bridges. + +*The most recent version of this document is [here](https://matrix.org/docs/guides/types-of-bridging.html) ([source](https://github.com/matrix-org/matrix-doc/blob/master/supporting-docs/guides/2017-03-11-types-of-bridging.md)) but we’re also posting it as a blog post for visibility.* + +## Types of rooms + +### Portal rooms + +Bridges can register themselves as controlling chunks of room aliases namespace, letting Matrix users join remote rooms transparently if they /join #freenode_#wherever:matrix.org or similar. The resulting Matrix room is typically automatically bridged to the single target remote room. Access control for Matrix users is typically managed by the remote network’s side of the room. This is called a portal room, and is useful for jumping into remote rooms without any configuration needed whatsoever - using Matrix as a ‘bouncer’ for the remote network. + +### Plumbed rooms + +Alternatively, an existing Matrix room can be can plumbed into one or more specific remote rooms by configuring a bridge (which can be run by anyone). For instance, #matrix:matrix.org is plumbed into #matrix on Freenode, matrixdotorg/#matrix on Slack, etc. Access control for Matrix users is necessarily managed by the Matrix side of the room. This is useful for using Matrix to link together different communities. + +Migrating rooms between a portal & plumbed room is currently a bit of a mess, as there’s not yet a way for users to remove portal rooms once they’re created, so you can end up with a mix of portal & plumbed users bridged into a room, which looks weird from both the Matrix and non-Matrix viewpoints. https://github.com/matrix-org/matrix-appservice-irc/issues/387 tracks this. + +## Types of bridges (simplest first): + +### Bridgebot-based bridges + +The simplest way to exchange messages with a remote network is to have the bridge log into the network using one or more predefined users called bridge bots - typically called MatrixBridge or MatrixBridge[123] etc. These relay traffic on behalf of the users on the other side, but it’s a terrible experience as all the metadata about the messages and senders is lost. This is how the [telematrix](https://github.com/SijmenSchoon/telematrix) matrix<->telegram bridge currently works. + +### Bot-API (aka Virtual user) based bridges + +Some remote systems support the idea of injecting messages from ‘fake’ or ‘virtual’ users, which can be used to represent the Matrix-side users as unique entities in the remote network. For instance, Slack’s inbound webhooks lets remote bots be created on demand, letting Matrix users be shown cosmetically correctly in the timeline as virtual users. However, the resulting virtual users aren’t real users on the remote system, so don’t have presence/profile and can’t be tab-completed or direct-messaged etc. They also have no way to receive typing notifs or other richer info which may not be available via bot APIs. This is how the current [matrix-appservice-slack](https://github.com/matrix-org/matrix-appservice-slack) bridge works. + +### Simple puppeted bridge + +This is a richer form of bridging, where the bridge logs into the remote service as if it were a real 3rd party client for that service. As a result, the Matrix user has to already have a valid account on the remote system. In exchange, the Matrix user ‘puppets’ their remote user, such that other users on the remote system aren’t even aware they are speaking to a user via Matrix. The full semantics of the remote system are available to the bridge to expose into Matrix. However, the bridge has to handle the authentication process to log the user into the remote bridge. + +This is essentially how the current [matrix-appservice-irc](https://github.com/matrix-org/matrix-appservice-irc) bridge works (if you configure it to log into the remote IRC network as your ‘real’ IRC nickname). [matrix-appservice-gitter](https://github.com/matrix-org/matrix-appservice-gitter) is being extended to support both puppeted and bridgebot-based operation. It’s how the experimental [matrix-appservice-tg](https://github.com/matrix-org/matrix-appservice-tg) bridge works. + +Going forwards we’re aiming for all bridges to be at least simple puppeted, if not double-puppeted. + +### Double-puppeted bridge + +A simple ‘puppeted bridge’ allows the Matrix user to control their account on their remote network. However, ideally this puppeting should work in both directions, so if the user logs into (say) their native telegram client and starts conversations, sends messages etc, these should be reflected back into Matrix as if the user had done them there. This requires the bridge to be able to puppet the Matrix side of the bridge on behalf of the user. + +This is the holy-grail of bridging because both the Matrix account and the third party account are accurately represented on their respective networks, with all user metadata intact. This is in contrast to a relaybot which would appear as a separate user from whom it represents. + +Several obstacles exist to the proper implementation of double-puppeted bridges. On the Matrix side, we need an elegant way of having the bridge auth with Matrix as the matrix user (which requires some kind of scoped access_token delegation). On the third-party network, unique problems exist depending on the limitations of that particular network network. For example, many third party networks will lack the ability to represent other Matrix users than the one being puppeted (see hybrid relaybot). + +[matrix-puppet-bridge](https://github.com/matrix-hacks/matrix-puppet-bridge) is a community project that tries to facilitate development of double-puppeted bridges, having done so, without a bridgebot feature, for [several networks](https://github.com/matrix-hacks/matrix-puppet-bridge#examples). A downside to their approach is the assumption that an individual will run the bridge on their own homeserver, thus working around the problem of sharing auth credentials on a shared homeserver. + +### Hybrid Relaybot Puppet Bridge + +This type of bridge is a combination single or double puppet bridge which tries to solve the problem of representing other users by means of the bridgebot technique. [matrix-appservice-gitter](https://github.com/matrix-org/matrix-appservice-gitter) works in this way. + +### Server-to-server bridging + +Some remote protocols (IRC, XMPP, SIP, SMTP, NNTP, GnuSocial etc) support federation - either open or closed. The most elegant way of bridging to these protocols would be to have the bridge participate in the federation as a server, directly bridging the entire namespace into Matrix. + +We’re not aware of anyone who’s done this yet. + +### One-way bridging + +One-way bridging is rare, but can be used to represent a bridge that is bridging from the remote system into matrix. This is common when the remote system does not permit message posting, or is simply not capable of handling posting outside their system. The users bridged from the remote system often appear as virtual users in matrix, as is the case with [matrix-appservice-instagram](https://github.com/turt2live/matrix-appservice-instagram). + +### Sidecar bridge + +Finally: the types of bridging described above assume that you are synchronising the conversation history of the remote system into Matrix, so it may be decentralised and exposed to multiple users within the wider Matrix network. + +This can cause problems where the remote system may have arbitrarily complicated permissions (ACLs) controlling access to the history, which will then need to be correctly synchronised with Matrix’s ACL model, without introducing security issues such as races. We already see some problems with this on the IRC bridge, where history visibility for +i and +k channels have to be carefully synchronised with the Matrix rooms. + +You can also hit problems with other network-specific features not yet having equivalent representation in the Matrix protocol (e.g. ephemeral messages, or op-only messages - although arguably that’s a type of ACL). + +One solution could be to support an entirely different architecture of bridging, where the Matrix client-server API is mapped directly to the remote service, meaning that ACL decisions are delegated to the remote service, and conversations are not exposed into the wider Matrix. This is effectively using the bridge purely as a 3rd party client for the network (similar to Bitlbee). The bridge is only available to a single user, and conversations cannot be shared with other Matrix users as they aren’t actually Matrix rooms. (Another solution could be to use Active Policy Servers at last as a way of centralising and delegating ACLs for a room) + +This is essentially an entirely different product to the rest of Matrix, and whilst it could be a solution for some particularly painful ACL problems, we’re focusing on non-sidecar bridges for now. diff --git a/supporting-docs/howtos/2014-06-09-client-server.md b/supporting-docs/howtos/2014-06-09-client-server.md new file mode 100644 index 00000000000..d62f86d72f0 --- /dev/null +++ b/supporting-docs/howtos/2014-06-09-client-server.md @@ -0,0 +1,11 @@ +--- +layout: project +title: Try Matrix Now! +categories: howtos +--- + + + + + + diff --git a/supporting-docs/howtos/client-server.rst b/supporting-docs/howtos/client-server.rst deleted file mode 100644 index 3bed5a9f418..00000000000 --- a/supporting-docs/howtos/client-server.rst +++ /dev/null @@ -1,644 +0,0 @@ -.. TODO kegan - Room config (specifically: message history, - public rooms). /register seems super simplistic compared to /login, maybe it - would be better if /register used the same technique as /login? /register should - be "user" not "user_id". - -How to use the client-server API -================================ - -.. NOTE:: - The git version of this document is ``{{git_version}}`` - -This guide focuses on how the client-server APIs *provided by the reference -home server* can be used. Since this is specific to a home server -implementation, there may be variations in relation to registering/logging in -which are not covered in extensive detail in this guide. - -If you haven't already, get a home server up and running on -``http://localhost:8008``. - - -Accounts -======== -Before you can send and receive messages, you must **register** for an account. -If you already have an account, you must **login** into it. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/register_login - -Registration ------------- -The aim of registration is to get a user ID and access token which you will need -when accessing other APIs:: - - curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register" - - { - "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc", - "home_server": "localhost", - "user_id": "@example:localhost" - } - -NB: If a ``user`` is not specified, one will be randomly generated for you. -If you do not specify a ``password``, you will be unable to login to the account -if you forget the ``access_token``. - -Implementation note: The matrix specification does not enforce how users -register with a server. It just specifies the URL path and absolute minimum -keys. The reference home server uses a username/password to authenticate user, -but other home servers may use different methods. This is why you need to -specify the ``type`` of method. - -Login ------ -The aim when logging in is to get an access token for your existing user ID:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/login" - - { - "flows": [ - { - "type": "m.login.password" - } - ] - } - - curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login" - - { - "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd", - "home_server": "localhost", - "user_id": "@example:localhost" - } - -Implementation note: Different home servers may implement different methods for -logging in to an existing account. In order to check that you know how to login -to this home server, you must perform a ``GET`` first and make sure you -recognise the login type. If you do not know how to login, you can -``GET /login/fallback`` which will return a basic webpage which you can use to -login. The reference home server implementation support username/password login, -but other home servers may support different login methods (e.g. OAuth2). - - -Communicating -============= - -In order to communicate with another user, you must **create a room** with that -user and **send a message** to that room. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/create_room_send_msg - -Creating a room ---------------- -If you want to send a message to someone, you have to be in a room with them. To -create a room:: - - curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN" - - { - "room_alias": "#tutorial:localhost", - "room_id": "!CvcvRuDYDzTOzfKKgh:localhost" - } - -The "room alias" is a human-readable string which can be shared with other users -so they can join a room, rather than the room ID which is a randomly generated -string. You can have multiple room aliases per room. - -.. TODO(kegan) - How to add/remove aliases from an existing room. - - -Sending messages ----------------- -You can now send messages to this room:: - - curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN" - - { - "event_id": "YUwRidLecu" - } - -The event ID returned is a unique ID which identifies this message. - -NB: There are no limitations to the types of messages which can be exchanged. -The only requirement is that ``"msgtype"`` is specified. The Matrix -specification outlines the following standard types: ``m.text``, ``m.image``, -``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for -more information on these types. - -Users and rooms -=============== - -Each room can be configured to allow or disallow certain rules. In particular, -these rules may specify if you require an **invitation** from someone already in -the room in order to **join the room**. In addition, you may also be able to -join a room **via a room alias** if one was set up. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/room_memberships - -Inviting a user to a room -------------------------- -You can directly invite a user to a room like so:: - - curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN" - -This informs ``@myfriend:localhost`` of the room ID -``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room. - -Joining a room via an invite ----------------------------- -If you receive an invite, you can join the room:: - - curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN" - -NB: Only the person invited (``@myfriend:localhost``) can change the membership -state to ``"join"``. Repeatedly joining a room does nothing. - -Joining a room via an alias ---------------------------- -Alternatively, if you know the room alias for this room and the room config -allows it, you can directly join a room via the alias:: - - curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN" - - { - "room_id": "!CvcvRuDYDzTOzfKKgh:localhost" - } - -You will need to use the room ID when sending messages, not the room alias. - -NB: If the room is configured to be an invite-only room, you will still require -an invite in order to join the room even though you know the room alias. As a -result, it is more common to see a room alias in relation to a public room, -which do not require invitations. - -Getting events -============== -An event is some interesting piece of data that a client may be interested in. -It can be a message in a room, a room invite, etc. There are many different ways -of getting events, depending on what the client already knows. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/event_stream - -Getting all state ------------------ -If the client doesn't know any information on the rooms the user is -invited/joined on, they can get all the user's state for all rooms:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN" - - { - "end": "s39_18_0", - "presence": [ - { - "content": { - "last_active_ago": 1061436, - "user_id": "@example:localhost" - }, - "type": "m.presence" - } - ], - "rooms": [ - { - "membership": "join", - "messages": { - "chunk": [ - { - "content": { - "@example:localhost": 10, - "default": 0 - }, - "event_id": "wAumPSTsWF", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.power_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "join_rule": "public" - }, - "event_id": "jrLVqKHKiI", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.join_rules", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 10 - }, - "event_id": "WpmTgsNWUZ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.add_state_level", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 0 - }, - "event_id": "qUMBJyKsTQ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.send_event_level", - "user_id": "@example:localhost" - }, - { - "content": { - "ban_level": 5, - "kick_level": 5 - }, - "event_id": "YAaDmKvoUW", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.ops_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "RJbPMtCutf", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409665586730, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "body": "hello", - "hsob_ts": 1409665660439, - "msgtype": "m.text" - }, - "event_id": "YUwRidLecu", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "ts": 1409665660439, - "type": "m.room.message", - "user_id": "@example:localhost" - }, - { - "content": { - "membership": "invite" - }, - "event_id": "YjNuBKnPsb", - "membership": "invite", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@myfriend:localhost", - "ts": 1409666426819, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join", - "prev": "join" - }, - "event_id": "KWwdDjNZnm", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666551582, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ], - "end": "s39_18_0", - "start": "t1-11_18_0" - }, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state": [ - { - "content": { - "creator": "@example:localhost" - }, - "event_id": "dMUoqVTZca", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.create", - "user_id": "@example:localhost" - }, - { - "content": { - "@example:localhost": 10, - "default": 0 - }, - "event_id": "wAumPSTsWF", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.power_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "join_rule": "public" - }, - "event_id": "jrLVqKHKiI", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.join_rules", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 10 - }, - "event_id": "WpmTgsNWUZ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.add_state_level", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 0 - }, - "event_id": "qUMBJyKsTQ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.send_event_level", - "user_id": "@example:localhost" - }, - { - "content": { - "ban_level": 5, - "kick_level": 5 - }, - "event_id": "YAaDmKvoUW", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.ops_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "membership": "invite" - }, - "event_id": "YjNuBKnPsb", - "membership": "invite", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@myfriend:localhost", - "ts": 1409666426819, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ] - } - ] - } - -This returns all the room information the user is invited/joined on, as well as -all of the presences relevant for these rooms. This can be a LOT of data. You -may just want the most recent event for each room. This can be achieved by -applying query parameters to ``limit`` this request:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN" - - { - "end": "s39_18_0", - "presence": [ - { - "content": { - "last_active_ago": 1279484, - "user_id": "@example:localhost" - }, - "type": "m.presence" - } - ], - "rooms": [ - { - "membership": "join", - "messages": { - "chunk": [ - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ], - "end": "s39_18_0", - "start": "t10-30_18_0" - }, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state": [ - { - "content": { - "creator": "@example:localhost" - }, - "event_id": "dMUoqVTZca", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.create", - "user_id": "@example:localhost" - }, - { - "content": { - "@example:localhost": 10, - "default": 0 - }, - "event_id": "wAumPSTsWF", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.power_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "join_rule": "public" - }, - "event_id": "jrLVqKHKiI", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.join_rules", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 10 - }, - "event_id": "WpmTgsNWUZ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.add_state_level", - "user_id": "@example:localhost" - }, - { - "content": { - "level": 0 - }, - "event_id": "qUMBJyKsTQ", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.send_event_level", - "user_id": "@example:localhost" - }, - { - "content": { - "ban_level": 5, - "kick_level": 5 - }, - "event_id": "YAaDmKvoUW", - "required_power_level": 10, - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "", - "ts": 1409665585188, - "type": "m.room.ops_levels", - "user_id": "@example:localhost" - }, - { - "content": { - "membership": "invite" - }, - "event_id": "YjNuBKnPsb", - "membership": "invite", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@myfriend:localhost", - "ts": 1409666426819, - "type": "m.room.member", - "user_id": "@example:localhost" - }, - { - "content": { - "avatar_url": null, - "displayname": null, - "membership": "join" - }, - "event_id": "JFLVteSvQc", - "membership": "join", - "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", - "state_key": "@example:localhost", - "ts": 1409666587265, - "type": "m.room.member", - "user_id": "@example:localhost" - } - ] - } - ] - } - -Getting live state ------------------- -Once you know which rooms the client has previously interacted with, you need to -listen for incoming events. This can be done like so:: - - curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN" - - { - "chunk": [], - "end": "s39_18_0", - "start": "s39_18_0" - } - -This will block waiting for an incoming event, timing out after several seconds. -Even if there are no new events (as in the example above), there will be some -pagination stream response keys. The client should make subsequent requests -using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from`` -query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access -_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the -client reopens your app after a period of inactivity, you can resume from where -you got up to in the event stream. If it has been a long period of inactivity, -there may be LOTS of events waiting for the user. In this case, you may wish to -get all state instead and then resume getting live state from a newer end token. - -NB: The timeout can be changed by adding a ``timeout`` query parameter, which is -in milliseconds. A timeout of 0 will not block. - - -Example application -------------------- -The following example demonstrates registration and login, live event streaming, -creating and joining rooms, sending messages, getting member lists and getting -historical messages for a room. This covers most functionality of a messaging -application. - -.. NOTE:: - `Try out the fiddle`__ - - .. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/example_app diff --git a/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.html b/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.html index 088ff7ac0f1..b7e874c2c60 100644 --- a/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.html +++ b/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.html @@ -1,5 +1,5 @@
-

This room creation / message sending demo requires a home server to be running on http://localhost:8008

+

This room creation / message sending demo requires a homeserver to be running on http://localhost:8008

diff --git a/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.js b/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.js index 9c346e2f64f..c16395acb23 100644 --- a/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.js +++ b/supporting-docs/howtos/jsfiddles/create_room_send_msg/demo.js @@ -19,7 +19,7 @@ $('.login').live('click', function() { showLoggedIn(data); }, error: function(err) { - var errMsg = "To try this, you need a home server running!"; + var errMsg = "To try this, you need a homeserver running!"; var errJson = $.parseJSON(err.responseText); if (errJson) { errMsg = JSON.stringify(errJson); diff --git a/supporting-docs/howtos/jsfiddles/event_stream/demo.html b/supporting-docs/howtos/jsfiddles/event_stream/demo.html index 7657780d28d..3de7b0813da 100644 --- a/supporting-docs/howtos/jsfiddles/event_stream/demo.html +++ b/supporting-docs/howtos/jsfiddles/event_stream/demo.html @@ -1,5 +1,5 @@
-

This event stream demo requires a home server to be running on http://localhost:8008

+

This event stream demo requires a homeserver to be running on http://localhost:8008

diff --git a/supporting-docs/howtos/jsfiddles/event_stream/demo.js b/supporting-docs/howtos/jsfiddles/event_stream/demo.js index acba8391fa3..65b118d27c1 100644 --- a/supporting-docs/howtos/jsfiddles/event_stream/demo.js +++ b/supporting-docs/howtos/jsfiddles/event_stream/demo.js @@ -58,7 +58,7 @@ $('.login').live('click', function() { showLoggedIn(data); }, error: function(err) { - var errMsg = "To try this, you need a home server running!"; + var errMsg = "To try this, you need a homeserver running!"; var errJson = $.parseJSON(err.responseText); if (errJson) { errMsg = JSON.stringify(errJson); diff --git a/supporting-docs/howtos/jsfiddles/example_app/demo.html b/supporting-docs/howtos/jsfiddles/example_app/demo.html index 7a9dffddd0f..a183f61db36 100644 --- a/supporting-docs/howtos/jsfiddles/example_app/demo.html +++ b/supporting-docs/howtos/jsfiddles/example_app/demo.html @@ -1,5 +1,5 @@