|
|
|
.. Copyright 2016 OpenMarket Ltd
|
|
|
|
..
|
|
|
|
.. 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.
|
|
|
|
|
|
|
|
Third party invites
|
|
|
|
===================
|
|
|
|
|
|
|
|
.. _module:third-party-invites:
|
|
|
|
|
|
|
|
This module adds in support for inviting new members to a room where their
|
|
|
|
Matrix user ID is not known, instead addressing them by a third party identifier
|
|
|
|
such as an email address.
|
|
|
|
There are two flows here; one if a Matrix user ID is known for the third party
|
|
|
|
identifier, and one if not. Either way, the client calls ``/invite`` with the
|
|
|
|
details of the third party identifier.
|
|
|
|
|
|
|
|
The homeserver asks the identity server whether a Matrix user ID is known for
|
|
|
|
that identifier:
|
|
|
|
|
|
|
|
- If it is, an invite is simply issued for that user.
|
|
|
|
|
|
|
|
- If it is not, the homeserver asks the identity server to record the details of
|
|
|
|
the invitation, and to notify the invitee's homeserver of this pending invitation if it gets
|
|
|
|
a binding for this identifier in the future. The identity server returns a token
|
|
|
|
and public key to the inviting homeserver.
|
|
|
|
|
|
|
|
When the invitee's homeserver receives the notification of the binding, it
|
|
|
|
should insert an ``m.room.member`` event into the room's graph for that user,
|
|
|
|
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
|
|
|
|
------
|
|
|
|
|
|
|
|
{{m_room_third_party_invite_event}}
|
|
|
|
|
|
|
|
Client behaviour
|
|
|
|
----------------
|
|
|
|
|
|
|
|
A client asks a server to invite a user by their third party identifier.
|
|
|
|
|
|
|
|
{{third_party_membership_cs_http_api}}
|
|
|
|
|
|
|
|
Server behaviour
|
|
|
|
----------------
|
|
|
|
|
|
|
|
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 | |
|
|
|
|
|<------------------------------------| |
|
|
|
|
| | |
|
|
|
|
|
|
|
|
|
|
|
|
All homeservers MUST verify the signature in the event's
|
|
|
|
``content.third_party_invite.signed`` object.
|
|
|
|
|
|
|
|
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
|
|
|
|
room (which is chosen as per the standard server-server specification) MUST
|
|
|
|
validate that the public key used for signing is still valid, by checking
|
|
|
|
``key_validity_url`` in the above described way.
|
|
|
|
|
|
|
|
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
|
|
|
|
membership is questionable.
|
|
|
|
|
|
|
|
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 | | |
|
|
|
|
| | |----------------------------------------------->| | |
|
|
|
|
| | | | | |
|
|
|
|
| | | | | |
|
|
|
|
| | | (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 Service 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.
|
|
|
|
If some other participating server doesn't have a network path to the keyserver,
|
|
|
|
or if the keyserver were to go offline, or revoke its keys, that other server
|
|
|
|
would reject the event and cause the participating servers' graphs to diverge.
|
|
|
|
This relies on participating servers trusting each other, but that trust is
|
|
|
|
already implied by the server-server protocol. Also, the public key signature
|
|
|
|
verification must still be performed, so the attack surface here is minimized.
|
|
|
|
|
|
|
|
Security considerations
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
There are a number of privacy and trust implications to this module.
|
|
|
|
|
|
|
|
It is important for user privacy that leaking the mapping between a matrix user
|
|
|
|
ID and a third party identifier is hard. In particular, being able to look up
|
|
|
|
all third party identifiers from a matrix user ID (and accordingly, being able
|
|
|
|
to link each third party identifier) should be avoided wherever possible.
|
|
|
|
To this end, the third party identifier is not put in any event, rather an
|
|
|
|
opaque display name provided by the identity server is put into the events.
|
|
|
|
Clients should not remember or display third party identifiers from invites,
|
|
|
|
other than for the use of the inviter themself.
|
|
|
|
|
|
|
|
Homeservers are not required to trust any particular identity server(s). It is
|
|
|
|
generally a client's responsibility to decide which identity servers it trusts,
|
|
|
|
not a homeserver's. Accordingly, this API takes identity servers as input from
|
|
|
|
end users, and doesn't have any specific trusted set. It is possible some
|
|
|
|
homeservers may want to supply defaults, or reject some identity servers for
|
|
|
|
*its* users, but no homeserver is allowed to dictate which identity servers
|
|
|
|
*other* homeservers' users trust.
|
|
|
|
|
|
|
|
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
|