18 KiB
第三方邀请
本模块为邀请新成员加入房间提供了支持,即使对方的 Matrix 用户 ID 未知,也可以通过第三方标识符(如电子邮件地址)进行邀请。此处包含两种流程:一种是已知该第三方标识符对应的 Matrix 用户 ID,另一种是未知。无论哪种情况,客户端都通过 /invite
接口并提供第三方标识符的详细信息来发起邀请。
主服务器会向身份服务器查询该标识符是否对应已知的 Matrix 用户 ID:
- 如果已知,则直接向该用户发出邀请。
- 如果未知,主服务器会请求身份服务器记录该邀请的详细信息,并在未来为该标识符分配绑定时通知被邀请者的主服务器有待处理的邀请。身份服务器会向发起邀请的主服务器返回一个令牌和公钥。
当被邀请者的主服务器收到绑定通知时,应在房间的事件图中插入一个 m.room.member
事件,其中 content.membership
为 invite
,同时包含一个 content.third_party_invite
属性,以证明被邀请者确实拥有该第三方标识符。更多信息请参见 m.room.member 事件架构说明。
事件
{{% event event="m.room.third_party_invite" %}}
客户端行为
客户端通过第三方标识符请求服务器邀请用户。
{{% http-api spec="client-server" api="third_party_membership" anchor_base="thirdparty" %}}
服务器行为
在收到 /invite
请求后,服务器需要使用指定的身份服务器查询第三方标识符。如果查询结果返回了 Matrix 用户 ID,则可以启动标准的邀请流程。该流程如下所示:
+---------+ +-------------+ +-----------------+
| 客户端 | | 主服务器 | | 身份服务器 |
+---------+ +-------------+ +-----------------+
| | |
| POST /invite | |
|----------------------------------->| |
| | |
| | GET /lookup |
| |--------------------------------------------------->|
| | |
| | 用户 ID 结果 |
| |<---------------------------------------------------|
| | |
| | 对发现的用户 ID 启动邀请流程 |
| |------------------------------------------ |
| | | |
| |<----------------------------------------- |
| | |
| 完成 /invite 请求 | |
|<-----------------------------------| |
| | |
然而,如果查询结果没有返回已绑定的用户 ID,主服务器必须将邀请信息存储在身份服务器上,并向房间发出有效的 m.room.third_party_invite
事件。该流程如下所示:
+---------+ +-------------+ +-----------------+
| 客户端 | | 主服务器 | | 身份服务器 |
+---------+ +-------------+ +-----------------+
| | |
| POST /invite | |
|----------------------------------->| |
| | |
| | GET /lookup |
| |------------------------------------------------------------>|
| | |
| | “无用户” 结果 |
| |<------------------------------------------------------------|
| | |
| | POST /store-invite |
| |------------------------------------------------------------>|
| | |
| | m.room.third_party_invite 事件所需信息 |
| |<------------------------------------------------------------|
| | |
| | 向房间发出 m.room.third_party_invite 事件 |
| |------------------------------------------- |
| | | |
| |<------------------------------------------ |
| | |
| 完成 /invite 请求 | |
|<-----------------------------------| |
| | |
所有主服务器必须验证事件中 content.third_party_invite.signed
对象的签名。
第三方用户随后需要验证其身份,这将使身份服务器调用主服务器,将该第三方标识符绑定到用户的主服务器。此时,主服务器会将房间中的 m.room.third_party_invite
事件替换为该用户的完整 membership: invite
的 m.room.member
事件。
如果主服务器是通过 m.room.third_party_invite
首次加入某个房间,则已在该房间中的服务器(根据标准服务器间协议选定)必须校验用于签名的公钥仍然有效,方法即如上文所述检查 key_validity_url
。
其他主服务器不得仅因 key_validity_url
拒绝房间加入请求,这是为了确保所有主服务器都能获得一致的房间视图。但主服务器可以向其客户端指出成员的成员资格可能存在疑问。
例如,假设 H1、H2、H3 为主服务器,UserA 是 H1 的用户,身份服务器为 IS,第三方邀请的完整流程如下图所示。图中假定 H1、H2 已在房间,H3 尝试加入。
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
| UserA | | 第三方用户 | | H1 | | H2 | | H3 | | IS |
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
| | | | | |
| POST /invite 针对第三方用户 | | | |
|--------------------------------->| | | |
| | | | | |
| | | GET /lookup | | |
| | |---------------------------------------------------------------------------------------------->|
| | | | | |
| | | | 查询结果(空对象) |
| | |<----------------------------------------------------------------------------------------------|
| | | | | |
| | | POST /store-invite | | |
| | |---------------------------------------------------------------------------------------------->|
| | | | | |
| | | | 第三方邀请生成的令牌、公钥等 |
| | |<----------------------------------------------------------------------------------------------|
| | | | | |
| | | (联邦)向房间发出 m.room.third_party_invite | | |
| | |----------------------------------------------->| | |
| | | | | |
| 完成 /invite 请求 | | | |
|<---------------------------------| | | |
| | | | | |
| | 验证身份 | | | |
| |-------------------------------------------------------------------------------------------------------------------->|
| | | | | |
| | | | | POST /3pid/onbind |
| | | | |<---------------------------|
| | | | | |
| | | PUT /exchange_third_party_invite/:roomId | |
| | |<-----------------------------------------------------------------| |
| | | | | |
| | | 验证该请求 | | |
| | |------------------- | | |
| | | | | | |
| | |<------------------ | | |
| | | | | |
| | | (联邦)发出 m.room.member 邀请 | | |
| | |----------------------------------------------->| | |
| | | | | |
| | | | | |
| | | (联邦)将 m.room.member 事件发送给 H2 | |
| | |----------------------------------------------------------------->| |
| | | | | |
| | | 完成 /exchange_third_party_invite/:roomId 请求 | |
| | |----------------------------------------------------------------->| |
| | | | | |
| | | | | 加入房间 |
| | | | |------------------------ |
| | | | | | |
| | | | |<----------------------- |
| | | | | |
注意当 H1 向 H2 和 H3 发送 m.room.member
事件时,H1 不需要等待任何服务器确认收到该事件。同样,H1 可以在向 H2、H3 发送 m.room.member
事件的同时完成 /exchange_third_party_invite
请求。另外,H3 可以在任意时刻完成其从 IS 接收到的 /3pid/onbind
请求——此流程中的完成未在图中展示。
H1 必须校验来自 H3 的请求,确保 signed
属性正确,且 key_validity_url
仍有效。此操作需向 身份服务器 /isvalid 端点发起请求,且应使用已提供的 URL 而非自行构造。查询字符串和返回值必须符合身份服务规范。
不允许其他主服务器仅基于校验 key_validity_url
的结果拒绝事件,因为我们必须保证事件的接受方式具备确定性。如果某些参与服务器无法连接到密钥服务器、密钥服务器宕机或吊销密钥,则其他服务器会拒绝该事件,导致参与服务器的事件图发生分歧。此行为依赖参与服务器间的信任,但该信任已通过服务器间协议隐含。此外,还必须完成公钥签名验证,从而最大限度减少攻击面。
安全性考虑
本模块涉及若干隐私和信任问题。
为保护用户隐私,必须强力防止 Matrix 用户 ID 与第三方标识符之间的映射泄露。尤其是,应尽量阻止通过 Matrix 用户 ID 查找所有第三方标识符(从而能够关联各第三方标识符)。为此,任何事件都不会直接包含第三方标识符,而是采用由身份服务器提供的不透明显示名称。客户端不应因邀请而记住或显示第三方标识符,仅限于邀请方自身的使用。
主服务器不需要信任特定的身份服务器。一般而言,由客户端自行决定信任哪些身份服务器,而非主服务器决定。因此,此 API 从最终用户处获取身份服务器,并未指定受信任服务器集。当然,一些主服务器可能会为其用户提供默认配置或拒绝个别身份服务器,但主服务器无权对其他主服务器的用户信任哪些身份服务器进行干预。
身份服务器或主服务器可能因收到大量请求或存储大量状态而面临拒绝服务攻击风险。如何防御此类风险由实现方自行决定。