From afd50184940a957b6b0023acd284be07e177147b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 28 Aug 2019 21:56:09 -0600 Subject: [PATCH] Spec the terms of service handling for identity servers Part of MSC2140 Convert status codes to strings if there is a string status code. Fixes a build error when we mix 4xx and 403 in the same definition. We also have to correct stringified numbers to pass the build. --- api/identity/email_associations.yaml | 2 +- api/identity/phone_associations.yaml | 2 +- api/identity/v2_associations.yaml | 25 +++ api/identity/v2_auth.yaml | 22 +++ api/identity/v2_email_associations.yaml | 35 +++- api/identity/v2_invitation_signing.yaml | 11 ++ api/identity/v2_phone_associations.yaml | 35 +++- api/identity/v2_store_invite.yaml | 11 ++ api/identity/v2_terms.yaml | 149 ++++++++++++++++++ .../identity_service/newsfragments/2258.new | 1 + event-schemas/examples/m.accepted_terms | 10 ++ event-schemas/schema/m.accepted_terms | 23 +++ scripts/templating/matrix_templates/units.py | 16 +- specification/identity_service_api.rst | 27 ++++ 14 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 api/identity/v2_terms.yaml create mode 100644 changelogs/identity_service/newsfragments/2258.new create mode 100644 event-schemas/examples/m.accepted_terms create mode 100644 event-schemas/schema/m.accepted_terms diff --git a/api/identity/email_associations.yaml b/api/identity/email_associations.yaml index 6ac28cd0..69ec7c58 100644 --- a/api/identity/email_associations.yaml +++ b/api/identity/email_associations.yaml @@ -165,7 +165,7 @@ paths: description: The token generated by the ``requestToken`` call and emailed to the user. x-example: atoken responses: - "200": + 200: description: Email address is validated. "3xx": description: |- diff --git a/api/identity/phone_associations.yaml b/api/identity/phone_associations.yaml index 28312a4d..65bbff0c 100644 --- a/api/identity/phone_associations.yaml +++ b/api/identity/phone_associations.yaml @@ -167,7 +167,7 @@ paths: description: The token generated by the ``requestToken`` call and sent to the user. x-example: atoken responses: - "200": + 200: description: Phone number is validated. "3xx": description: |- diff --git a/api/identity/v2_associations.yaml b/api/identity/v2_associations.yaml index d1b29a8f..b9039a18 100644 --- a/api/identity/v2_associations.yaml +++ b/api/identity/v2_associations.yaml @@ -95,6 +95,17 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" "/3pid/bind": post: summary: Publish an association between a session and a Matrix user ID. @@ -208,6 +219,17 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" "/3pid/unbind": post: summary: Remove an association between a session and a Matrix user ID. @@ -294,6 +316,9 @@ paths: This may also be returned if the identity server does not support the chosen authentication method (such as blocking homeservers from unbinding identifiers). + + Another common error code is ``M_TERMS_NOT_SIGNED`` where the user + needs to `agree to more terms`_ in order to continue. examples: application/json: { "errcode": "M_FORBIDDEN", diff --git a/api/identity/v2_auth.yaml b/api/identity/v2_auth.yaml index 16864b8e..a34f679c 100644 --- a/api/identity/v2_auth.yaml +++ b/api/identity/v2_auth.yaml @@ -80,6 +80,17 @@ paths: type: string description: The user ID which registered the token. required: ['user_id'] + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" "/account/logout": post: summary: Logs out an access token, rendering it unusable. @@ -107,3 +118,14 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" diff --git a/api/identity/v2_email_associations.yaml b/api/identity/v2_email_associations.yaml index eff18eaf..76c3747e 100644 --- a/api/identity/v2_email_associations.yaml +++ b/api/identity/v2_email_associations.yaml @@ -74,6 +74,17 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" "/validate/email/submitToken": post: summary: Validate ownership of an email address. @@ -135,6 +146,17 @@ paths: type: boolean description: Whether the validation was successful or not. required: ['success'] + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" get: summary: Validate ownership of an email address. description: |- @@ -171,7 +193,7 @@ paths: description: The token generated by the ``requestToken`` call and emailed to the user. x-example: atoken responses: - "200": + 200: description: Email address is validated. "3xx": description: |- @@ -181,3 +203,14 @@ paths: "4xx": description: Validation failed. + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" diff --git a/api/identity/v2_invitation_signing.yaml b/api/identity/v2_invitation_signing.yaml index c1267bdc..0431233a 100644 --- a/api/identity/v2_invitation_signing.yaml +++ b/api/identity/v2_invitation_signing.yaml @@ -99,3 +99,14 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" diff --git a/api/identity/v2_phone_associations.yaml b/api/identity/v2_phone_associations.yaml index cfaea410..6d4ad79b 100644 --- a/api/identity/v2_phone_associations.yaml +++ b/api/identity/v2_phone_associations.yaml @@ -76,6 +76,17 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" "/validate/msisdn/submitToken": post: summary: Validate ownership of a phone number. @@ -137,6 +148,17 @@ paths: type: boolean description: Whether the validation was successful or not. required: ['success'] + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" get: summary: Validate ownership of a phone number. description: |- @@ -173,7 +195,7 @@ paths: description: The token generated by the ``requestToken`` call and sent to the user. x-example: atoken responses: - "200": + 200: description: Phone number is validated. "3xx": description: |- @@ -183,3 +205,14 @@ paths: "4xx": description: Validation failed. + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" diff --git a/api/identity/v2_store_invite.yaml b/api/identity/v2_store_invite.yaml index afc41a1c..9b7a653c 100644 --- a/api/identity/v2_store_invite.yaml +++ b/api/identity/v2_store_invite.yaml @@ -163,3 +163,14 @@ paths: } schema: $ref: "../client-server/definitions/errors/error.yaml" + 403: + description: | + The user must do something in order to use this endpoint. One example + is an ``M_TERMS_NOT_SIGNED`` error where the user must `agree to more terms`_. + examples: + application/json: { + "errcode": "M_TERMS_NOT_SIGNED", + "error": "Please accept our updated terms of service before continuing" + } + schema: + $ref: "../client-server/definitions/errors/error.yaml" diff --git a/api/identity/v2_terms.yaml b/api/identity/v2_terms.yaml new file mode 100644 index 00000000..9b831fba --- /dev/null +++ b/api/identity/v2_terms.yaml @@ -0,0 +1,149 @@ +# Copyright 2019 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. +swagger: '2.0' +info: + title: "Matrix Identity Service Terms of Service API" + version: "2.0.0" +host: localhost:8090 +schemes: + - https +basePath: /_matrix/identity/v2 +consumes: + - application/json +produces: + - application/json +securityDefinitions: + $ref: definitions/security.yaml +paths: + "/terms": + get: + summary: Gets the terms of service offered by the server. + description: |- + Gets all the terms of service offered by the server. The client is expected + to filter through the terms to determine which terms need acceptance from the + user. Note that this endpoint does not require authentication. + operationId: getTerms + parameters: [] + responses: + 200: + description: |- + The terms of service offered by the server. + examples: + application/json: { + "policies": { + "terms_of_service": { + "version": "2.0", + "en": { + "name": "Terms of Service", + "url": "https://example.org/somewhere/terms-2.0-en.html" + }, + "fr": { + "name": "Conditions d'utilisation", + "url": "https://example.org/somewhere/terms-2.0-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" + } + } + } + } + schema: + type: object + properties: + policies: + type: object + title: Policy Map + description: |- + The policies the server offers. Mapped from arbitrary ID (unused in + this version of the specification) to a Policy Object. + additionalProperties: + type: object + title: Policy Object + description: |- + The policy. Includes a map of language (ISO 639-2) to language-specific + policy information. + properties: + version: + type: string + description: |- + The version for the policy. There are no requirements on what this + might be and could be "alpha", semantically versioned, or arbitrary. + required: ['version'] + # TODO: TravisR - Make this render + additionalProperties: + type: object + title: Internationalised Policy + description: |- + The policy information for the specified language. + properties: + name: + type: string + description: The translated name of the policy. + url: + type: string + description: |- + The URL, which should include the policy ID, version, and language + in it, to be presented to the user as the policy. URLs should have + all three criteria to avoid conflicts when the policy is updated + in the future: for example, if this was "https://example.org/terms.html" + then the server would be unable to update it because the client would + have already added that URL to the ``m.accepted_terms`` collection. + required: ['name', 'url'] + required: ['policies'] + post: + summary: Indicates acceptance of terms to the server. + description: |- + Called by a client to indicate that the user has accepted/agreed to the included + set of URLs. Servers MUST NOT assume that the client will be sending all previously + accepted URLs and should therefore append the provided URLs to what the server + already knows has been accepted. + + Clients MUST provide the URL of the policy in the language that was presented + to the user. Servers SHOULD consider acceptance of any one language's URL as + acceptance for all other languages of that policy. + + The server should avoid returning ``M_TERMS_NOT_SIGNED`` because the client + may not be accepting all terms at once. + operationId: agreeToTerms + security: + - accessToken: [] + parameters: + - in: body + name: body + schema: + type: object + properties: + user_accepts: + type: array + items: + type: string + description: The URLs the user is accepting in this request. + example: "https://example.org/somewhere/terms-2.0-en.html" + required: ['user_accepts'] + responses: + 200: + description: |- + The server has considered the user as having accepted the provided URLs. + examples: + application/json: {} + schema: + type: object diff --git a/changelogs/identity_service/newsfragments/2258.new b/changelogs/identity_service/newsfragments/2258.new new file mode 100644 index 00000000..06b9efff --- /dev/null +++ b/changelogs/identity_service/newsfragments/2258.new @@ -0,0 +1 @@ +Add endpoints for accepting and handling terms of service. diff --git a/event-schemas/examples/m.accepted_terms b/event-schemas/examples/m.accepted_terms new file mode 100644 index 00000000..5e8dad16 --- /dev/null +++ b/event-schemas/examples/m.accepted_terms @@ -0,0 +1,10 @@ +{ + "$ref": "core/event.json", + "type": "m.accepted_terms", + "content": { + "accepted": [ + "https://example.org/somewhere/terms-1.2-en.html", + "https://example.org/somewhere/privacy-1.2-en.html" + ] + } +} diff --git a/event-schemas/schema/m.accepted_terms b/event-schemas/schema/m.accepted_terms new file mode 100644 index 00000000..510e741d --- /dev/null +++ b/event-schemas/schema/m.accepted_terms @@ -0,0 +1,23 @@ +--- +allOf: + - $ref: core-event-schema/event.yaml +description: |- + A list of terms URLs the user has previously accepted. Clients SHOULD use this + to avoid presenting the user with terms they have already agreed to. +properties: + content: + type: object + properties: + accepted: + type: array + items: + type: string + description: |- + The list of URLs the user has previously accepted. Should be appended to + when the user agrees to new terms. + type: + enum: + - m.accepted_terms + type: string +title: Accepted Terms of Service URLs +type: object diff --git a/scripts/templating/matrix_templates/units.py b/scripts/templating/matrix_templates/units.py index 04e6f8a9..0c835508 100644 --- a/scripts/templating/matrix_templates/units.py +++ b/scripts/templating/matrix_templates/units.py @@ -586,7 +586,21 @@ class MatrixUnits(Units): raise Exception("Error handling parameter %s" % param_name, e) # endfor[param] good_response = None - for code in sorted(endpoint_swagger.get("responses", {}).keys()): + endpoint_status_codes = endpoint_swagger.get("responses", {}).keys() + # Check to see if any of the status codes are strings ("4xx") and if + # so convert everything to a string to avoid comparing ints and strings. + has_string_status = False + for code in endpoint_status_codes: + if isinstance(code, str): + has_string_status = True + break + if has_string_status: + endpoint_status_codes = [str(i) for i in endpoint_status_codes] + for code in sorted(endpoint_status_codes): + # Convert numeric responses to ints, assuming they got converted + # above. + if isinstance(code, str) and code.isdigit(): + code = int(code) res = endpoint_swagger["responses"][code] if not good_response and code == 200: good_response = res diff --git a/specification/identity_service_api.rst b/specification/identity_service_api.rst index 4afac6ef..c92f737b 100644 --- a/specification/identity_service_api.rst +++ b/specification/identity_service_api.rst @@ -198,6 +198,33 @@ status of 401 and the error code ``M_UNAUTHORIZED``. {{v2_auth_is_http_api}} + +.. _`agree to more terms`: + +Terms of service +---------------- + +Identity Servers are encouraged to have terms of service (or similar policies) to +ensure that users have agreed to their data being processed by the server. To facilitate +this, an identity server can respond to almost any authenticated API endpoint with a +HTTP 403 and the error code ``M_TERMS_NOT_SIGNED``. The error code is used to indicate +that the user must accept new terms of service before being able to continue. + +All endpoints which support authentication can return the ``M_TERMS_NOT_SIGNED`` error. +When clients receive the error, they are expected to make a call to ``GET /terms`` to +find out what terms the server offers. The client compares this to the ``m.accepted_terms`` +account data for the user (described later) and presents the user with option to accept +the still-missing terms of service. After the user has made their selection, if applicable, +the client sends a request to ``POST /terms`` to indicate the user's acceptance. The +server cannot expect that the client will send acceptance for all pending terms, and the +client should not expect that the server will not respond with another ``M_TERMS_NOT_SIGNED`` +on their next request. The terms the user has just accepted are appended to ``m.accepted_terms``. + +{{m_accepted_terms_event}} + +{{v2_terms_is_http_api}} + + Status check ------------