From 88b35d1be5e016958878af6890286023b31aa272 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 9 Aug 2018 08:30:50 -0600 Subject: [PATCH 1/6] Clarify how third party invites work This adds several diagrams to the Client-Server API about how invites are handled, including what the server is expected to do. This helps implementors know what they are supposed to do in the common cases, and infer where needed to get the more complex cases correct. Although lacking in some areas, this is how third party invites work today. A link to the now-improved client-server documentation for third party invites has been added to the server-server specification. The existing server-server specification needed no further changes on the subject. Fixes https://github.com/matrix-org/matrix-doc/issues/1366 --- api/identity/pubkey.yaml | 2 +- specification/modules/third_party_invites.rst | 205 +++++++++++++----- specification/server_server_api.rst | 4 + 3 files changed, 158 insertions(+), 53 deletions(-) diff --git a/api/identity/pubkey.yaml b/api/identity/pubkey.yaml index 00796975..5ea6341f 100644 --- a/api/identity/pubkey.yaml +++ b/api/identity/pubkey.yaml @@ -56,7 +56,7 @@ paths: get: summary: Check whether a long-term public key is valid. description: |- - Check whether a long-term public key is valid. + Check whether a long-term public key is valid. The request must be idempotent. operationId: isPubKeyValid parameters: - in: query diff --git a/specification/modules/third_party_invites.rst b/specification/modules/third_party_invites.rst index 9ea0eb0b..a9b06258 100644 --- a/specification/modules/third_party_invites.rst +++ b/specification/modules/third_party_invites.rst @@ -40,6 +40,7 @@ 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 ------ @@ -55,41 +56,79 @@ A client asks a server to invite a user by their third party identifier. Server behaviour ---------------- -All homeservers MUST verify the signature in the event's -``content.third_party_invite.signed`` object. - -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 +Upon receipt of an ``/invite``, the server is expected to look up the third party +identifier with the provided identity server. If the lookup yields a result for +a Matrix User ID then the normal invite process can be initiated. This process +ends up looking like this: + +:: + + +---------+ +-------------+ +-----------------+ + | Client | | Homeserver | | IdentityServer | + +---------+ +-------------+ +-----------------+ + | | | + | POST /invite | | + |------------------------------------>| | + | | | + | | GET /lookup | + | |--------------------------------------------------->| + | | | + | | User ID result | + | |<---------------------------------------------------| + | | | + | | Invite process for the discovered User ID | + | |------------------------------------------ | + | | | | + | |<----------------------------------------- | + | | | + | Complete the /invite request | | + |<------------------------------------| | + | | | + + +However, if the lookup does not yield a bound User ID, the homeserver must store +the invite on the identity server and emit a valid ``m.room.third_party_invite`` +event to the room. This process ends up looking like this: + +:: + + +---------+ +-------------+ +-----------------+ + | Client | | Homeserver | | IdentityServer | + +---------+ +-------------+ +-----------------+ + | | | + | POST /invite | | + |------------------------------------>| | + | | | + | | GET /lookup | + | |-------------------------------------------------------------->| + | | | + | | "no users" result | + | |<--------------------------------------------------------------| + | | | + | | POST /store-invite | + | |-------------------------------------------------------------->| + | | | + | | Information needed for the m.room.third_party_invite | + | |<--------------------------------------------------------------| + | | | + | | Emit m.room.third_party_invite to the room | + | |------------------------------------------- | + | | | | + | |<------------------------------------------ | + | | | + | Complete the /invite request | | + |<------------------------------------| | + | | | -Schema:: - => GET $key_validity_url?public_key=$public_key - <= HTTP/1.1 200 OK - { - "valid": true|false - } - - -Example:: - - key_validity_url = https://identity.server/is_valid - public_key = ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4 - => GET https://identity.server/is_valid?public_key=ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4 - <= HTTP/1.1 200 OK - { - "valid": true - } +All homeservers MUST verify the signature in the event's +``content.third_party_invite.signed`` object. -with the querystring -?public_key=``public_key``. A JSON object will be returned. -The invitation is valid if the object contains a key named ``valid`` which is -``true``. Otherwise, the invitation MUST be rejected. This request is -idempotent and may be retried by the homeserver. +The third party user will then need to verify their identity, which results in +a call from the Identity Server to the homeserver that bound the third party +identifier to a user. The homeserver then exchanges the ``m.room.third_party_invite`` +event in the room for a complete ``m.room.member`` event for ``membership: invite`` +for the user that has bound the third party identifier. If a homeserver is joining a room for the first time because of an ``m.room.third_party_invite``, the server which is already participating in the @@ -102,26 +141,85 @@ No other homeservers may reject the joining of the room on the basis of the room. They may, however, indicate to their clients that a member's' membership is questionable. -For example: - -#. Room R has two participating homeservers, H1, H2 - -#. 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. - -#. 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 the signature given to it by the identity server. - -#. 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 invite event to the room, and H3 - begins participating in the room. H2 *must* accept this event. +For example, given H1, H2, and H3 as homeservers, UserA as a user of H1, and an +identity server IS, the full sequence for a third party invite would look like +the following. This diagram assumes H1 and H2 are residents of the room while +H3 is attempting to join. + +:: + + +-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+ + | UserA | | ThirdPartyUser | | H1 | | H2 | | H3 | | IS | + +-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+ + | | | | | | + | POST /invite for ThirdPartyUser | | | | + |----------------------------------->| | | | + | | | | | | + | | | GET /lookup | | | + | | |---------------------------------------------------------------------------------------------->| + | | | | | | + | | | | Lookup results (empty object) | + | | |<----------------------------------------------------------------------------------------------| + | | | | | | + | | | POST /store-invite | | | + | | |---------------------------------------------------------------------------------------------->| + | | | | | | + | | | | Token, keys, etc for third party invite | + | | |<----------------------------------------------------------------------------------------------| + | | | | | | + | | | (Federation) Emit m.room.third_party_invite | | | + | | |----------------------------------------------->| | | + | | | | | | + | Complete /invite request | | | | + |<-----------------------------------| | | | + | | | | | | + | | Verify identity | | | | + | |-------------------------------------------------------------------------------------------------------------------->| + | | | | | | + | | | | | POST /3pid/onbind | + | | | | |<---------------------------| + | | | | | | + | | | PUT /exchange_third_party_invite/:roomId | | + | | |<-----------------------------------------------------------------| | + | | | | | | + | | | Verify the request | | | + | | |------------------- | | | + | | | | | | | + | | |<------------------ | | | + | | | | | | + | | | (Federation) Emit m.room.member for invite | | | + | | |----------------------------------------------->| | | + | | | | | | + | | | | Accept event | | + | | | |------------- | | + | | | | | | | + | | | |<------------ | | + | | | | | | + | | | (Federation) Emit the m.room.member event sent to H2 | | + | | |----------------------------------------------------------------->| | + | | | | | | + | | | Complete /exchange_third_party_invite/:roomId request | | + | | |----------------------------------------------------------------->| | + | | | | | | + | | | | | Participate in the room | + | | | | |------------------------ | + | | | | | | | + | | | | |<----------------------- | + | | | | | | + + +Note that when H1 sends the ``m.room.member`` event to H2 and H3 it does not +have to block on either server's receipt of the event. Likewise, H1 may complete +the ``/exchange_third_party_invite/:roomId`` request at the same time as sending +the ``m.room.member`` event to H2 and H3. Additionally, H3 may complete the +``/3pid/onbind`` request it got from IS at any time - the completion is not shown +in the diagram. + +H1 MUST verify the request from H3 to ensure the ``signed`` property is correct +as well as the ``key_validity_url`` as still being valid. This is done by making +a request to the `Identity Server /isvalid`_ endpoint, using the provided URL +rather than constructing a new one. The query string and response for the provided +URL must match the Identity Server specification. 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. @@ -158,3 +256,6 @@ 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. + + +.. _`Identity Server /isvalid`: ../identity_service/unstable.html#get-matrix-identity-api-v1-pubkey-isvalid diff --git a/specification/server_server_api.rst b/specification/server_server_api.rst index 472aca12..b8df0b47 100644 --- a/specification/server_server_api.rst +++ b/specification/server_server_api.rst @@ -734,6 +734,10 @@ event to other servers in the room. Third-party invites ------------------- +.. NOTE:: + More information about third party invites is available in the `Client-Server API`_ + under the Third Party Invites module. + 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). From a556e33eb9d606efeea321466678ba1ed2c3c379 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 Aug 2018 16:59:36 -0600 Subject: [PATCH 2/6] Spec /3pid/onbind Fixes https://github.com/matrix-org/matrix-doc/issues/1422 --- api/server-server/third_party_invite.yaml | 123 ++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/api/server-server/third_party_invite.yaml b/api/server-server/third_party_invite.yaml index 754a3282..2225bf1a 100644 --- a/api/server-server/third_party_invite.yaml +++ b/api/server-server/third_party_invite.yaml @@ -190,3 +190,126 @@ paths: type: object description: An empty object example: {} + "/3pid/onbind": + put: + summary: |- + Notifies the server that a third party identifier has been bound to one + of its users. + description: |- + Used by Identity Servers to notify the homeserver that one of its users + has bound a third party identifier successfully, including any pending + room invites the Identity Server has been made aware of. + operationId: onBindThirdPartyIdentifier + parameters: + - in: body + name: body + type: object + required: true + schema: + type: object + properties: + medium: + type: string + description: |- + The type of third party identifier. Currently only "email" is + a possible value. + example: "email" + address: + type: string + description: |- + The third party identifier itself. For example, an email address. + example: "alice@domain.com" + mxid: + type: string + description: The user that is now bound to the third party identifier. + example: "@alice:matrix.org" + invites: + type: array + description: |- + A list of pending invites that the third party identifier has received. + items: + type: object + title: Third Party Invite + properties: + medium: + type: string + description: |- + The type of third party invite issues. Currently only + "email" is used. + example: "email" + address: + type: string + description: |- + The third party identifier that received the invite. + example: "alice@domain.com" + mxid: + type: string + description: The now-bound user ID that received the invite. + example: "@alice:matrix.org" + room_id: + type: string + description: The room ID the invite is valid for. + example: "!somewhere:example.org" + sender: + type: string + description: The user ID that sent the invite. + example: "@bob:matrix.org" + # TODO (TravisR): Make this reusable when doing IS spec changes + # also make sure it isn't lying about anything, like the key version + signed: + type: object + title: Identity Server Signatures + description: |- + Signature from the Identity Server using a long-term private + key. + properties: + mxid: + type: string + description: |- + The user ID that has been bound to the third party + identifier. + example: "@alice:matrix.org" + token: + type: string + # TODO: What is this actually? + description: A token. + example: "Hello World" + signatures: + type: object + title: Identity Server Signature + description: |- + The signature from the identity server. The ``string`` key + is the identity server's domain name, such as vector.im + additionalProperties: + type: object + title: Identity Server Domain Signature + description: The signature for the identity server. + properties: + "ed25519:0": + type: string + description: The signature. + example: "SomeSignatureGoesHere" + required: ['ed25519:0'] + example: { + "vector.im": { + "ed25519:0": "SomeSignatureGoesHere" + } + } + required: ['mxid', 'token', 'signatures'] + required: + - medium + - address + - mxid + - room_id + - sender + - signed + required: ['medium', 'address', 'mxid', 'invites'] + responses: + 200: + description: The homeserver has processed the notification. + examples: + application/json: {} + schema: + type: object + description: An empty object + example: {} From 3de50cbc7f9b9187b426108f810eab9516586560 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 17 Aug 2018 09:49:09 -0600 Subject: [PATCH 3/6] Clarify how /isvalid is meant to always be truthful --- api/identity/pubkey.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/identity/pubkey.yaml b/api/identity/pubkey.yaml index 5ea6341f..1cd7b94c 100644 --- a/api/identity/pubkey.yaml +++ b/api/identity/pubkey.yaml @@ -56,7 +56,8 @@ paths: get: summary: Check whether a long-term public key is valid. description: |- - Check whether a long-term public key is valid. The request must be idempotent. + Check whether a long-term public key is valid. The response should always + be the same, provided the key exists. operationId: isPubKeyValid parameters: - in: query From 5e6a2c30a2171366049fbde23c0b55711b2b1b79 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 30 Aug 2018 15:00:14 -0600 Subject: [PATCH 4/6] Comment out the part where identity services can revoke their keys They can't because otherwise 3rd party invites can be rejected by homeservers, as per https://github.com/matrix-org/matrix-doc/issues/1633 --- specification/identity_service_api.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/specification/identity_service_api.rst b/specification/identity_service_api.rst index cb079593..d438f2c7 100644 --- a/specification/identity_service_api.rst +++ b/specification/identity_service_api.rst @@ -80,9 +80,11 @@ 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. +.. TODO: Actually allow identity services to revoke all keys + See: https://github.com/matrix-org/matrix-doc/issues/1633 +.. 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 From 356626845ccadabdb5d9239eb80e010c505bde40 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 30 Aug 2018 15:00:52 -0600 Subject: [PATCH 5/6] Remove unhelpful arrow from the 3rd party invite sequence dance This doesn't add anything in terms of clarity. --- specification/modules/third_party_invites.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/specification/modules/third_party_invites.rst b/specification/modules/third_party_invites.rst index a9b06258..0af81844 100644 --- a/specification/modules/third_party_invites.rst +++ b/specification/modules/third_party_invites.rst @@ -190,10 +190,6 @@ H3 is attempting to join. | | | (Federation) Emit m.room.member for invite | | | | | |----------------------------------------------->| | | | | | | | | - | | | | Accept event | | - | | | |------------- | | - | | | | | | | - | | | |<------------ | | | | | | | | | | | (Federation) Emit the m.room.member event sent to H2 | | | | |----------------------------------------------------------------->| | From 1f6499d563ab26af66f80a0d946cc0c07aa87176 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 30 Aug 2018 15:01:20 -0600 Subject: [PATCH 6/6] Fix typo --- specification/modules/third_party_invites.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/modules/third_party_invites.rst b/specification/modules/third_party_invites.rst index 0af81844..248b9ba9 100644 --- a/specification/modules/third_party_invites.rst +++ b/specification/modules/third_party_invites.rst @@ -138,7 +138,7 @@ validate that the public key used for signing is still valid, by checking No other homeservers may reject the joining of the room on the basis of ``key_validity_url``, this is so that all homeservers have a consistent view of -the room. They may, however, indicate to their clients that a member's' +the room. They may, however, indicate to their clients that a member's membership is questionable. For example, given H1, H2, and H3 as homeservers, UserA as a user of H1, and an