diff --git a/changelogs/client_server/newsfragments/1812.feature b/changelogs/client_server/newsfragments/1812.feature new file mode 100644 index 000000000..baa9aa7d6 --- /dev/null +++ b/changelogs/client_server/newsfragments/1812.feature @@ -0,0 +1 @@ +Specify terms of services at registration, as per [MSC1692](https://github.com/matrix-org/matrix-spec-proposals/pull/1692). diff --git a/changelogs/internal/newsfragments/1814.clarification b/changelogs/internal/newsfragments/1814.clarification new file mode 100644 index 000000000..a540ea7e0 --- /dev/null +++ b/changelogs/internal/newsfragments/1814.clarification @@ -0,0 +1 @@ +Add support for rendering string formats. diff --git a/changelogs/server_server/newsfragments/1818.clarification b/changelogs/server_server/newsfragments/1818.clarification new file mode 100644 index 000000000..8c50b6ace --- /dev/null +++ b/changelogs/server_server/newsfragments/1818.clarification @@ -0,0 +1 @@ +Clarify that whitespace around commas is allowed in the `X-Matrix` `Authorization` header value params list. \ No newline at end of file diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index ba5272755..b0ce72896 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -546,8 +546,10 @@ 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: - HTTP/1.1 401 Unauthorized - Content-Type: application/json +``` +HTTP/1.1 401 Unauthorized +Content-Type: application/json +``` ```json { @@ -590,8 +592,10 @@ 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: - POST /_matrix/client/v3/endpoint HTTP/1.1 - Content-Type: application/json +``` +POST /_matrix/client/v3/endpoint HTTP/1.1 +Content-Type: application/json +``` ```json { @@ -611,8 +615,10 @@ 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: - HTTP/1.1 401 Unauthorized - Content-Type: application/json +``` +HTTP/1.1 401 Unauthorized +Content-Type: application/json +``` ```json { @@ -643,8 +649,10 @@ 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: - HTTP/1.1 401 Unauthorized - Content-Type: application/json +``` +HTTP/1.1 401 Unauthorized +Content-Type: application/json +``` ```json { @@ -671,8 +679,10 @@ status 401 response as above, with the addition of the standard If the request fails for a reason other than authentication, the server returns an error message in the standard format. For example: - HTTP/1.1 400 Bad request - Content-Type: application/json +``` +HTTP/1.1 400 Bad request +Content-Type: application/json +``` ```json { @@ -970,6 +980,129 @@ in the registration process that their token has expired. {{% http-api spec="client-server" api="registration_tokens" %}} +##### Terms of service at registration + +{{% added-in v="1.11" %}} + +| Type | Description | +|--------------------------|--------------------------------------------------------------------------| +| `m.login.terms` | Authentication requires the user to accept a set of policy documents. | + +{{% boxes/note %}} +The `m.login.terms` authentication type is only valid on the +[`/register`](#post_matrixclientv3register) endpoint. +{{% /boxes/note %}} + +This authentication type is used when the homeserver requires new users to +accept a given set of policy documents, such as a terms of service and a privacy +policy. There may be many different types of documents, all of which are +versioned and presented in (potentially) multiple languages. + +When the server requires the user to accept some terms, it does so by returning +a 401 response to the `/register` request, where the response body includes +`m.login.terms` in the `flows` list, and the `m.login.terms` property in the +`params` object has the structure [shown below](#definition-mloginterms-params). + +If a client encounters an invalid parameter, registration should stop with an +error presented to the user. + +The client should present the user with a checkbox to accept each policy, +including a link to the provided URL. Once the user has done so, the client +submits an `auth` dict with just the `type` and `session`, as follows, to +indicate that all of the policies have been accepted: + +```json +{ + "type": "m.login.terms", + "session": "" +} +``` + +The server is expected to track which document versions it presented to the +user during registration, if applicable. + + +**Example** + +1. A client might submit a registration request as follows: + + ``` + POST /_matrix/client/v3/register + ``` + ```json + { + "username": "cheeky_monkey", + "password": "ilovebananas" + } + ``` + +2. The server requires the user to accept some terms of service before + registration, so returns the following response: + + ``` + HTTP/1.1 401 Unauthorized + Content-Type: application/json + ``` + ```json + { + "flows": [ + { "stages": [ "m.login.terms" ] } + ], + "params": { + "m.login.terms": { + "policies": { + "terms_of_service": { + "version": "1.2", + "en": { + "name": "Terms of Service", + "url": "https://example.org/somewhere/terms-1.2-en.html" + }, + "fr": { + "name": "Conditions d'utilisation", + "url": "https://example.org/somewhere/terms-1.2-fr.html" + } + } + } + } + }, + "session": "kasgjaelkgj" + } + ``` + +3. The client presents the list of documents to the user, inviting them to + accept the polices. + +4. The client repeats the registration request, confirming that the user has + accepted the documents: + ``` + POST /_matrix/client/v3/register + ``` + ```json + { + "username": "cheeky_monkey", + "password": "ilovebananas", + "auth": { + "type": "m.login.terms", + "session": "kasgjaelkgj" + } + } + ``` + +5. All authentication steps have now completed, so the request is successful: + ``` + HTTP/1.1 200 OK + Content-Type: application/json + ``` + ```json + { + "access_token": "abc123", + "device_id": "GHTYAJCE", + "user_id": "@cheeky_monkey:matrix.org" + } + ``` + +{{% definition path="api/client-server/definitions/m.login.terms_params" %}} + #### Fallback Clients cannot be expected to be able to know how to process every diff --git a/content/server-server-api.md b/content/server-server-api.md index fb1b06f4e..e92d871c5 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -350,9 +350,10 @@ def authorization_headers(origin_name, origin_signing_key, The format of the Authorization header is given in [RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235#section-2.1). In -summary, the header begins with authorization scheme `X-Matrix`, followed by -one or more spaces, followed by a comma-separated list of parameters written as -name=value pairs. The names are case insensitive and order does not matter. The +summary, the header begins with authorization scheme `X-Matrix`, followed by one +or more spaces, followed by a comma-separated list of parameters written as +name=value pairs. Zero or more spaces and tabs around each comma are allowed. +The names are case insensitive and order does not matter. The values must be enclosed in quotes if they contain characters that are not allowed in `token`s, as defined in [RFC 7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6); if a @@ -363,8 +364,9 @@ replaced by the character that follows the backslash. For compatibility with older servers, the sender should - only include one space after `X-Matrix`, -- only use lower-case names, and -- avoid using backslashes in parameter values. +- only use lower-case names, +- avoid using backslashes in parameter values, and +- avoid including whitespace around the commas between name=value pairs. For compatibility with older servers, the recipient should allow colons to be included in values without requiring the value to be enclosed in quotes. diff --git a/data/api/client-server/definitions/m.login.terms_params.yaml b/data/api/client-server/definitions/m.login.terms_params.yaml new file mode 100644 index 000000000..67001c189 --- /dev/null +++ b/data/api/client-server/definitions/m.login.terms_params.yaml @@ -0,0 +1,82 @@ +# Copyright 2024 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: m.login.terms params +description: Schema for `m.login.terms` entry in the `params` object in a User-Interactive Authentication response. +required: ['policies'] +properties: + policies: + type: object + description: | + A map from "Policy ID" to the current definition of this policy document. The Policy ID is a unique + identifier for a given policy document, using the [Opaque Identifier Grammar](/appendices/#opaque-identifiers). + additionalProperties: + type: object + title: Policy Definition + required: [version] + properties: + version: + type: string + description: | + The version of this policy document. This is provided as a convenience for the client, + and uses the [Opaque Identifier Grammar](/appendices/#opaque-identifiers). + additionalProperties: + type: object + title: Policy Translation + required: [name, url] + description: | + Map from language codes to details of the document in that language. + Language codes SHOULD be formatted as per [Section 2.2 of RFC + 5646](https://datatracker.ietf.org/doc/html/rfc5646#section-2.2), + though some implementations may use an underscore instead of dash + (for example, `en_US` instead of `en-US`). + properties: + name: + type: string + description: | + The name of this document, in the appropriate language. An + arbitrary string with no specified maximum length. + url: + type: string + description: | + A link to the text of this document, in the appropriate + language. MUST be a valid URI with scheme `https://` or + `http://`. Insecure HTTP is discouraged. +example: { + "policies": { + "terms_of_service": { + "version": "1.2", + "en": { + "name": "Terms of Service", + "url": "https://example.org/somewhere/terms-1.2-en.html" + }, + "fr": { + "name": "Conditions d'utilisation", + "url": "https://example.org/somewhere/terms-1.2-fr.html" + } + }, + "privacy_policy": { + "version": "1.2", + "en": { + "name": "Privacy Policy", + "url": "https://example.org/somewhere/privacy-1.2-en.html" + }, + "fr": { + "name": "Politique de confidentialité", + "url": "https://example.org/somewhere/privacy-1.2-fr.html" + } + } + } +} \ No newline at end of file diff --git a/data/custom-formats.yaml b/data/custom-formats.yaml index 489ad6c27..2075c1464 100644 --- a/data/custom-formats.yaml +++ b/data/custom-formats.yaml @@ -35,4 +35,9 @@ mx-event-id: mx-room-id: title: Room ID url: /appendices#room-ids - # regex: "^!" \ No newline at end of file + # regex: "^!" + +uri: + title: URI + url: http://tools.ietf.org/html/rfc3986 + # no regex diff --git a/layouts/partials/openapi/render-object-table.html b/layouts/partials/openapi/render-object-table.html index 72ea5a8ae..d2b09acb6 100644 --- a/layouts/partials/openapi/render-object-table.html +++ b/layouts/partials/openapi/render-object-table.html @@ -128,6 +128,8 @@ * `anchor`: optional HTML element id for the target type, which will be used to link to it. + * `format`: optional string for the format of the type, used for strings. + */}} {{ define "partials/property-type" }} {{ $type := "" }} @@ -143,6 +145,15 @@ {{ $items := .items }} {{ $inner_type := partial "property-type" $items }} {{ $type = delimit (slice "[" $inner_type "]") "" }} + {{ else if eq .type "string" }} + {{ $type = "string" }} + + {{/* If the string uses a known format, use it. */}} + {{ with .format }} + {{ with partial "custom-format" . }} + {{ $type = . }} + {{ end }} + {{ end }} {{ else if or (reflect.IsSlice .type) .oneOf }} {{/* It's legal to specify an array of types. @@ -167,7 +178,7 @@ {{ $type = delimit $types "|" }} {{ else }} - {{/* A simple type like string or boolean */}} + {{/* A simple type like integer or boolean */}} {{ $type = (htmlEscape .type) }} {{ end }} @@ -241,8 +252,8 @@ {{ range $formatId, $formatType := $formatMap.Values }} {{ $formatKey := "string" }} {{ if ne $formatId "string" }} - {{ with index site.Data "custom-formats" $formatId }} - {{ $formatKey = printf "%s" (htmlEscape .url) (htmlEscape .title) }} + {{ with partial "custom-format" $formatId }} + {{ $formatKey = . }} {{ else }} {{ errorf "Unsupported value for `x-pattern-format`: %s" $formatId }} {{ end }} @@ -290,3 +301,18 @@ {{ if (index .property "x-addedInMatrixVersion") }}{{ partial "added-in" (dict "v" (index .property "x-addedInMatrixVersion")) }}{{ end -}} {{ if (index .property "x-changedInMatrixVersion") }}{{ partial "changed-in" (dict "changes_dict" (index .property "x-changedInMatrixVersion")) }}{{ end -}} {{ end }} + + +{{/* + Computes the type to display for a string format, given the identifier of + the format as a string. +*/}} +{{ define "partials/custom-format" }} + {{ $customFormat := "" }} + + {{ with index site.Data "custom-formats" . }} + {{ $customFormat = printf "%s" (htmlEscape .url) (htmlEscape .title) }} + {{ end }} + + {{ return $customFormat }} +{{ end }}