Compare commits

...

5 commits

Author SHA1 Message Date
Kévin Commaille 1ff7ca7bc1
Merge 73b1936dae into c7581356bf 2025-03-25 10:31:27 +00:00
Johannes Marbach c7581356bf
MSC4260: Reporting users (Client-Server API) (#2093)
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
* MSC4260: Reporting users (Client-Server API)

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>

* Add changelog

* Update data/api/client-server/report_content.yaml

Co-authored-by: Kévin Commaille <76261501+zecakeh@users.noreply.github.com>

* Move option to consistently respond with 200 to user reporting endpoint

* Move optional random delay to event and user reporting endpoints

* Make reason required for user and room reports

* Fix requiredness syntax

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
Co-authored-by: Kévin Commaille <76261501+zecakeh@users.noreply.github.com>
2025-03-21 13:33:26 -06:00
Kévin Commaille 73b1936dae
Upgrade Python in CI
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-25 18:35:51 +01:00
Kévin Commaille 8602a1ae8f
Add changelog
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-25 18:31:12 +01:00
Kévin Commaille 41de0b1bf7
Replace Hugo shortcodes in OpenAPI output
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-02-25 18:27:06 +01:00
6 changed files with 210 additions and 17 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 @@
Add `POST /_matrix/client/v3/users/{userId}/report` as per [MSC4260](https://github.com/matrix-org/matrix-spec-proposals/pull/4260).

View file

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

View file

@ -29,3 +29,9 @@ is in before accepting a report.
based on whether or not the reporting user is joined to the room. This is
because users can be exposed to harmful content without being joined to a
room. For instance, through room directories or invites.
{{% added-in v="1.14" %}} Similarly, servers MUST NOT restrict user reports
based on whether or not the reporting user is joined to any rooms that the
reported user is joined to. This is because users can be exposed to harmful
content without being joined to a room. For instance, through user
directories or invites.

View file

@ -45,7 +45,9 @@ paths:
properties:
reason:
type: string
description: The reason the room is being reported.
description: The reason the room is being reported. May be blank.
required:
- reason
required: true
security:
- accessTokenQuery: []
@ -88,12 +90,11 @@ paths:
Reports an event as inappropriate to the server, which may then notify
the appropriate people. The caller must be joined to the room to report
it.
It might be possible for clients to deduce whether an event exists by
timing the response, as only a report for an event that does exist
will require the homeserver to check whether a user is joined to
the room. To combat this, homeserver implementations should add
a random delay when generating a response.
Furthermore, it might be possible for clients to deduce whether a reported
event exists by timing the response. This is because only a report for an
existing event will require the homeserver to do further processing. To
combat this, homeservers MAY add a random delay when generating a response.
operationId: reportEvent
parameters:
- in: path
@ -164,6 +165,88 @@ paths:
}
tags:
- Reporting content
"/users/{userId}/report":
post:
x-addedInMatrixVersion: "1.14"
summary: Report a user as inappropriate.
description: |-
Reports a user as inappropriate to the server, which may then notify
the appropriate people. How such information is delivered is left up to
implementations. The caller is not required to be joined to any rooms
that the reported user is joined to.
Clients may wish to [ignore](#ignoring-users) users after reporting them.
Clients could infer whether a reported user exists based on the 404 response.
Homeservers that wish to conceal this information MAY return 200 responses
regardless of the existence of the reported user.
Furthermore, it might be possible for clients to deduce whether a reported
user exists by timing the response. This is because only a report for an
existing user will require the homeserver to do further processing. To
combat this, homeservers MAY add a random delay when generating a response.
operationId: reportUser
parameters:
- in: path
name: userId
description: The user being reported.
required: true
example: "@someguy:example.com"
schema:
type: string
format: mx-user-id
pattern: "^@"
requestBody:
content:
application/json:
schema:
type: object
example: {
"reason": "this makes me sad"
}
properties:
reason:
type: string
description: The reason the room is being reported. May be blank.
required:
- reason
required: true
security:
- accessTokenQuery: []
- accessTokenBearer: []
responses:
"200":
description: |
The user has been reported successfully or the server chose
to not disclose whether the users exists.
content:
application/json:
schema:
type: object
examples:
response:
value: {}
"404":
description: |-
The user was not found on the homeserver.
content:
application/json:
schema:
$ref: definitions/errors/error.yaml
examples:
response:
value: {
"errcode": "M_NOT_FOUND",
"error": "The user was not found."
}
"429":
description: This request was rate-limited.
content:
application/json:
schema:
$ref: definitions/errors/rate_limited.yaml
tags:
- Reporting content
servers:
- url: "{protocol}://{hostname}{basePath}"
variables:

View file

@ -32,6 +32,29 @@ 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):
#
# {{%
# <one or more whitespaces>
# <name of shortcode>
# <one or more whitespaces>
# (optional <list of parameters><one or more whitespaces>)
# %}}
#
# With:
#
# * <name of shortcode>: any word character and `-` and `/`.
# * <list of parameters>: any character except `}`, must not start or end with a
# whitespace.
shortcode_regex = re.compile(r"\{\{\%\s+(?P<name>[\w\/-]+)\s+(?:(?P<params>[^\s\}][^\}]+[^\s\}])\s+)?\%\}\}", re.ASCII)
# 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 +67,95 @@ 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(text, match, replacement):
"""Replaces the regex match by the replacement in the text."""
return text[:match.start()] + replacement + text[match.end():]
def replace_shortcode(text, 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
"""
if shortcode['name'].startswith("/"):
# This is the end of the shortcode, just remove it.
return replace_match(text, shortcode['match'], "")
match shortcode['name']:
case "boxes/note":
text = replace_match(text, shortcode['match'], "**NOTE:** ")
case "boxes/rationale":
text = replace_match(text, shortcode['match'], "**RATIONALE:** ")
case "boxes/warning":
text = replace_match(text, shortcode['match'], "**WARNING:** ")
case "added-in":
version = shortcode['params']['v']
if not version:
raise ValueError("Missing parameter `v` for `added-in` shortcode")
text = replace_match(text, shortcode['match'], f"**[Added in `v{version}`]** ")
case "changed-in":
version = shortcode['params']['v']
if not version:
raise ValueError("Missing parameter `v` for `changed-in` shortcode")
text = replace_match(text, shortcode['match'], f"**[Changed in `v{version}`]** ")
case _:
raise ValueError("Unknown shortcode", shortcode['name'])
return text
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 match := shortcode_regex.search(text):
# Parse the parameters of the shortcode
params = {}
if match['params']:
for param in shortcode_params_regex.finditer(match['params']):
if param['key']:
params[param['key']] = param['value']
shortcode = {
'name': match['name'],
'params': params,
'match': match,
}
text = replace_shortcode(text, 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 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 +265,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)