Compare commits

...

7 commits

Author SHA1 Message Date
Johannes Marbach 9a8b6be393
Merge 6656f00bee into ca9c376076 2025-05-08 05:58:40 -06:00
Kim Brose ca9c376076
Clarify Well-Known URIs (#2140)
Some checks failed
Spec / 🔎 Validate OpenAPI specifications (push) Has been cancelled
Spec / 🔎 Check Event schema examples (push) Has been cancelled
Spec / 🔎 Check OpenAPI definitions examples (push) Has been cancelled
Spec / 🔎 Check JSON Schemas inline examples (push) Has been cancelled
Spec / ⚙️ Calculate baseURL for later jobs (push) Has been cancelled
Spec / 📢 Run towncrier for changelog (push) Has been cancelled
Spell Check / Spell Check with Typos (push) Has been cancelled
Spec / 🐍 Build OpenAPI definitions (push) Has been cancelled
Spec / 📖 Build the spec (push) Has been cancelled
Spec / 🔎 Validate generated HTML (push) Has been cancelled
Spec / 📖 Build the historical backup spec (push) Has been cancelled
* Clarify Well-Known URIs

Signed-off-by: HarHarLinks <2803622+HarHarLinks@users.noreply.github.com>

* Fix section link

---------

Signed-off-by: HarHarLinks <2803622+HarHarLinks@users.noreply.github.com>
2025-05-08 13:55:51 +02:00
Kévin Commaille fe46e0c363
Replace Hugo shortcodes in OpenAPI output (#2088)
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-05-08 09:29:32 +00:00
Andrew Morgan a8c326962a
Add a note to the federation invite endpoints that invites can be sent twice (#2067)
... as this may be non-obvious when implementing behaviour that is triggered by an incoming invite
event.

See https://github.com/matrix-org/matrix-spec/issues/2062 for more context.

Co-authored-by: Kévin Commaille <76261501+zecakeh@users.noreply.github.com>
2025-05-08 10:09:45 +01:00
Johannes Marbach 6656f00bee Add missing x-addedInMatrixVersion 2025-03-28 13:33:18 +01:00
Johannes Marbach 00fdf603a9 Add changelog 2025-03-28 12:55:10 +01:00
Johannes Marbach 6b7268ab47 MSC4147: Including device keys with Olm-encrypted events
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2025-03-28 12:52:02 +01:00
16 changed files with 269 additions and 43 deletions

View file

@ -2,6 +2,7 @@ name: "Spec"
env:
HUGO_VERSION: 0.139.0
PYTHON_VERSION: 3.13
on:
push:
@ -40,7 +41,7 @@ jobs:
- name: " Setup Python"
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies"
@ -59,7 +60,7 @@ jobs:
- name: " Setup Python"
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies"
@ -78,7 +79,7 @@ jobs:
- name: " Setup Python"
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies"
@ -120,7 +121,7 @@ jobs:
- name: " Setup Python"
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: scripts/requirements.txt
- name: " Install dependencies"
@ -172,7 +173,7 @@ jobs:
- name: " Setup Python"
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: ${{ env.PYTHON_VERSION }}
- name: " Install towncrier"
run: "pip install 'towncrier'"
- name: "Generate changelog"

View file

@ -0,0 +1 @@
Include device keys with Olm-encrypted events as per [MSC4147](https://github.com/matrix-org/matrix-spec-proposals/pull/4147).

View file

@ -0,0 +1 @@
Clarify that Well-Known URIs are available on the server name's hostname. Contributed by @HarHarLinks.

View file

@ -0,0 +1 @@
Replace Hugo shortcodes in OpenAPI output.

View file

@ -0,0 +1 @@
Add a note to the invite endpoints that invites to local users may be received twice over federation if the homeserver is already in the room.

View file

@ -0,0 +1 @@
Clarify that Well-Known URIs are available on the server name's hostname. Contributed by @HarHarLinks.

View file

@ -371,15 +371,23 @@ valid data was obtained, but no server is available to serve the client.
No further guess should be attempted and the user should make a
conscientious decision what to do next.
### Well-known URI
### Well-known URIs
Matrix facilitates automatic discovery for the Client-Server API base URL and more via the
[RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615) "Well-Known URI" method.
This method uses JSON files at a predetermined location on the root path `/.well-known/` to
specify parameter values.
{{% boxes/note %}}
Diverging from the rest of the endpoints in the Client-Server spec, these files can not be provided
on the base URL that the Client-Server API is reachable on, as it is yet to be discovered.
Instead, they can be reached via HTTPS on the [server name](/appendices/#server-name)'s hostname as domain.
Servers hosting the `.well-known` JSON file SHOULD offer CORS headers,
as per the [CORS](#web-browser-clients) section in this specification.
{{% /boxes/note %}}
The `.well-known` method uses a JSON file at a predetermined location to
specify parameter values. The flow for this method is as follows:
The flow for auto-discovery is as follows:
1. Extract the [server name](/appendices/#server-name) from the user's Matrix ID by splitting the
Matrix ID at the first colon.
@ -415,10 +423,17 @@ specify parameter values. The flow for this method is as follows:
{{% http-api spec="client-server" api="wellknown" %}}
{{% http-api spec="client-server" api="versions" %}}
{{% http-api spec="client-server" api="support" %}}
### API Versions
Upon connecting, the Matrix client and server need to negotiate which version of the specification
they commonly support, as the API evolves over time. The server advertises its supported versions
and optionally unstable features to the client, which can then go on to make requests to the
endpoints it supports.
{{% http-api spec="client-server" api="versions" %}}
## Client Authentication
Most API endpoints require the user to identify themselves by presenting

View file

@ -1512,20 +1512,7 @@ message.
The plaintext payload is of the form:
```json
{
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
}
}
```
{{% definition path="api/client-server/definitions/olm_payload" %}}
The type and content of the plaintext message event are given in the
payload.
@ -1536,15 +1523,19 @@ claiming to have sent messages which they didn't. `sender` must
correspond to the user who sent the event, `recipient` to the local
user, and `recipient_keys` to the local ed25519 key.
Clients must confirm that the `sender_key` property in the cleartext
`m.room.encrypted` event body, and the `keys.ed25519` property in the
decrypted plaintext, match the keys returned by
[`/keys/query`](#post_matrixclientv3keysquery) for
the given user. Clients must also verify the signature of the keys from the
`/keys/query` response. Without this check, a client cannot be sure that
the sender device owns the private part of the ed25519 key it claims to
have in the Olm payload. This is crucial when the ed25519 key corresponds
to a verified device.
Clients must ensure that the sending device owns the private part of
the ed25519 key it claims to have in the Olm payload. This is crucial
when the ed25519 key corresponds to a verified device. To perform
this check, clients MUST confirm that the `sender_key` property in the
cleartext `m.room.encrypted` event body, and the `keys.ed25519` property
in the decrypted plaintext, match the keys under the `sender_device_keys`
property. Additionally, clients MUST also verify the signature of the keys.
If `sender_device_keys` is absent, clients MUST retrieve the sender's
keys from [`/keys/query`](#post_matrixclientv3keysquery) instead. This
will not allow them to verify key ownership if the sending device was
logged out or had its keys reset since sending the event. Therefore,
clients MUST populate the `sender_device_keys` property when sending
events themselves.
If a client has multiple sessions established with another device, it
should use the session from which it last received and successfully

View file

@ -119,7 +119,8 @@ to send. The process overall is as follows:
server must present a valid certificate for the hostname.
3. If the hostname is not an IP literal, a regular HTTPS request is
made to `https://<hostname>/.well-known/matrix/server`, expecting
made to `https://<hostname>/.well-known/matrix/server` (according to
[RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615)), expecting
the schema defined later in this section. 30x redirects should be
followed, however redirection loops should be avoided. Responses
(successful or otherwise) to the `/.well-known` endpoint should be

View file

@ -0,0 +1,88 @@
# Copyright 2025 The Matrix.org Foundation C.I.C
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
type: object
title: OlmPayload
description: |-
The plaintext payload of Olm message events.
properties:
type:
type: string
description: The type of the event.
content:
type: object
description: The event content.
sender:
type: string
description: The user ID of the event sender.
recipient:
type: string
description: The user ID of the intended event recipient.
recipient_keys:
description: The recipient's signing keys of the encrypted event.
$ref: "#/components/schemas/SigningKeys"
keys:
$ref: "#/components/schemas/SigningKeys"
description: The sender's signing keys of the encrypted event.
sender_device_keys:
$ref: device_keys.yaml
description: The sender's device keys.
x-addedInMatrixVersion: "1.15"
required:
- type
- content
- sender
- recipient
- recipient_keys
- keys
components:
schemas:
SigningKeys:
type: object
title: SigningKeys
description: Public keys used for an `m.olm.v1.curve25519-aes-sha2` event.
properties:
ed25519:
type: string
description: The Ed25519 public key encoded using unpadded base64.
required:
- ed25519
example: {
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
},
"sender_device_keys": {
"algorithms": ["<supported>", "<algorithms>"],
"user_id": "<user_id>",
"device_id": "<device_id>",
"keys": {
"ed25519:<device_id>": "<sender_ed25519_key>",
"curve25519:<device_id>": "<sender_curve25519_key>"
},
"signatures": {
"<user_id>": {
"ed25519:<device_id>": "<device_signature>",
"ed25519:<ssk_id>": "<ssk_signature>",
}
}
}
}

View file

@ -22,9 +22,12 @@ paths:
description: |-
Gets server admin contact and support page of the domain.
Like the [well-known discovery URI](/client-server-api/#well-known-uri),
this should be accessed with the hostname of the homeserver by making a
{{% boxes/note %}}
Like the [well-known discovery URI](/client-server-api/#well-known-uris),
this endpoint should be accessed with the hostname of the homeserver's
[server name](/appendices/#server-name) by making a
GET request to `https://hostname/.well-known/matrix/support`.
{{% /boxes/note %}}
Note that this endpoint is not necessarily handled by the homeserver.
It may be served by another webserver, used for discovering support

View file

@ -26,6 +26,12 @@ paths:
suitably namespaced for each application and reduces the risk of
clashes.
{{% boxes/note %}}
This endpoint should be accessed with the hostname of the homeserver's
[server name](/appendices/#server-name) by making a
GET request to `https://hostname/.well-known/matrix/client`.
{{% /boxes/note %}}
Note that this endpoint is not necessarily handled by the homeserver,
but by another webserver, to be used for discovering the homeserver URL.
operationId: getWellknown

View file

@ -20,7 +20,7 @@ paths:
put:
summary: Invites a remote user to a room
description: |-
Invites a remote user to a room. Once the event has been signed by both the inviting
Invites a remote user to a room. Once the event has been signed by both the inviting
homeserver and the invited homeserver, it can be sent to all of the servers in the
room by the inviting homeserver.
@ -32,6 +32,10 @@ paths:
[room version specification](/rooms) for precise event formats. **The request and response
bodies here describe the common event fields in more detail and may be missing other
required fields for a PDU.**
Also note that if the remote homeserver is already in the room, it will receive the
invite event twice; once through this endpoint, and again through a [federation
transaction](/server-server-api/#transactions).
operationId: sendInviteV1
security:
- signedRequest: []

View file

@ -24,7 +24,7 @@ paths:
This API is nearly identical to the v1 API with the exception of the request
body being different, and the response format fixed.
Invites a remote user to a room. Once the event has been signed by both the inviting
Invites a remote user to a room. Once the event has been signed by both the inviting
homeserver and the invited homeserver, it can be sent to all of the servers in the
room by the inviting homeserver.
@ -36,6 +36,10 @@ paths:
[room version specification](/rooms) for precise event formats. **The request and response
bodies here describe the common event fields in more detail and may be missing other
required fields for a PDU.**
Also note that if the remote homeserver is already in the room, it will receive the
invite event twice; once through this endpoint, and again through a [federation
transaction](/server-server-api/#transactions).
operationId: sendInviteV2
security:
- signedRequest: []

View file

@ -24,6 +24,12 @@ paths:
Gets information about the delegated server for server-server communication
between Matrix homeservers. Servers should follow 30x redirects, carefully
avoiding redirect loops, and use normal X.509 certificate validation.
{{% boxes/note %}}
This endpoint should be accessed with the hostname of the homeserver's
[server name](/appendices/#server-name) by making a
GET request to `https://hostname/.well-known/matrix/server`.
{{% /boxes/note %}}
operationId: getWellKnown
responses:
"200":

View file

@ -32,6 +32,35 @@ import yaml
scripts_dir = os.path.dirname(os.path.abspath(__file__))
api_dir = os.path.join(os.path.dirname(scripts_dir), "data", "api")
# Finds a Hugo shortcode in a string.
#
# A shortcode is defined as (newlines and whitespaces for presentation purpose):
#
# {{%
# <zero or more whitespaces>
# <name of shortcode>
# (optional <one or more whitespaces><list of parameters>)
# <zero or more whitespaces>
# %}}
#
# With:
#
# * <name of shortcode>: any word character and `-` and `/`. `re.ASCII` is used to only match
# ASCII characters in the name.
# * <list of parameters>: any character except `}`, must not start or end with a
# whitespace.
shortcode_regex = re.compile(r"""\{\{\% # {{%
\s* # zero or more whitespaces
(?P<name>[\w/-]+) # name of shortcode
(?:\s+(?P<params>[^\s\}][^\}]+[^\s\}]))? # optional list of parameters
\s* # zero or more whitespaces
\%\}\} # %}}""", re.ASCII | re.VERBOSE)
# Parses the parameters of a Hugo shortcode.
#
# For simplicity, this currently only supports the `key="value"` format.
shortcode_params_regex = re.compile(r"(?P<key>\w+)=\"(?P<value>[^\"]+)\"", re.ASCII)
def prefix_absolute_path_references(text, base_url):
"""Adds base_url to absolute-path references.
@ -44,17 +73,90 @@ def prefix_absolute_path_references(text, base_url):
"""
return text.replace("](/", "]({}/".format(base_url))
def edit_links(node, base_url):
"""Finds description nodes and makes any links in them absolute."""
def replace_match(match, replacement):
"""Replaces the regex match by the replacement in the text."""
return match.string[:match.start()] + replacement + match.string[match.end():]
def replace_shortcode(shortcode):
"""Replaces the shortcode by a Markdown fallback in the text.
The supported shortcodes are:
* boxes/note, boxes/rationale, boxes/warning
* added-in, changed-in
All closing tags (`{{ /shortcode }}`) are replaced with the empty string.
"""
if shortcode['name'].startswith("/"):
# This is the end of the shortcode, just remove it.
return replace_match(shortcode, "")
# Parse the parameters of the shortcode
params = {}
if shortcode['params']:
for param in shortcode_params_regex.finditer(shortcode['params']):
if param['key']:
params[param['key']] = param['value']
match shortcode['name']:
case "boxes/note":
return replace_match(shortcode, "**NOTE:** ")
case "boxes/rationale":
return replace_match(shortcode, "**RATIONALE:** ")
case "boxes/warning":
return replace_match(shortcode, "**WARNING:** ")
case "added-in":
version = params['v']
if not version:
raise ValueError("Missing parameter `v` for `added-in` shortcode")
return replace_match(shortcode, f"**[Added in `v{version}`]** ")
case "changed-in":
version = params['v']
if not version:
raise ValueError("Missing parameter `v` for `changed-in` shortcode")
return replace_match(shortcode, f"**[Changed in `v{version}`]** ")
case _:
raise ValueError("Unknown shortcode", shortcode['name'])
def find_and_replace_shortcodes(text):
"""Finds Hugo shortcodes and replaces them by a Markdown fallback.
The supported shortcodes are:
* boxes/note, boxes/rationale, boxes/warning
* added-in, changed-in
"""
# We use a `while` loop with `search` instead of a `for` loop with
# `finditer`, because as soon as we start replacing text, the
# indices of the match are invalid.
while shortcode := shortcode_regex.search(text):
text = replace_shortcode(shortcode)
return text
def edit_descriptions(node, base_url):
"""Finds description nodes and apply fixes to them.
The fixes that are applied are:
* Make links absolute
* Replace Hugo shortcodes
"""
if isinstance(node, dict):
for key in node:
if isinstance(node[key], str):
node[key] = prefix_absolute_path_references(node[key], base_url)
node[key] = find_and_replace_shortcodes(node[key])
else:
edit_links(node[key], base_url)
edit_descriptions(node[key], base_url)
elif isinstance(node, list):
for item in node:
edit_links(item, base_url)
edit_descriptions(item, base_url)
parser = argparse.ArgumentParser(
"dump-openapi.py - assemble the OpenAPI specs into a single JSON file"
@ -164,7 +266,7 @@ for filename in os.listdir(selected_api_dir):
if untagged != 0:
print("{} untagged operations, you may want to look into fixing that.".format(untagged))
edit_links(output, base_url)
edit_descriptions(output, base_url)
print("Generating %s" % output_file)