Merge remote-tracking branch 'matrix-org/master' into travis/c2s/read-markers
This commit is contained in:
commit
679ddabb53
45 changed files with 1685 additions and 361 deletions
|
@ -50,7 +50,9 @@ paths:
|
||||||
"threepids": [
|
"threepids": [
|
||||||
{
|
{
|
||||||
"medium": "email",
|
"medium": "email",
|
||||||
"address": "monkey@banana.island"
|
"address": "monkey@banana.island",
|
||||||
|
"validated_at": 1535176800000,
|
||||||
|
"added_at": 1535336848756
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -70,6 +72,19 @@ paths:
|
||||||
address:
|
address:
|
||||||
type: string
|
type: string
|
||||||
description: The third party identifier address.
|
description: The third party identifier address.
|
||||||
|
validated_at:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: |-
|
||||||
|
The timestamp, in milliseconds, when the identifier was
|
||||||
|
validated by the identity service.
|
||||||
|
added_at:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description:
|
||||||
|
The timestamp, in milliseconds, when the homeserver
|
||||||
|
associated the third party identifier with the user.
|
||||||
|
required: ['medium', 'address', 'validated_at', 'added_at']
|
||||||
tags:
|
tags:
|
||||||
- User data
|
- User data
|
||||||
post:
|
post:
|
||||||
|
@ -133,6 +148,41 @@ paths:
|
||||||
"$ref": "definitions/errors/error.yaml"
|
"$ref": "definitions/errors/error.yaml"
|
||||||
tags:
|
tags:
|
||||||
- User data
|
- User data
|
||||||
|
"/account/3pid/delete":
|
||||||
|
post:
|
||||||
|
summary: Deletes a third party identifier from the user's account
|
||||||
|
description: |-
|
||||||
|
Removes a third party identifier from the user's account. This might not
|
||||||
|
cause an unbind of the identifier from the identity service.
|
||||||
|
operationId: delete3pidFromAccount
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
medium:
|
||||||
|
type: string
|
||||||
|
description: The medium of the third party identifier being removed.
|
||||||
|
enum: ["email", "msisdn"]
|
||||||
|
example: "email"
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
description: The third party address being removed.
|
||||||
|
example: "example@domain.com"
|
||||||
|
required: ['medium', 'address']
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: |-
|
||||||
|
The homeserver has disassociated the third party identifier from the
|
||||||
|
user.
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
tags:
|
||||||
|
- User data
|
||||||
"/account/3pid/email/requestToken":
|
"/account/3pid/email/requestToken":
|
||||||
post:
|
post:
|
||||||
summary: Requests a validation token be sent to the given email address for the purpose of adding an email address to an account
|
summary: Requests a validation token be sent to the given email address for the purpose of adding an email address to an account
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Copyright 2016 OpenMarket Ltd
|
# Copyright 2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -13,10 +14,10 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
properties:
|
properties:
|
||||||
events:
|
events:
|
||||||
description: List of events
|
description: List of events.
|
||||||
items:
|
items:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: event.yaml
|
- $ref: event-schemas/schema/core-event-schema/event.yaml
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
|
27
api/client-server/definitions/room_event_batch.yaml
Normal file
27
api/client-server/definitions/room_event_batch.yaml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright 2018 New Vector 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.
|
||||||
|
properties:
|
||||||
|
events:
|
||||||
|
description: List of events.
|
||||||
|
items:
|
||||||
|
allOf:
|
||||||
|
- $ref: event-schemas/schema/core-event-schema/sync_room_event.yaml
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- event_id
|
||||||
|
#- room_id - Not in /sync
|
||||||
|
- sender
|
||||||
|
- origin_server_ts
|
||||||
|
type: array
|
||||||
|
type: object
|
28
api/client-server/definitions/state_event_batch.yaml
Normal file
28
api/client-server/definitions/state_event_batch.yaml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Copyright 2018 New Vector 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.
|
||||||
|
properties:
|
||||||
|
events:
|
||||||
|
description: List of events.
|
||||||
|
items:
|
||||||
|
allOf:
|
||||||
|
- $ref: event-schemas/schema/core-event-schema/sync_state_event.yaml
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- event_id
|
||||||
|
#- room_id - Not in /sync
|
||||||
|
- sender
|
||||||
|
- origin_server_ts
|
||||||
|
- state_key
|
||||||
|
type: array
|
||||||
|
type: object
|
|
@ -1,4 +1,5 @@
|
||||||
# Copyright 2016 OpenMarket Ltd
|
# Copyright 2016 OpenMarket Ltd
|
||||||
|
# Copyright 2018 New Vector Ltd
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,14 +13,14 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: event_batch.yaml
|
- $ref: room_event_batch.yaml
|
||||||
properties:
|
properties:
|
||||||
limited:
|
limited:
|
||||||
description: True if the number of events returned was limited by the ``limit``
|
description: True if the number of events returned was limited by the ``limit``
|
||||||
on the filter
|
on the filter.
|
||||||
type: boolean
|
type: boolean
|
||||||
prev_batch:
|
prev_batch:
|
||||||
description: A token that can be supplied to the ``from`` parameter of the
|
description: A token that can be supplied to the ``from`` parameter of the
|
||||||
rooms/{roomId}/messages endpoint
|
rooms/{roomId}/messages endpoint.
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -117,6 +117,13 @@ paths:
|
||||||
A display name to assign to the newly-created device. Ignored
|
A display name to assign to the newly-created device. Ignored
|
||||||
if ``device_id`` corresponds to a known device.
|
if ``device_id`` corresponds to a known device.
|
||||||
example: Jungle Phone
|
example: Jungle Phone
|
||||||
|
inhibit_login:
|
||||||
|
type: boolean
|
||||||
|
description: |-
|
||||||
|
If true, an ``access_token`` and ``device_id`` should not be
|
||||||
|
returned from this call, therefore preventing an automatic
|
||||||
|
login. Defaults to false.
|
||||||
|
example: false
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: The account has been registered.
|
description: The account has been registered.
|
||||||
|
@ -141,6 +148,7 @@ paths:
|
||||||
description: |-
|
description: |-
|
||||||
An access token for the account.
|
An access token for the account.
|
||||||
This access token can then be used to authorize other requests.
|
This access token can then be used to authorize other requests.
|
||||||
|
Required if the ``inhibit_login`` option is false.
|
||||||
home_server:
|
home_server:
|
||||||
type: string
|
type: string
|
||||||
description: |-
|
description: |-
|
||||||
|
@ -155,6 +163,8 @@ paths:
|
||||||
description: |-
|
description: |-
|
||||||
ID of the registered device. Will be the same as the
|
ID of the registered device. Will be the same as the
|
||||||
corresponding parameter in the request, if one was specified.
|
corresponding parameter in the request, if one was specified.
|
||||||
|
Required if the ``inhibit_login`` option is false.
|
||||||
|
required: ['user_id']
|
||||||
400:
|
400:
|
||||||
description: |-
|
description: |-
|
||||||
Part of the request was invalid. This may include one of the following error codes:
|
Part of the request was invalid. This may include one of the following error codes:
|
||||||
|
|
|
@ -135,7 +135,7 @@ paths:
|
||||||
``timeline``, if ``since`` is not given, or
|
``timeline``, if ``since`` is not given, or
|
||||||
``full_state`` is true).
|
``full_state`` is true).
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "definitions/event_batch.yaml"
|
- $ref: "definitions/state_event_batch.yaml"
|
||||||
timeline:
|
timeline:
|
||||||
title: Timeline
|
title: Timeline
|
||||||
type: object
|
type: object
|
||||||
|
@ -202,8 +202,35 @@ paths:
|
||||||
the room then the current state will be given as a
|
the room then the current state will be given as a
|
||||||
delta against the archived ``state`` not the
|
delta against the archived ``state`` not the
|
||||||
``invite_state``.
|
``invite_state``.
|
||||||
allOf:
|
properties:
|
||||||
- $ref: "definitions/event_batch.yaml"
|
events:
|
||||||
|
description: The StrippedState events that form the invite state.
|
||||||
|
items:
|
||||||
|
description: |-
|
||||||
|
A stripped down state event, with only the ``type``, ``state_key``,
|
||||||
|
``sender``, and ``content`` keys.
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
description: The ``content`` for the event.
|
||||||
|
title: EventContent
|
||||||
|
type: object
|
||||||
|
state_key:
|
||||||
|
description: The ``state_key`` for the event.
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The ``type`` for the event.
|
||||||
|
type: string
|
||||||
|
sender:
|
||||||
|
description: The ``sender`` for the event.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- state_key
|
||||||
|
- content
|
||||||
|
- sender
|
||||||
|
title: StrippedState
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
leave:
|
leave:
|
||||||
title: Left rooms
|
title: Left rooms
|
||||||
type: object
|
type: object
|
||||||
|
@ -219,7 +246,7 @@ paths:
|
||||||
description: |-
|
description: |-
|
||||||
The state updates for the room up to the start of the timeline.
|
The state updates for the room up to the start of the timeline.
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "definitions/event_batch.yaml"
|
- $ref: "definitions/state_event_batch.yaml"
|
||||||
timeline:
|
timeline:
|
||||||
title: Timeline
|
title: Timeline
|
||||||
type: object
|
type: object
|
||||||
|
@ -270,6 +297,8 @@ paths:
|
||||||
description: |-
|
description: |-
|
||||||
Information on end-to-end encryption keys, as specified
|
Information on end-to-end encryption keys, as specified
|
||||||
in |device_lists_sync|_.
|
in |device_lists_sync|_.
|
||||||
|
required:
|
||||||
|
- next_batch
|
||||||
examples:
|
examples:
|
||||||
application/json: {
|
application/json: {
|
||||||
"next_batch": "s72595_4483_1934",
|
"next_batch": "s72595_4483_1934",
|
||||||
|
@ -321,7 +350,6 @@ paths:
|
||||||
{
|
{
|
||||||
"sender": "@alice:example.com",
|
"sender": "@alice:example.com",
|
||||||
"type": "m.room.message",
|
"type": "m.room.message",
|
||||||
"age": 124524,
|
|
||||||
"txn_id": "1234",
|
"txn_id": "1234",
|
||||||
"content": {
|
"content": {
|
||||||
"body": "I am a fish",
|
"body": "I am a fish",
|
||||||
|
|
|
@ -43,14 +43,14 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The id of the user to get tags for. The access token must be
|
The id of the user to get tags for. The access token must be
|
||||||
authorized to make requests for this user id.
|
authorized to make requests for this user ID.
|
||||||
x-example: "@alice:example.com"
|
x-example: "@alice:example.com"
|
||||||
- in: path
|
- in: path
|
||||||
type: string
|
type: string
|
||||||
name: roomId
|
name: roomId
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The id of the room to get tags for.
|
The ID of the room to get tags for.
|
||||||
x-example: "!726s6s6q:example.com"
|
x-example: "!726s6s6q:example.com"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
@ -60,13 +60,23 @@ paths:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
tags:
|
tags:
|
||||||
title: Tags
|
|
||||||
type: object
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
title: Tag
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: |-
|
||||||
|
A number in a range ``[0,1]`` describing a relative
|
||||||
|
position of the room under the given tag.
|
||||||
|
additionalProperties: true
|
||||||
examples:
|
examples:
|
||||||
application/json: {
|
application/json: {
|
||||||
"tags": {
|
"tags": {
|
||||||
"m.favourite": {},
|
"m.favourite": {"order": 0.1},
|
||||||
"u.Work": {"order": "1"},
|
"u.Work": {"order": 0.7},
|
||||||
"u.Customers": {}
|
"u.Customers": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,14 +97,14 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The id of the user to add a tag for. The access token must be
|
The id of the user to add a tag for. The access token must be
|
||||||
authorized to make requests for this user id.
|
authorized to make requests for this user ID.
|
||||||
x-example: "@alice:example.com"
|
x-example: "@alice:example.com"
|
||||||
- in: path
|
- in: path
|
||||||
type: string
|
type: string
|
||||||
name: roomId
|
name: roomId
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The id of the room to add a tag to.
|
The ID of the room to add a tag to.
|
||||||
x-example: "!726s6s6q:example.com"
|
x-example: "!726s6s6q:example.com"
|
||||||
- in: path
|
- in: path
|
||||||
type: string
|
type: string
|
||||||
|
@ -102,7 +112,7 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The tag to add.
|
The tag to add.
|
||||||
x-example: "work"
|
x-example: "u.work"
|
||||||
- in: body
|
- in: body
|
||||||
name: body
|
name: body
|
||||||
required: true
|
required: true
|
||||||
|
@ -110,8 +120,17 @@ paths:
|
||||||
Extra data for the tag, e.g. ordering.
|
Extra data for the tag, e.g. ordering.
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
|
properties:
|
||||||
|
order:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: |-
|
||||||
|
A number in a range ``[0,1]`` describing a relative
|
||||||
|
position of the room under the given tag.
|
||||||
|
additionalProperties: true
|
||||||
example: {
|
example: {
|
||||||
"order": "1"}
|
"order": 0.25
|
||||||
|
}
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description:
|
description:
|
||||||
|
@ -119,8 +138,7 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
examples:
|
examples:
|
||||||
application/json: {
|
application/json: {}
|
||||||
}
|
|
||||||
tags:
|
tags:
|
||||||
- User data
|
- User data
|
||||||
delete:
|
delete:
|
||||||
|
@ -137,14 +155,14 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The id of the user to remove a tag for. The access token must be
|
The id of the user to remove a tag for. The access token must be
|
||||||
authorized to make requests for this user id.
|
authorized to make requests for this user ID.
|
||||||
x-example: "@alice:example.com"
|
x-example: "@alice:example.com"
|
||||||
- in: path
|
- in: path
|
||||||
type: string
|
type: string
|
||||||
name: roomId
|
name: roomId
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The id of the room to remove a tag from.
|
The ID of the room to remove a tag from.
|
||||||
x-example: "!726s6s6q:example.com"
|
x-example: "!726s6s6q:example.com"
|
||||||
- in: path
|
- in: path
|
||||||
type: string
|
type: string
|
||||||
|
@ -152,15 +170,14 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
description: |-
|
description: |-
|
||||||
The tag to remove.
|
The tag to remove.
|
||||||
x-example: "work"
|
x-example: "u.work"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description:
|
description:
|
||||||
The tag was successfully removed
|
The tag was successfully removed.
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
examples:
|
examples:
|
||||||
application/json: {
|
application/json: {}
|
||||||
}
|
|
||||||
tags:
|
tags:
|
||||||
- User data
|
- User data
|
||||||
|
|
|
@ -31,8 +31,16 @@ paths:
|
||||||
post:
|
post:
|
||||||
summary: Searches the user directory.
|
summary: Searches the user directory.
|
||||||
description: |-
|
description: |-
|
||||||
This API performs a server-side search over all users registered on the server.
|
Performs a search for users on the homeserver. The homeserver may
|
||||||
It searches user ID and displayname case-insensitively for users that you share a room with or that are in public rooms.
|
determine which subset of users are searched, however the homeserver
|
||||||
|
MUST at a minimum consider the users the requesting user shares a
|
||||||
|
room with and those who reside in public rooms (known to the homeserver).
|
||||||
|
The search MUST consider local users to the homeserver, and SHOULD
|
||||||
|
query remote users as part of the search.
|
||||||
|
|
||||||
|
The search is performed case-insensitively on user IDs and display
|
||||||
|
names preferably using a collation determined based upon the
|
||||||
|
``Accept-Language`` header provided in the request, if present.
|
||||||
operationId: searchUserDirectory
|
operationId: searchUserDirectory
|
||||||
security:
|
security:
|
||||||
- accessToken: []
|
- accessToken: []
|
||||||
|
@ -48,7 +56,7 @@ paths:
|
||||||
example: "foo"
|
example: "foo"
|
||||||
limit:
|
limit:
|
||||||
type: integer
|
type: integer
|
||||||
description: The maximum number of results to return (Defaults to 10).
|
description: The maximum number of results to return. Defaults to 10.
|
||||||
example: 10
|
example: 10
|
||||||
required: ["search_term"]
|
required: ["search_term"]
|
||||||
responses:
|
responses:
|
||||||
|
|
|
@ -69,7 +69,8 @@ paths:
|
||||||
get:
|
get:
|
||||||
summary: Check whether a long-term public key is valid.
|
summary: Check whether a long-term public key is valid.
|
||||||
description: |-
|
description: |-
|
||||||
Check whether a long-term public key is valid.
|
Check whether a long-term public key is valid. The response should always
|
||||||
|
be the same, provided the key exists.
|
||||||
operationId: isPubKeyValid
|
operationId: isPubKeyValid
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
|
|
|
@ -23,7 +23,8 @@ allOf:
|
||||||
hashes:
|
hashes:
|
||||||
type: object
|
type: object
|
||||||
title: Event Hash
|
title: Event Hash
|
||||||
description: Hashes of the PDU, following the algorithm specified in `Signing Events`_.
|
description: |-
|
||||||
|
Content hashes of the PDU, following the algorithm specified in `Signing Events`_.
|
||||||
example: {
|
example: {
|
||||||
"sha256": "thishashcoversallfieldsincasethisisredacted"
|
"sha256": "thishashcoversallfieldsincasethisisredacted"
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,8 @@ properties:
|
||||||
prev_events:
|
prev_events:
|
||||||
type: array
|
type: array
|
||||||
description: |-
|
description: |-
|
||||||
Event IDs and hashes of the most recent events in the room that the homeserver was aware
|
Event IDs and reference hashes for the most recent events in the room
|
||||||
of when it made this event.
|
that the homeserver was aware of when it made this event.
|
||||||
items:
|
items:
|
||||||
type: array
|
type: array
|
||||||
maxItems: 2
|
maxItems: 2
|
||||||
|
@ -86,7 +86,7 @@ properties:
|
||||||
auth_events:
|
auth_events:
|
||||||
type: array
|
type: array
|
||||||
description: |-
|
description: |-
|
||||||
An event reference list containing the authorization events that would
|
Event IDs and reference hashes for the authorization events that would
|
||||||
allow this event to be in the room.
|
allow this event to be in the room.
|
||||||
items:
|
items:
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -194,3 +194,126 @@ paths:
|
||||||
type: object
|
type: object
|
||||||
description: An empty object
|
description: An empty object
|
||||||
example: {}
|
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: {}
|
||||||
|
|
189
api/server-server/user_keys.yaml
Normal file
189
api/server-server/user_keys.yaml
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
# Copyright 2018 New Vector 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.
|
||||||
|
|
||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Federation User Key Management API"
|
||||||
|
version: "1.0.0"
|
||||||
|
host: localhost:8448
|
||||||
|
schemes:
|
||||||
|
- https
|
||||||
|
basePath: /_matrix/federation/v1
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
securityDefinitions:
|
||||||
|
$ref: definitions/security.yaml
|
||||||
|
paths:
|
||||||
|
"/user/keys/claim":
|
||||||
|
post:
|
||||||
|
summary: Claims one-time encryption keys for a user.
|
||||||
|
description: |-
|
||||||
|
Claims one-time keys for use in pre-key messages.
|
||||||
|
operationId: claimUserEncryptionKeys
|
||||||
|
security:
|
||||||
|
- signedRequest: []
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
type: object
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
one_time_keys:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
The keys to be claimed. A map from user ID, to a map from
|
||||||
|
device ID to algorithm name.
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: algorithm
|
||||||
|
example: "signed_curve25519"
|
||||||
|
example: {
|
||||||
|
"@alice:example.com": {
|
||||||
|
"JLAFKJWSCS": "signed_curve25519"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
required:
|
||||||
|
- one_time_keys
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The claimed keys
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
one_time_keys:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
One-time keys for the queried devices. A map from user ID, to a
|
||||||
|
map from devices to a map from ``<algorithm>:<key_id>`` to the key object.
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- object
|
||||||
|
required: ['one_time_keys']
|
||||||
|
examples:
|
||||||
|
application/json: {
|
||||||
|
"one_time_keys": {
|
||||||
|
"@alice:example.com": {
|
||||||
|
"JLAFKJWSCS": {
|
||||||
|
"signed_curve25518:AAAAHg": {
|
||||||
|
"key": "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs",
|
||||||
|
"signatures": {
|
||||||
|
"@alice:example.com": {
|
||||||
|
"ed25519:JLAFKJWSCS": "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"/user/keys/query":
|
||||||
|
post:
|
||||||
|
summary: Download device identity keys.
|
||||||
|
description: |-
|
||||||
|
Returns the current devices and identity keys for the given users.
|
||||||
|
operationId: queryUserEncryptionKeys
|
||||||
|
security:
|
||||||
|
- signedRequest: []
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
type: object
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
device_keys:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
The keys to be downloaded. A map from user ID, to a list of
|
||||||
|
device IDs, or to an empty list to indicate all devices for the
|
||||||
|
corresponding user.
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: "Device ID"
|
||||||
|
example: {
|
||||||
|
"@alice:example.com": []
|
||||||
|
}
|
||||||
|
required: ['device_keys']
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The device information.
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
device_keys:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
Information on the queried devices. A map from user ID, to a
|
||||||
|
map from device ID to device information. For each device,
|
||||||
|
the information returned will be the same as uploaded via
|
||||||
|
``/keys/upload``, with the addition of an ``unsigned``
|
||||||
|
property.
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
allOf:
|
||||||
|
- $ref: ../client-server/definitions/device_keys.yaml
|
||||||
|
properties:
|
||||||
|
unsigned:
|
||||||
|
title: UnsignedDeviceInfo
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
Additional data added to the device key information
|
||||||
|
by intermediate servers, and not covered by the
|
||||||
|
signatures.
|
||||||
|
properties:
|
||||||
|
device_display_name:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
The display name which the user set on the device.
|
||||||
|
required: ['device_keys']
|
||||||
|
examples:
|
||||||
|
application/json: {
|
||||||
|
"device_keys": {
|
||||||
|
"@alice:example.com": {
|
||||||
|
"JLAFKJWSCS": {
|
||||||
|
"user_id": "@alice:example.com",
|
||||||
|
"device_id": "JLAFKJWSCS",
|
||||||
|
"algorithms": [
|
||||||
|
"m.olm.v1.curve25519-aes-sha256",
|
||||||
|
"m.megolm.v1.aes-sha"
|
||||||
|
],
|
||||||
|
"keys": {
|
||||||
|
"curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
|
||||||
|
"ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
|
||||||
|
},
|
||||||
|
"signatures": {
|
||||||
|
"@alice:example.com": {
|
||||||
|
"ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"device_display_name": "Alice's mobile phone"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
r0.1.0
|
||||||
|
======
|
||||||
|
|
||||||
|
This is the first release of the Application Service specification. It
|
||||||
|
includes support for application services being able to interact with
|
||||||
|
homeservers and bridge third party networks, such as IRC, over to Matrix
|
||||||
|
in a standard and accessible way.
|
1
changelogs/client_server/newsfragments/1567.feature
Normal file
1
changelogs/client_server/newsfragments/1567.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Document the ``validated_at`` and ``added_at`` fields on ``GET /acount/3pid``.
|
1
changelogs/client_server/newsfragments/1567.new
Normal file
1
changelogs/client_server/newsfragments/1567.new
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add ``POST /account/3pid/delete``
|
|
@ -0,0 +1 @@
|
||||||
|
Clarify the homeserver's behaviour for searching users.
|
|
@ -0,0 +1 @@
|
||||||
|
Clarify the event fields used in the ``/sync`` response.
|
1
changelogs/client_server/newsfragments/1589.feature
Normal file
1
changelogs/client_server/newsfragments/1589.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add an ``inhibit_login`` registration option.
|
1
changelogs/client_server/newsfragments/1600.feature
Normal file
1
changelogs/client_server/newsfragments/1600.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Recommend that servers set a Content Security Policy for the content repository.
|
|
@ -0,0 +1 @@
|
||||||
|
Add the other keys that redactions are expected to preserve.
|
|
@ -0,0 +1 @@
|
||||||
|
Clarify that clients should not be generating invalid HTML for formatted events.
|
|
@ -0,0 +1 @@
|
||||||
|
Clarify the room tag structure (thanks @KitsuneRal!)
|
|
@ -0,0 +1 @@
|
||||||
|
Add a note that clients may use the transaction ID to avoid flickering when doing local echo.
|
|
@ -3,7 +3,7 @@
|
||||||
"type": "m.tag",
|
"type": "m.tag",
|
||||||
"content": {
|
"content": {
|
||||||
"tags": {
|
"tags": {
|
||||||
"u.work": {"order": 1}
|
"u.work": {"order": 0.9}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,6 @@ properties:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- type
|
- type
|
||||||
|
- content
|
||||||
title: Event
|
title: Event
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -1,46 +1,14 @@
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: event.yaml
|
- $ref: sync_room_event.yaml
|
||||||
description: In addition to the Event fields, Room Events have the following additional
|
description: In addition to the Event fields, Room Events have the following additional
|
||||||
fields.
|
fields.
|
||||||
properties:
|
properties:
|
||||||
event_id:
|
|
||||||
description: The globally unique event identifier.
|
|
||||||
type: string
|
|
||||||
room_id:
|
room_id:
|
||||||
description: The ID of the room associated with this event.
|
description: |-
|
||||||
|
The ID of the room associated with this event. Will not be present on events
|
||||||
|
that arrive through ``/sync``, despite being required everywhere else.
|
||||||
type: string
|
type: string
|
||||||
sender:
|
|
||||||
description: Contains the fully-qualified ID of the user who *sent*
|
|
||||||
this event.
|
|
||||||
type: string
|
|
||||||
origin_server_ts:
|
|
||||||
description: Timestamp in milliseconds on originating homeserver
|
|
||||||
when this event was sent.
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
unsigned:
|
|
||||||
description: Contains optional extra information about the event.
|
|
||||||
properties:
|
|
||||||
age:
|
|
||||||
description: The time in milliseconds that has elapsed since the event was
|
|
||||||
sent. This field is generated by the local homeserver, and may be incorrect
|
|
||||||
if the local time on at least one of the two servers is out of sync, which can
|
|
||||||
cause the age to either be negative or greater than it actually is.
|
|
||||||
type: integer
|
|
||||||
redacted_because:
|
|
||||||
description: Optional. The event that redacted this event, if any.
|
|
||||||
title: Event
|
|
||||||
type: object
|
|
||||||
transaction_id:
|
|
||||||
description: The client-supplied transaction ID, if the client being given
|
|
||||||
the event is the same one which sent it.
|
|
||||||
type: string
|
|
||||||
title: UnsignedData
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- event_id
|
|
||||||
- room_id
|
- room_id
|
||||||
- sender
|
|
||||||
- origin_server_ts
|
|
||||||
title: Room Event
|
title: Room Event
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -1,23 +1,7 @@
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: room_event.yaml
|
- $ref: room_event.yaml
|
||||||
|
- $ref: sync_state_event.yaml
|
||||||
description: In addition to the Room Event fields, State Events have the following
|
description: In addition to the Room Event fields, State Events have the following
|
||||||
additional fields.
|
additional fields.
|
||||||
properties:
|
|
||||||
prev_content:
|
|
||||||
description: Optional. The previous ``content`` for this event. If there is no
|
|
||||||
previous content, this key will be missing.
|
|
||||||
title: EventContent
|
|
||||||
type: object
|
|
||||||
state_key:
|
|
||||||
description: A unique key which defines the overwriting semantics for this piece
|
|
||||||
of room state. This value is often a zero-length string. The presence of this
|
|
||||||
key makes this event a State Event.
|
|
||||||
|
|
||||||
State keys starting with an ``@`` are reserved for referencing user IDs, such
|
|
||||||
as room members. With the exception of a few events, state events set with a
|
|
||||||
given user's ID as the state key MUST only be set by that user.
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- state_key
|
|
||||||
title: State Event
|
title: State Event
|
||||||
type: object
|
type: object
|
||||||
|
|
60
event-schemas/schema/core-event-schema/sync_room_event.yaml
Normal file
60
event-schemas/schema/core-event-schema/sync_room_event.yaml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# Copyright 2018 New Vector 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.
|
||||||
|
|
||||||
|
# Note: this is technically not a core event schema, however it is included here
|
||||||
|
# to keep things sane. The short story is that /sync doesn't require a room_id to
|
||||||
|
# be on events, so we give it a whole event structure as a base for room_event.
|
||||||
|
# This base doesn't declare a room_id, which instead appears in the room_event.
|
||||||
|
|
||||||
|
allOf:
|
||||||
|
- $ref: event.yaml
|
||||||
|
description: In addition to the Event fields, Room Events have the following additional
|
||||||
|
fields.
|
||||||
|
properties:
|
||||||
|
event_id:
|
||||||
|
description: The globally unique event identifier.
|
||||||
|
type: string
|
||||||
|
sender:
|
||||||
|
description: Contains the fully-qualified ID of the user who sent this event.
|
||||||
|
type: string
|
||||||
|
origin_server_ts:
|
||||||
|
description: Timestamp in milliseconds on originating homeserver
|
||||||
|
when this event was sent.
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
unsigned:
|
||||||
|
description: Contains optional extra information about the event.
|
||||||
|
properties:
|
||||||
|
age:
|
||||||
|
description: The time in milliseconds that has elapsed since the event was
|
||||||
|
sent. This field is generated by the local homeserver, and may be incorrect
|
||||||
|
if the local time on at least one of the two servers is out of sync, which can
|
||||||
|
cause the age to either be negative or greater than it actually is.
|
||||||
|
type: integer
|
||||||
|
redacted_because:
|
||||||
|
description: Optional. The event that redacted this event, if any.
|
||||||
|
title: Event
|
||||||
|
type: object
|
||||||
|
transaction_id:
|
||||||
|
description: The client-supplied transaction ID, if the client being given
|
||||||
|
the event is the same one which sent it.
|
||||||
|
type: string
|
||||||
|
title: UnsignedData
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- event_id
|
||||||
|
- sender
|
||||||
|
- origin_server_ts
|
||||||
|
title: Room Event
|
||||||
|
type: object
|
39
event-schemas/schema/core-event-schema/sync_state_event.yaml
Normal file
39
event-schemas/schema/core-event-schema/sync_state_event.yaml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Copyright 2018 New Vector 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.
|
||||||
|
|
||||||
|
# See sync_room_event.yaml for why this file is here.
|
||||||
|
|
||||||
|
allOf:
|
||||||
|
- $ref: sync_room_event.yaml
|
||||||
|
description: In addition to the Room Event fields, State Events have the following
|
||||||
|
additional fields.
|
||||||
|
properties:
|
||||||
|
prev_content:
|
||||||
|
description: Optional. The previous ``content`` for this event. If there is no
|
||||||
|
previous content, this key will be missing.
|
||||||
|
title: EventContent
|
||||||
|
type: object
|
||||||
|
state_key:
|
||||||
|
description: A unique key which defines the overwriting semantics for this piece
|
||||||
|
of room state. This value is often a zero-length string. The presence of this
|
||||||
|
key makes this event a State Event.
|
||||||
|
|
||||||
|
State keys starting with an ``@`` are reserved for referencing user IDs, such
|
||||||
|
as room members. With the exception of a few events, state events set with a
|
||||||
|
given user's ID as the state key MUST only be set by that user.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- state_key
|
||||||
|
title: State Event
|
||||||
|
type: object
|
|
@ -18,7 +18,15 @@
|
||||||
"description": "The tags on the room and their contents.",
|
"description": "The tags on the room and their contents.",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"title": "Tag",
|
"title": "Tag",
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"order": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float",
|
||||||
|
"description":
|
||||||
|
"A number in a range ``[0,1]`` describing a relative position of the room under the given tag."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
526
proposals/1442-state-resolution.md
Normal file
526
proposals/1442-state-resolution.md
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
# State Resolution: Reloaded
|
||||||
|
|
||||||
|
|
||||||
|
Thoughts on the next iteration of the state resolution algorithm that aims to
|
||||||
|
mitigate currently known attacks
|
||||||
|
|
||||||
|
|
||||||
|
# Background
|
||||||
|
|
||||||
|
The state of a room at an event is a mapping from key to event, which is built
|
||||||
|
up and updated by sending state events into the room. All the information about
|
||||||
|
the room is encoded in the state, from metadata like the name and topic to
|
||||||
|
membership of the room to security policies like bans and join rules.
|
||||||
|
|
||||||
|
It is therefore important that─wherever possible─the view of the state of the
|
||||||
|
room is consistent across all servers. If different servers have different
|
||||||
|
views of the state then it can lead to the room bifurcating, due to differing
|
||||||
|
ideas on who is in the room, who is allowed to talk, etc.
|
||||||
|
|
||||||
|
The difficulty comes when the room DAG forks and then merges again (which can
|
||||||
|
happen naturally if two servers send events at the same time or when a network
|
||||||
|
partition is resolved). The state after the merge has to be resolved from the
|
||||||
|
state of the two branches: the algorithm to resolve this is called the _state
|
||||||
|
resolution algorithm_.
|
||||||
|
|
||||||
|
Since the result of state resolution must be consistent across servers, the
|
||||||
|
information that the algorithm can use is strictly limited to the information
|
||||||
|
that will always be available to all servers (including future servers that may
|
||||||
|
not even be in the room at that point) at any point in time where the
|
||||||
|
resolution needs to be calculated. In particular, this has the consequence that
|
||||||
|
the algorithm cannot use information from the room DAG, since servers are not
|
||||||
|
required to store events for any length of time.
|
||||||
|
|
||||||
|
**As such, the state resolution algorithm is effectively a pure function from
|
||||||
|
sets of state to a single resolved set of state.**
|
||||||
|
|
||||||
|
The final important property for state resolution is that it should not allow
|
||||||
|
malicious servers to avoid moderation action by forking and merging the room
|
||||||
|
DAG. For example, if a server gets banned and then forks the room before the
|
||||||
|
ban, any merge back should always ensure that the ban is still in the state.
|
||||||
|
|
||||||
|
|
||||||
|
# Current Algorithm
|
||||||
|
|
||||||
|
The current state resolution is known to have some undesirable properties,
|
||||||
|
which can be summarized into two separate cases:
|
||||||
|
|
||||||
|
1. Moderation evasion ─ where an attacker can avoid e.g. bans by forking and
|
||||||
|
joining the room DAG in particular ways.
|
||||||
|
1. State resets ─ where a server (often innocently) sends an event that points
|
||||||
|
to disparate parts of the graph, causing state resolution to pick old state
|
||||||
|
rather than later versions.
|
||||||
|
|
||||||
|
These have the following causes:
|
||||||
|
|
||||||
|
1. Conflicting state must pass auth checks to be eligible to be picked, but the
|
||||||
|
algorithm does not consider previous (superseded) state changes in a fork.
|
||||||
|
For example, where Alice gives Bob power and then Bob gives Charlie power on
|
||||||
|
one branch of a conflict, when the latter power level event is authed
|
||||||
|
against the original power level (where Bob didn't have power), it fails.
|
||||||
|
1. The algorithm relies on the deprecated and untrustable depth parameter to
|
||||||
|
try and ensure that the "most recent" state is picked. Without having a copy
|
||||||
|
of the complete room DAG the algorithm doesn't know that e.g. one topic
|
||||||
|
event came strictly after another in the DAG. For efficiency and storage
|
||||||
|
reasons servers are not required (or expected) to store the whole room DAG.
|
||||||
|
1. The algorithm always accepts events where there are no conflicting
|
||||||
|
alternatives in other forks. This means that if an admin changed the join
|
||||||
|
rules to `private`, then new joins on forks based on parts of the DAG which
|
||||||
|
predate that change would always be accepted without being authed against
|
||||||
|
the join_rules event.
|
||||||
|
|
||||||
|
|
||||||
|
# Desirable Properties
|
||||||
|
|
||||||
|
As well as the important properties listed in the "Background" section, there
|
||||||
|
are also some other properties that would significantly improve the experience
|
||||||
|
of end users, though not strictly essential. These include:
|
||||||
|
|
||||||
|
* Banning and changing power levels should "do the right thing", i.e. end
|
||||||
|
users shouldn't have to take extra steps to make the state resolution
|
||||||
|
produce the "right" results.
|
||||||
|
* Minimise occurences of "state resets". Servers will sometimes point to
|
||||||
|
disparate parts of the room DAG (due to a variety of reasons), which ideally
|
||||||
|
should not result in changes in the state.
|
||||||
|
* Be efficient; state resolution can happen a lot on some large rooms. Ideally
|
||||||
|
it would also support efficiently working on "state deltas" - i.e. the
|
||||||
|
ability to calculate state resolution incrementally from snapshots rather
|
||||||
|
than having to consider the full state of each fork each time a conflict is
|
||||||
|
resolved
|
||||||
|
|
||||||
|
|
||||||
|
# Ideas for New Algorithm
|
||||||
|
|
||||||
|
|
||||||
|
## Auth Chain
|
||||||
|
|
||||||
|
The _auth events_ of a given event is the set of events which justify why a
|
||||||
|
given event is allowed to be sent into a room (e.g. an m.room.create, an
|
||||||
|
m.room.power_levels and the sender's m.room.membership). The _auth chain_ of an
|
||||||
|
event is its auth events and their auth events, recursively. The auth chains of
|
||||||
|
a set of events in a given room form a DAG.
|
||||||
|
|
||||||
|
"Auth events" are events that can appear as auth events of an event. These
|
||||||
|
include power levels, membership etc.[^1]
|
||||||
|
|
||||||
|
Servers in a room are required to have the full auth chain for all events that
|
||||||
|
they have seen, and so the auth chain is available to be used by state
|
||||||
|
resolution algorithms.
|
||||||
|
|
||||||
|
|
||||||
|
## Unconflicted State
|
||||||
|
|
||||||
|
The current algorithm defines the notion of "unconflicted state" to be all
|
||||||
|
entries that for each set of state either has the same event or no entry. All
|
||||||
|
unconflicted state entries are included in the resolved state. This is
|
||||||
|
problematic due to the fact that any new entries introduced on forks always
|
||||||
|
appear in the resolved state, regardless of if they would pass the checks
|
||||||
|
applied to conflicted state.
|
||||||
|
|
||||||
|
The new algorithm could redefine "unconflicted state" to be all entries which
|
||||||
|
both exist and are the same in every state set (as opposed to previously where
|
||||||
|
the entry didn't need to exist in every state set).
|
||||||
|
|
||||||
|
|
||||||
|
## Replacing Depth
|
||||||
|
|
||||||
|
Since depth of an event cannot be reliably calculated without possessing the
|
||||||
|
full DAG, and cannot be trusted when provided by other servers, it can not be
|
||||||
|
used in future versions of state resolution. A potential alternative, however,
|
||||||
|
is to use "origin_server_ts". While it cannot be relied on to be accurate─an
|
||||||
|
attacker can set it to arbitrary values─it has the advantage over depth that
|
||||||
|
end users can clearly see when a server is using incorrect values. (Note that
|
||||||
|
server clocks don't need to be particularly accurate for the ordering to still
|
||||||
|
be more useful than other arbitrary orderings).
|
||||||
|
|
||||||
|
It can also be assumed that in most cases the origin_server_ts for a given
|
||||||
|
benign server will be mostly consistent. For example, if a server sends a join
|
||||||
|
and then a leave in the vast majority of cases the leave would have a greater
|
||||||
|
origin_server_ts.
|
||||||
|
|
||||||
|
This makes "origin_server_ts" a good candidate to be used as a last resort to
|
||||||
|
order events if necessary, where otherwise a different arbitrary ordering would
|
||||||
|
be used. However, it's important that there is some other mechanism to ensure
|
||||||
|
that malicious servers can't abuse origin_server_ts to ensure their state
|
||||||
|
always gets picked during resolution (In the proposal below we use the auth DAG
|
||||||
|
ordering to override users who set state with malicious origin_server_ts.)
|
||||||
|
|
||||||
|
|
||||||
|
## Ordering and Authing
|
||||||
|
|
||||||
|
Roughly, the current algorithm tries to ensure that moderation evasion doesn't
|
||||||
|
happen by ordering conflicted events by depth and (re)authing them
|
||||||
|
sequentially. The exact implementation has several issues, but the idea of
|
||||||
|
ensuring that state events from forks still need to pass auth subject to e.g.
|
||||||
|
bans and power level changes is a powerful one, as it reduces the utility of
|
||||||
|
maliciously forking.
|
||||||
|
|
||||||
|
For that to work we need to ensure that there is a suitable ordering that puts
|
||||||
|
e.g. bans before events sent in other forks. (However events can point to old
|
||||||
|
parts of the DAG, for a variety of reasons, and ideally in that case the
|
||||||
|
resolved state would closely match the recent state).
|
||||||
|
|
||||||
|
Similarly care should be taken when multiple changes to e.g. power levels happen
|
||||||
|
in a fork. If Alice gives Bob power (A), then Bob gives Charlie power (B) and
|
||||||
|
then Charlie, say, changes the ban level (C). If you try and resolve two state
|
||||||
|
sets one of which has A and the other has C, C will not pass auth unless B is
|
||||||
|
also taken into account. This case can be handled if we also consider the
|
||||||
|
difference in auth chains between the two sets, which in the previous example
|
||||||
|
would include B.
|
||||||
|
|
||||||
|
(This is also the root cause of the "Hotel California" issue, where left users
|
||||||
|
get spontaneously rejoined to rooms. This happens when a user has a sequence of
|
||||||
|
memberships changes of the form: leave (A), join (B) and then another leave (C).
|
||||||
|
In the current algorithm a resoluton of A and C would pick A, and a resolution
|
||||||
|
of A and B would then pick B, i.e. the join. This means that a suitably forked
|
||||||
|
graph can reset the state to B. This is fixed if when resolving A and C we also
|
||||||
|
consider B, since its in the auth chain of C.)
|
||||||
|
|
||||||
|
|
||||||
|
## Power Level Ordering
|
||||||
|
|
||||||
|
Actions that malicious servers would try and evade are actions that require
|
||||||
|
greater power levels to perform, for example banning, reducing power level,
|
||||||
|
etc. We define "power events" as events that have the potential to remove the
|
||||||
|
ability of another user to do something.[^2] (Note that they are a subset of
|
||||||
|
auth events.)
|
||||||
|
|
||||||
|
In all these cases it is desirable for those privileged actions to take
|
||||||
|
precedence over events in other forks. This can be achieved by first
|
||||||
|
considering "power events", and requiring the remaining events to pass auth
|
||||||
|
based on them.
|
||||||
|
|
||||||
|
|
||||||
|
## Mainline
|
||||||
|
|
||||||
|
An issue caused by servers not storing the full room DAG is that one can't tell
|
||||||
|
how two arbitrary events are ordered. The auth chain gives a partial ordering
|
||||||
|
to certain events, though far from complete; however, all events do contain a
|
||||||
|
reference to the current power levels in their auth events. As such if two
|
||||||
|
state events reference two different power levels events, and one power levels'
|
||||||
|
auth chain references the other, then there is a strong likelihood that the
|
||||||
|
event referencing the latter power level came after the other event.
|
||||||
|
|
||||||
|
A "mainline" is a list of power levels events created if you pick a particular
|
||||||
|
power levels event (usually the current resolved power level) and recursively
|
||||||
|
follow each power level referenced in auth_events back to the first power level
|
||||||
|
event.
|
||||||
|
|
||||||
|
The mainline can then be used to induce an ordering on events by looking at
|
||||||
|
where the power level referenced in their auth_events is in the mainline (or
|
||||||
|
recursively following the chain of power level events back until one is found
|
||||||
|
that appears in the mainline). This effectively partitions the room into
|
||||||
|
epochs, where a new epoch is started whenever a new power level is sent.
|
||||||
|
|
||||||
|
If this mainline ordering is combined with ordering by origin_server_ts, then
|
||||||
|
it gives an ordering that is correct for servers that don't lie about the time,
|
||||||
|
while giving a mechanism that can be used to deal if a server lied (by room
|
||||||
|
admins starting a new epoch).
|
||||||
|
|
||||||
|
The natural course of action for a room admin to take when noticing a
|
||||||
|
user/server is misbehaving is to ban them from the room, rather than changing
|
||||||
|
the power levels. It would therefore be useful if banning a user or server
|
||||||
|
started a new epoch as well. This would require being able to create a mainline
|
||||||
|
that includes power level events and bans[^3], which would suggest that power
|
||||||
|
level and ban events would need to point to the latest ban event as well. (This
|
||||||
|
would be significantly easier if we maintained a list of bans in a single
|
||||||
|
event, however there is concern that would limit the number of possible bans in
|
||||||
|
a room.)
|
||||||
|
|
||||||
|
|
||||||
|
# Proposed Algorithm
|
||||||
|
|
||||||
|
First we define:
|
||||||
|
|
||||||
|
* **"State sets"** are the sets of state that the resolution algorithm tries
|
||||||
|
to resolve, i.e. the inputs to the algorithm.
|
||||||
|
* **"Power events"** are events that have the potential to remove the ability
|
||||||
|
of another user to do something. These are power levels, join rules, bans
|
||||||
|
and kicks.
|
||||||
|
* The **"unconflicted state map"** is the state where the value of each key
|
||||||
|
exists and is the same in every state set. The **"conflicted state map"** is
|
||||||
|
everything else. (Note that this is subtly different to the definition used
|
||||||
|
in the existing algorithm, which considered the merge of a present event
|
||||||
|
with an absent event to be unconflicted rather than conflicted)
|
||||||
|
* The "**auth difference"** is calculated by first calculating the full auth
|
||||||
|
chain for each state set and taking every event that doesn't appear in every
|
||||||
|
auth chain.
|
||||||
|
* The **"full conflicted set"** is the union of the conflicted state map and
|
||||||
|
auth difference.
|
||||||
|
* The **"reverse topological power ordering"**[^4] of a set of events is an
|
||||||
|
ordering of the given events, plus any events in their auth chains that
|
||||||
|
appear in the auth difference, topologically ordered by their auth chains
|
||||||
|
with ties broken such that x < y if:
|
||||||
|
|
||||||
|
1. x's sender has a greater power level than y (calculated by looking at
|
||||||
|
their respective auth events, or if
|
||||||
|
2. x's origin_server_ts is less than y's, or if
|
||||||
|
3. x's event_id is lexicographically less than y's
|
||||||
|
|
||||||
|
This is also known as a lexicographical topological sort (i.e. this is the
|
||||||
|
unique topological ordering such that for an entry x all entries after it
|
||||||
|
must either have x in their auth chain or be greater than x as defined
|
||||||
|
above). This can be implemented using Kahn's algorithm.
|
||||||
|
|
||||||
|
* The **"mainline ordering"** based on a power level event P of a set of
|
||||||
|
events is calculated as follows:
|
||||||
|
1. Generate the list of power levels starting at P and recursively take the
|
||||||
|
power level from its auth events. This list is called the mainline,
|
||||||
|
ordered such that P is last.
|
||||||
|
1. We say the "closest mainline event" of an event is the first power level
|
||||||
|
event encountered in mainline when iteratively descending through the
|
||||||
|
power level events in the auth events.
|
||||||
|
1. Order the set of events such that x < y if:
|
||||||
|
1. The closest mainline event of x appears strictly before the closest
|
||||||
|
of y in the mainline list, or if
|
||||||
|
1. x's origin_server_ts is less than y's, or if
|
||||||
|
1. x's event_id lexicographically sorts before y's
|
||||||
|
* The **"iterative auth checks"** algorithm is where given a sorted list of
|
||||||
|
events, the auth check algorithm is applied to each event in turn. The state
|
||||||
|
events used to auth are built up from previous events that passed the auth
|
||||||
|
checks, starting from a base set of state. If a required auth key doesn't
|
||||||
|
exist in the state, then the one in the event's auth_events is used. (See
|
||||||
|
_Variations_ and _Attack Vectors_ below).
|
||||||
|
|
||||||
|
The algorithm proceeds as follows:
|
||||||
|
|
||||||
|
|
||||||
|
1. Take all power events and any events in their auth chains that appear in the
|
||||||
|
_full_ _conflicted set_ and order them by the _reverse topological power
|
||||||
|
ordering._
|
||||||
|
1. Apply the _iterative auth checks_ algorithm based on the unconflicted state
|
||||||
|
map to get a partial set of resolved state.
|
||||||
|
1. Take all remaining events that weren't picked in step 1 and order them by
|
||||||
|
the _mainline ordering_ based on the power level in the partially resolved
|
||||||
|
state.
|
||||||
|
1. Apply the _iterative auth checks algorithm_ based on the partial resolved
|
||||||
|
state.
|
||||||
|
1. Update the result with the _unconflicted state_ to get the final resolved
|
||||||
|
state[^5]. (_Note_: this is different from the current algorithm, which
|
||||||
|
considered different event types at distinct stages)
|
||||||
|
|
||||||
|
An example python implementation can be found on github
|
||||||
|
[here](https://github.com/matrix-org/matrix-test-state-resolution-ideas).
|
||||||
|
|
||||||
|
Note that this works best if we also change which events to include as an
|
||||||
|
event's auth_events. See the "Auth Events" section below.
|
||||||
|
|
||||||
|
|
||||||
|
## Discussion
|
||||||
|
|
||||||
|
Essentially, the algorithm works by producing a sorted list of all conflicted
|
||||||
|
events (and differences in auth chains), and applies the auth checks one by
|
||||||
|
one, building up the state as it goes. The list is produced in two parts: first
|
||||||
|
the power events and auth dependencies are ordered by power level of the
|
||||||
|
senders and resolved, then the remaining events are ordered using the
|
||||||
|
"mainline" of the resolved power levels and then resolved to produce the final
|
||||||
|
resolved state.
|
||||||
|
|
||||||
|
(This is equivalent to linearizing the full conflicted set of events and
|
||||||
|
reapplying the usual state updates and auth rules.)
|
||||||
|
|
||||||
|
|
||||||
|
### Variations
|
||||||
|
|
||||||
|
There are multiple options for what to use as the base state for _iterative
|
||||||
|
auth checks_ algorithm; while it needs to be some variation of auth events and
|
||||||
|
unconflicted events, it is unclear exactly which combination is best (and least
|
||||||
|
manipulatable by malicious servers).
|
||||||
|
|
||||||
|
Care has to be taken if we want to ensure that old auth events that appear in
|
||||||
|
the _auth chain difference_ can't supercede unconflicted state entries.
|
||||||
|
|
||||||
|
Due to auth chain differences being added to the resolved states during
|
||||||
|
_iterative auth checks_, we therefore need to re-apply the unconflicted state
|
||||||
|
at the end to ensure that they appear in the final resolved state. This feels
|
||||||
|
like an odd fudge that shouldn't be necessary, and may point to a flaw in the
|
||||||
|
proposed algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
### State Resets
|
||||||
|
|
||||||
|
The proposed algorithm still has some potentially unexpected behaviour.
|
||||||
|
|
||||||
|
One example of this is when Alice sets a topic and then gets banned. If an event
|
||||||
|
gets created (potentially much later) that points to both before and after the
|
||||||
|
topic and ban then the proposed algorithm will resolve and apply the ban before
|
||||||
|
resolving the topic, causing the topic to be denied and dropped from the
|
||||||
|
resolved state. This will result in no topic being set in the resolved state.
|
||||||
|
|
||||||
|
|
||||||
|
### Auth Events
|
||||||
|
|
||||||
|
The algorithm relies heavily on the ordering induced by the auth chain DAG.
|
||||||
|
|
||||||
|
There are two types of auth events (not necessarily distinct):
|
||||||
|
|
||||||
|
* Those that give authorization to do something
|
||||||
|
* Those that revoke authorization to do something.
|
||||||
|
|
||||||
|
For example, invites/joins are in the former category, leaves/kicks/bans are in
|
||||||
|
the latter and power levels are both.
|
||||||
|
|
||||||
|
Assuming[^6] revocations always point to (i.e., have in their auth chain) the
|
||||||
|
authorization event that they are revoking, and authorization events point to
|
||||||
|
revocations that they are superseding, then the algorithm will ensure that the
|
||||||
|
authorization events are applied in order (so generally the "latest"
|
||||||
|
authorization state would win).
|
||||||
|
|
||||||
|
This helps ensure that e.g. an invite cannot be reused after a leave/kick,
|
||||||
|
since the leave (revocation) would have the invite in their auth chain.
|
||||||
|
|
||||||
|
This idea also relies on revocations replacing the state that granted
|
||||||
|
authorization to do an action (and vice versa). For example, in the current
|
||||||
|
model bans (basically) revoke the ability for a particular user from being able
|
||||||
|
to join. If the user later gets unbanned and then rejoins, the join would point
|
||||||
|
to the join rules as the authorization that lets them join, but would not
|
||||||
|
(necessarily) point to the unban. This has the effect that if a state resolution
|
||||||
|
happened between the new join and the ban, the unban would not be included in
|
||||||
|
the resolution and so the join would be rejected.
|
||||||
|
|
||||||
|
The changes to the current model that would be required to make the above
|
||||||
|
assumptions true would be, for example:
|
||||||
|
|
||||||
|
1. By default permissions are closed.
|
||||||
|
1. Bans would need to be a list in either the join rules event or a separate
|
||||||
|
event type which all membership events pointed to.
|
||||||
|
1. Bans would only revoke the ability to join, not automatically remove users
|
||||||
|
from the room.
|
||||||
|
1. Change the defaults of join_rules to be closed by default
|
||||||
|
|
||||||
|
|
||||||
|
### Efficiency and Delta State Resolution
|
||||||
|
|
||||||
|
The current (unoptimised) implementation of the algorithm is 10x slower than
|
||||||
|
the current algorithm, based on a single, large test case. While hopefully some
|
||||||
|
optimisations can be made, the ability to [incrementally calculate state
|
||||||
|
resolution via deltas](https://github.com/matrix-org/synapse/pull/3122) will
|
||||||
|
also mitigate some of the slow down.
|
||||||
|
|
||||||
|
Another aspect that should be considered is the amount of data that is required
|
||||||
|
to perform the resolution. The current algorithm only requires the events for
|
||||||
|
the conflicted set, plus the events from the unconflicted set needed to auth
|
||||||
|
them. The proposed algorithm also requires the events in the auth chain
|
||||||
|
difference (calculating the auth chain difference may also require more data to
|
||||||
|
calculate).
|
||||||
|
|
||||||
|
Delta state resolution is where if you have, say, two state sets and their
|
||||||
|
resolution, then you can use that result to work out the new resolution where
|
||||||
|
there has been a small change to the state sets. For the proposed algorithm, if
|
||||||
|
the following properties hold true then the result can be found by simply
|
||||||
|
applying steps 3 and 4 to the state deltas. The properties are:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. The delta contains no power events
|
||||||
|
1. The origin_server_ts of all events in state delta are strictly greater than
|
||||||
|
those in the previous state sets
|
||||||
|
1. Any event that has been removed must not have been used to auth subsequent
|
||||||
|
events (e.g. if we replaced a member event and that user had also set a
|
||||||
|
topic)
|
||||||
|
|
||||||
|
These properties will likely hold true for most state updates that happen in a
|
||||||
|
room, allowing servers to use this more efficient algorithm the majority of the
|
||||||
|
time.
|
||||||
|
|
||||||
|
|
||||||
|
### Full DAG
|
||||||
|
|
||||||
|
It's worth noting that if the algorithm had access to the full room DAG that it
|
||||||
|
would really only help by ensuring that the ordering in "reverse topological
|
||||||
|
ordering" and "mainline ordering" respected the ordering induced by the DAG.
|
||||||
|
|
||||||
|
This would help, e.g., ensure the latest topic was always picked rather than
|
||||||
|
rely on origin_server_ts and mainline. As well as obviate the need to maintain
|
||||||
|
a separate auth chain, and the difficulties that entails (like having to
|
||||||
|
reapply the unconflicted state at the end).
|
||||||
|
|
||||||
|
|
||||||
|
### Attack Vectors
|
||||||
|
|
||||||
|
The main potential attack vector that needs to be considered is in the
|
||||||
|
_iterative auth checks_ algorithm, and whether an attacker could make use of
|
||||||
|
the fact that it's based on the unconflicted state and/or auth events of the
|
||||||
|
event.
|
||||||
|
|
||||||
|
|
||||||
|
# Appendix
|
||||||
|
|
||||||
|
The following is an example room DAG, where time flows down the page. We shall
|
||||||
|
work through resolving the state at both _Message 2_ and _Message 3_.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
(Note that green circles are events sent by Alice, blue circles sent by Bob and
|
||||||
|
black arrows point to previous events. The red arrows are the mainline computed
|
||||||
|
during resolution.)
|
||||||
|
|
||||||
|
First we resolve the state at _Message 2_. The conflicted event types are the
|
||||||
|
power levels and topics, and since the auth chains are the same for both state
|
||||||
|
sets the auth difference is the empty set.
|
||||||
|
|
||||||
|
Step 1: The _full conflicted set_ are the events _P2, P3, Topic 2 _and _Topic
|
||||||
|
3_, of which _P2_ and _P3_ are the only power events. Since Alice (the room
|
||||||
|
creator) has a greater power level than Bob (and neither _P2 _and _P3_ appear
|
||||||
|
in each other's auth chain), the reverse topological ordering is: [_P2, P3_].
|
||||||
|
|
||||||
|
Step 2: Now we apply the auth rules iteratively, _P2_ trivially passes based on
|
||||||
|
the unconflicted state, but _P3_ does not pass since after _P2_ Bob no longer
|
||||||
|
has sufficient power to set state. This results in the power levels resolving
|
||||||
|
to _P2_.
|
||||||
|
|
||||||
|
Step 3: Now we work out the mainline based on P2, which is coloured in red on
|
||||||
|
the diagram. We use the mainline to order _Topic 2_ and _Topic 3_. _Topic 2_
|
||||||
|
points to_ P1_, while the closest mainline to _Topic 3_ is also _P1_. We then
|
||||||
|
order based on the _origin_server_ts_ of the two events, let's assume that
|
||||||
|
gives us: [_Topic 2_, _Topic 3_].
|
||||||
|
|
||||||
|
Step 4: Iteratively applying the auth rules results in _Topic 2_ being allowed,
|
||||||
|
but _Topic 3 _being denied (since Bob doesn't have power to set state anymore),
|
||||||
|
so the topic is resolved to _Topic 2_.
|
||||||
|
|
||||||
|
This gives the resolved state at _Message 2_ to be _P2 _and _Topic 2_. (This is
|
||||||
|
actually the same result as the existing algorithm gives)
|
||||||
|
|
||||||
|
Now let's look at the state at _Message 3_.
|
||||||
|
|
||||||
|
Step 1: The full conflicted set are simple: _Topic 2_ and _Topic 4_. There are
|
||||||
|
no conflicted power events.
|
||||||
|
|
||||||
|
Step 2: N/A
|
||||||
|
|
||||||
|
Step 3: _Topic 2_ points to _P1_ in the mainline, and _Topic 4_ points to _P2_
|
||||||
|
in its auth events. Since _P2_ comes after _P1_ in the mainline, this gives an
|
||||||
|
ordering of [_Topic 2, Topic 4_].
|
||||||
|
|
||||||
|
Step 4: Iteratively applying the auth rules results in both topics passing the
|
||||||
|
auth checks, and so the last topic, _Topic 4_, is chosen.
|
||||||
|
|
||||||
|
This gives the resolved state at _Message 3_ to be _Topic 4_.
|
||||||
|
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
[^1]: In the current room protocol these are: the create event, power levels,
|
||||||
|
membership, join rules and third party invites. See the
|
||||||
|
[spec](https://matrix.org/docs/spec/server_server/unstable.html#pdu-fields).
|
||||||
|
|
||||||
|
[^2]: In the current protocol these are: power levels, kicks, bans and join
|
||||||
|
rules.
|
||||||
|
|
||||||
|
[^3]: Future room versions may have a concept of server ban event that works
|
||||||
|
like existing bans, which would also be included
|
||||||
|
|
||||||
|
[^4]: The topology being considered here is the auth chain DAG, rather than the
|
||||||
|
room DAG, so this ordering is only applicable to events which appear in the
|
||||||
|
auth chain DAG.
|
||||||
|
|
||||||
|
[^5]: We do this so that, if we receive events with misleading auth_events, this
|
||||||
|
ensures that the unconflicted state at least is correct.
|
||||||
|
|
||||||
|
[^6]: This isn't true in the current protocol
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
proposals/images/state-res.png
Normal file
BIN
proposals/images/state-res.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
|
@ -207,6 +207,13 @@ class MatrixSections(Sections):
|
||||||
apis = self.units.get("apis")
|
apis = self.units.get("apis")
|
||||||
return template.render(apis=apis)
|
return template.render(apis=apis)
|
||||||
|
|
||||||
|
def render_unstable_warnings(self):
|
||||||
|
rendered = {}
|
||||||
|
blocks = self.units.get("unstable_warnings")
|
||||||
|
for var, text in blocks.items():
|
||||||
|
rendered["unstable_warning_block_" + var] = text
|
||||||
|
return rendered
|
||||||
|
|
||||||
def render_swagger_definition(self):
|
def render_swagger_definition(self):
|
||||||
rendered = {}
|
rendered = {}
|
||||||
template = self.env.get_template("schema-definition.tmpl")
|
template = self.env.get_template("schema-definition.tmpl")
|
||||||
|
|
|
@ -971,6 +971,22 @@ class MatrixUnits(Units):
|
||||||
|
|
||||||
return changelogs
|
return changelogs
|
||||||
|
|
||||||
|
def load_unstable_warnings(self, substitutions):
|
||||||
|
warning = """
|
||||||
|
.. WARNING::
|
||||||
|
You are viewing an unstable version of this specification. Unstable
|
||||||
|
specifications may change at any time without notice. To view the
|
||||||
|
current specification, please `click here <latest.html>`_.
|
||||||
|
"""
|
||||||
|
warnings = {}
|
||||||
|
for var in substitutions.keys():
|
||||||
|
key = var[1:-1] # take off the surrounding %-signs
|
||||||
|
if substitutions.get(var, "unstable") == "unstable":
|
||||||
|
warnings[key] = warning
|
||||||
|
else:
|
||||||
|
warnings[key] = ""
|
||||||
|
return warnings
|
||||||
|
|
||||||
|
|
||||||
def load_spec_targets(self):
|
def load_spec_targets(self):
|
||||||
with open(TARGETS, "r") as f:
|
with open(TARGETS, "r") as f:
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
Application Service API
|
Application Service API
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
{{unstable_warning_block_APPSERVICE_RELEASE_LABEL}}
|
||||||
|
|
||||||
The Matrix client-server API and server-server APIs provide the means to
|
The Matrix client-server API and server-server APIs provide the means to
|
||||||
implement a consistent self-contained federated messaging fabric. However, they
|
implement a consistent self-contained federated messaging fabric. However, they
|
||||||
provide limited means of implementing custom server-side behaviour in Matrix
|
provide limited means of implementing custom server-side behaviour in Matrix
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
Client-Server API
|
Client-Server API
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
{{unstable_warning_block_CLIENT_RELEASE_LABEL}}
|
||||||
|
|
||||||
The client-server API provides a simple lightweight API to let clients send
|
The client-server API provides a simple lightweight API to let clients send
|
||||||
messages, control rooms and synchronise conversation history. It is designed to
|
messages, control rooms and synchronise conversation history. It is designed to
|
||||||
support both lightweight clients which store no state and lazy-load data from
|
support both lightweight clients which store no state and lazy-load data from
|
||||||
|
@ -1328,16 +1330,30 @@ the following list:
|
||||||
- ``state_key``
|
- ``state_key``
|
||||||
- ``prev_content``
|
- ``prev_content``
|
||||||
- ``content``
|
- ``content``
|
||||||
|
- ``hashes``
|
||||||
|
- ``signatures``
|
||||||
|
- ``depth``
|
||||||
|
- ``prev_events``
|
||||||
|
- ``prev_state``
|
||||||
|
- ``auth_events``
|
||||||
|
- ``origin``
|
||||||
|
- ``origin_server_ts``
|
||||||
|
- ``membership``
|
||||||
|
|
||||||
|
.. Note:
|
||||||
|
Some of the keys, such as ``hashes``, will appear on the federation-formatted
|
||||||
|
event and therefore the client may not be aware of them.
|
||||||
|
|
||||||
The content object should also be stripped of all keys, unless it is one of
|
The content object should also be stripped of all keys, unless it is one of
|
||||||
one of the following event types:
|
one of the following event types:
|
||||||
|
|
||||||
- ``m.room.member`` allows key ``membership``
|
- ``m.room.member`` allows key ``membership``.
|
||||||
- ``m.room.create`` allows key ``creator``
|
- ``m.room.create`` allows key ``creator``.
|
||||||
- ``m.room.join_rules`` allows key ``join_rule``
|
- ``m.room.join_rules`` allows key ``join_rule``.
|
||||||
- ``m.room.power_levels`` allows keys ``ban``, ``events``, ``events_default``,
|
- ``m.room.power_levels`` allows keys ``ban``, ``events``, ``events_default``,
|
||||||
``kick``, ``redact``, ``state_default``, ``users``, ``users_default``.
|
``kick``, ``redact``, ``state_default``, ``users``, ``users_default``.
|
||||||
- ``m.room.aliases`` allows key ``aliases``
|
- ``m.room.aliases`` allows key ``aliases``.
|
||||||
|
- ``m.room.history_visibility`` allows key ``history_visibility``.
|
||||||
|
|
||||||
The server should add the event causing the redaction to the ``unsigned``
|
The server should add the event causing the redaction to the ``unsigned``
|
||||||
property of the redacted event, under the ``redacted_because`` key. When a
|
property of the redacted event, under the ``redacted_because`` key. When a
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
Identity Service API
|
Identity Service API
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
{{unstable_warning_block_IDENTITY_RELEASE_LABEL}}
|
||||||
|
|
||||||
The Matrix client-server and server-server APIs are largely expressed in Matrix
|
The Matrix client-server and server-server APIs are largely expressed in Matrix
|
||||||
user identifiers. From time to time, it is useful to refer to users by other
|
user identifiers. From time to time, it is useful to refer to users by other
|
||||||
("third-party") identifiers, or "3pid"s, e.g. their email address or phone
|
("third-party") identifiers, or "3pid"s, e.g. their email address or phone
|
||||||
|
@ -179,9 +181,11 @@ An identity service has some long-term public-private keypairs. These are named
|
||||||
in a scheme ``algorithm:identifier``, e.g. ``ed25519:0``. When signing an
|
in a scheme ``algorithm:identifier``, e.g. ``ed25519:0``. When signing an
|
||||||
association, the standard `Signing JSON`_ algorithm applies.
|
association, the standard `Signing JSON`_ algorithm applies.
|
||||||
|
|
||||||
In the event of key compromise, the identity service may revoke any of its keys.
|
.. TODO: Actually allow identity services to revoke all keys
|
||||||
An HTTP API is offered to get public keys, and check whether a particular key is
|
See: https://github.com/matrix-org/matrix-doc/issues/1633
|
||||||
valid.
|
.. 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 service may also keep track of some short-term public-private
|
The identity service may also keep track of some short-term public-private
|
||||||
keypairs, which may have different usage and lifetime characteristics than the
|
keypairs, which may have different usage and lifetime characteristics than the
|
||||||
|
|
|
@ -33,6 +33,10 @@ recipient's local homeserver, which must first transfer the content from the
|
||||||
origin homeserver using the same API (unless the origin and destination
|
origin homeserver using the same API (unless the origin and destination
|
||||||
homeservers are the same).
|
homeservers are the same).
|
||||||
|
|
||||||
|
When serving content, the server SHOULD provide a ``Content-Security-Policy``
|
||||||
|
header. The recommended policy is ``default-src 'none'; script-src 'none';
|
||||||
|
plugin-types application/pdf; style-src 'unsafe-inline'; object-src 'self';``.
|
||||||
|
|
||||||
Client behaviour
|
Client behaviour
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,12 @@ of tags they can render, falling back to other representations of the tags where
|
||||||
For example, a client may not be able to render tables correctly and instead could fall
|
For example, a client may not be able to render tables correctly and instead could fall
|
||||||
back to rendering tab-delimited text.
|
back to rendering tab-delimited text.
|
||||||
|
|
||||||
|
In addition to not rendering unsafe HTML, clients should not emit unsafe HTML in events.
|
||||||
|
Likewise, clients should not generate HTML that is not needed, such as extra paragraph tags
|
||||||
|
surrounding text due to Rich Text Editors. HTML included in events should otherwise be valid,
|
||||||
|
such as having appropriate closing tags, appropriate attributes (considering the custom ones
|
||||||
|
defined in this specification), and generally valid structure.
|
||||||
|
|
||||||
.. Note::
|
.. Note::
|
||||||
A future iteration of the specification will support more powerful and extensible
|
A future iteration of the specification will support more powerful and extensible
|
||||||
message formatting options, such as the proposal `MSC1225 <https://github.com/matrix-org/matrix-doc/issues/1225>`_.
|
message formatting options, such as the proposal `MSC1225 <https://github.com/matrix-org/matrix-doc/issues/1225>`_.
|
||||||
|
@ -167,9 +173,14 @@ message which they receive from the event stream. The echo of the same message
|
||||||
from the event stream is referred to as "remote echo". Both echoes need to be
|
from the event stream is referred to as "remote echo". Both echoes need to be
|
||||||
identified as the same message in order to prevent duplicate messages being
|
identified as the same message in order to prevent duplicate messages being
|
||||||
displayed. Ideally this pairing would occur transparently to the user: the UI
|
displayed. Ideally this pairing would occur transparently to the user: the UI
|
||||||
would not flicker as it transitions from local to remote. Flickering cannot be
|
would not flicker as it transitions from local to remote. Flickering can be
|
||||||
fully avoided in the current client-server API. Two scenarios need to be
|
reduced through clients making use of the transaction ID they used to send
|
||||||
considered:
|
a particular event. The transaction ID used will be included in the event's
|
||||||
|
``unsigned`` data as ``transaction_id`` when it arrives through the event stream.
|
||||||
|
|
||||||
|
Clients unable to make use of the transaction ID are more likely to experience
|
||||||
|
flickering due to the following two scenarios, however the effect can be mitigated
|
||||||
|
to a degree:
|
||||||
|
|
||||||
- The client sends a message and the remote echo arrives on the event stream
|
- The client sends a message and the remote echo arrives on the event stream
|
||||||
*after* the request to send the message completes.
|
*after* the request to send the message completes.
|
||||||
|
|
|
@ -39,7 +39,7 @@ with an ``order`` of ``0.2`` would be displayed before a room with an ``order``
|
||||||
of ``0.7``. If a room has a tag without an ``order`` key then it should appear
|
of ``0.7``. If a room has a tag without an ``order`` key then it should appear
|
||||||
after the rooms with that tag that have an ``order`` key.
|
after the rooms with that tag that have an ``order`` key.
|
||||||
|
|
||||||
The name of a tag MUST not exceed 255 bytes.
|
The name of a tag MUST NOT exceed 255 bytes.
|
||||||
|
|
||||||
The tag namespace is defined as follows:
|
The tag namespace is defined as follows:
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ with ``content.membership`` = ``invite``, as well as a
|
||||||
``content.third_party_invite`` property which contains proof that the invitee
|
``content.third_party_invite`` property which contains proof that the invitee
|
||||||
does indeed own that third party identifier.
|
does indeed own that third party identifier.
|
||||||
|
|
||||||
|
|
||||||
Events
|
Events
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -55,41 +56,79 @@ A client asks a server to invite a user by their third party identifier.
|
||||||
Server behaviour
|
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
|
All homeservers MUST verify the signature in the event's
|
||||||
``content.third_party_invite.signed`` object.
|
``content.third_party_invite.signed`` object.
|
||||||
|
|
||||||
When a homeserver inserts an ``m.room.member`` ``invite`` event into the graph
|
The third party user will then need to verify their identity, which results in
|
||||||
because of an ``m.room.third_party_invite`` event,
|
a call from the Identity Server to the homeserver that bound the third party
|
||||||
that homesever MUST validate that the public
|
identifier to a user. The homeserver then exchanges the ``m.room.third_party_invite``
|
||||||
key used for signing is still valid, by checking ``key_validity_url`` from the ``m.room.third_party_invite``. It does
|
event in the room for a complete ``m.room.member`` event for ``membership: invite``
|
||||||
this by making an HTTP GET request to ``key_validity_url``:
|
for the user that has bound the third party identifier.
|
||||||
|
|
||||||
.. TODO: Link to identity server spec when it exists
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
If a homeserver is joining a room for the first time because of an
|
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
|
``m.room.third_party_invite``, the server which is already participating in the
|
||||||
|
@ -99,29 +138,84 @@ 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
|
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
|
``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.
|
membership is questionable.
|
||||||
|
|
||||||
For example:
|
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.
|
||||||
|
|
||||||
#. Room R has two participating homeservers, H1, H2
|
::
|
||||||
|
|
||||||
#. User A on H1 invites a third party identifier to room R
|
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
|
||||||
|
| 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 |
|
||||||
|
| | | | |------------------------ |
|
||||||
|
| | | | | | |
|
||||||
|
| | | | |<----------------------- |
|
||||||
|
| | | | | |
|
||||||
|
|
||||||
#. 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
|
Note that when H1 sends the ``m.room.member`` event to H2 and H3 it does not
|
||||||
is notified and attempts to issue an ``m.room.member`` event to participate
|
have to block on either server's receipt of the event. Likewise, H1 may complete
|
||||||
in the room.
|
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.
|
||||||
|
|
||||||
#. H3 validates the signature given to it by the identity server.
|
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
|
||||||
#. H3 then asks H1 to join it to the room. H1 *must* validate the ``signed``
|
a request to the `Identity Server /isvalid`_ endpoint, using the provided URL
|
||||||
property *and* check ``key_validity_url``.
|
rather than constructing a new one. The query string and response for the provided
|
||||||
|
URL must match the Identity Server specification.
|
||||||
#. Having validated these things, H1 writes the invite event to the room, and H3
|
|
||||||
begins participating in the room. H2 *must* accept this event.
|
|
||||||
|
|
||||||
The reason that no other homeserver may reject the event based on checking
|
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.
|
``key_validity_url`` is that we must ensure event acceptance is deterministic.
|
||||||
|
@ -158,3 +252,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
|
identity servers with many requests, or much state to store. Defending against
|
||||||
these is left to the implementer's discretion.
|
these is left to the implementer's discretion.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _`Identity Server /isvalid`: ../identity_service/unstable.html#get-matrix-identity-api-v1-pubkey-isvalid
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
Push Gateway API
|
Push Gateway API
|
||||||
================
|
================
|
||||||
|
|
||||||
|
{{unstable_warning_block_PUSH_GATEWAY_RELEASE_LABEL}}
|
||||||
|
|
||||||
Clients may want to receive push notifications when events are received at
|
Clients may want to receive push notifications when events are received at
|
||||||
the homeserver. This is managed by a distinct entity called the Push Gateway.
|
the homeserver. This is managed by a distinct entity called the Push Gateway.
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ Server implementation
|
||||||
|
|
||||||
{{version_ss_http_api}}
|
{{version_ss_http_api}}
|
||||||
|
|
||||||
Retrieving Server Keys
|
Retrieving server keys
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
|
@ -308,6 +308,8 @@ creating a new event in this room should populate the new event's
|
||||||
|
|
|
|
||||||
E4
|
E4
|
||||||
|
|
||||||
|
.. _`auth events selection`:
|
||||||
|
|
||||||
The ``auth_events`` field of a PDU identifies the set of events which give the
|
The ``auth_events`` field of a PDU identifies the set of events which give the
|
||||||
sender permission to send the event. The ``auth_events`` for the
|
sender permission to send the event. The ``auth_events`` for the
|
||||||
``m.room.create`` event in a room is empty; for other events, it should be the
|
``m.room.create`` event in a room is empty; for other events, it should be the
|
||||||
|
@ -315,8 +317,16 @@ following subset of the room state:
|
||||||
|
|
||||||
- The ``m.room.create`` event.
|
- The ``m.room.create`` event.
|
||||||
- The current ``m.room.power_levels`` event, if any.
|
- The current ``m.room.power_levels`` event, if any.
|
||||||
- The current ``m.room.join_rules`` event, if any.
|
|
||||||
- The sender's current ``m.room.member`` event, if any.
|
- The sender's current ``m.room.member`` event, if any.
|
||||||
|
- If type is ``m.room.member``:
|
||||||
|
|
||||||
|
- The target's current ``m.room.member`` event, if any.
|
||||||
|
- If ``membership`` is ``join`` or ``invite``, the current
|
||||||
|
``m.room.join_rules`` event, if any.
|
||||||
|
- If membership is ``invite`` and ``content`` contains a
|
||||||
|
``third_party_invite`` property, the current
|
||||||
|
``m.room.third_party_invite`` event with ``state_key`` matching
|
||||||
|
``content.third_party_invite.signed.token``, if any.
|
||||||
|
|
||||||
{{definition_ss_pdu}}
|
{{definition_ss_pdu}}
|
||||||
|
|
||||||
|
@ -358,42 +368,83 @@ be inserted. The types of state events that affect authorization are:
|
||||||
- ``m.room.member``
|
- ``m.room.member``
|
||||||
- ``m.room.join_rules``
|
- ``m.room.join_rules``
|
||||||
- ``m.room.power_levels``
|
- ``m.room.power_levels``
|
||||||
|
- ``m.room.third_party_invite``
|
||||||
Servers should not create new events that reference unauthorized events.
|
|
||||||
However, any event that does reference an unauthorized event is not itself
|
|
||||||
automatically considered unauthorized.
|
|
||||||
|
|
||||||
Unauthorized events that appear in the event graph do *not* have any effect on
|
|
||||||
the state of the room.
|
|
||||||
|
|
||||||
.. Note:: This is in contrast to redacted events which can still affect the
|
|
||||||
state of the room. For example, a redacted ``join`` event will still
|
|
||||||
result in the user being considered joined.
|
|
||||||
|
|
||||||
The rules are as follows:
|
The rules are as follows:
|
||||||
|
|
||||||
1. If type is ``m.room.create``, allow if and only if it has no
|
1. If type is ``m.room.create``:
|
||||||
previous events - *i.e.* it is the first event in the room.
|
|
||||||
|
|
||||||
2. If type is ``m.room.member``:
|
a. If it has any previous events, reject.
|
||||||
|
b. If the domain of the ``room_id`` does not match the domain of the
|
||||||
|
``sender``, reject.
|
||||||
|
c. If ``content.room_version`` is present and is not a recognised version,
|
||||||
|
reject.
|
||||||
|
d. If ``content`` has no ``creator`` field, reject.
|
||||||
|
e. Otherwise, allow.
|
||||||
|
|
||||||
a. If ``membership`` is ``join``:
|
#. Reject if event has ``auth_events`` that:
|
||||||
|
|
||||||
|
a. have duplicate entries for a given ``type`` and ``state_key`` pair
|
||||||
|
#. have entries whose ``type`` and ``state_key`` don't match those
|
||||||
|
specified by the `auth events selection`_ algorithm described above.
|
||||||
|
|
||||||
|
#. If event does not have a ``m.room.create`` in its ``auth_events``, reject.
|
||||||
|
|
||||||
|
#. If type is ``m.room.aliases``:
|
||||||
|
|
||||||
|
a. If event has no ``state_key``, reject.
|
||||||
|
b. If sender's domain doesn't matches ``state_key``, reject.
|
||||||
|
c. Otherwise, allow.
|
||||||
|
|
||||||
|
#. If type is ``m.room.member``:
|
||||||
|
|
||||||
|
a. If no ``state_key`` key or ``membership`` key in ``content``, reject.
|
||||||
|
|
||||||
|
#. If ``membership`` is ``join``:
|
||||||
|
|
||||||
i. If the only previous event is an ``m.room.create``
|
i. If the only previous event is an ``m.room.create``
|
||||||
and the ``state_key`` is the creator, allow.
|
and the ``state_key`` is the creator, allow.
|
||||||
|
|
||||||
#. If the ``sender`` does not match ``state_key``, reject.
|
#. If the ``sender`` does not match ``state_key``, reject.
|
||||||
|
|
||||||
#. If the user's current membership state is ``invite`` or ``join``,
|
#. If the ``sender`` is banned, reject.
|
||||||
allow.
|
|
||||||
|
#. If the ``join_rule`` is ``invite`` then allow if membership state
|
||||||
|
is ``invite`` or ``join``.
|
||||||
|
|
||||||
#. If the ``join_rule`` is ``public``, allow.
|
#. If the ``join_rule`` is ``public``, allow.
|
||||||
|
|
||||||
#. Otherwise, reject.
|
#. Otherwise, reject.
|
||||||
|
|
||||||
b. If ``membership`` is ``invite``:
|
#. If ``membership`` is ``invite``:
|
||||||
|
|
||||||
i. If the ``sender``'s current membership state is not ``join``, reject.
|
i. If ``content`` has ``third_party_invite`` key:
|
||||||
|
|
||||||
|
#. If *target user* is banned, reject.
|
||||||
|
|
||||||
|
#. If ``content.third_party_invite`` does not have a
|
||||||
|
``signed`` key, reject.
|
||||||
|
|
||||||
|
#. If ``signed`` does not have ``mxid`` and ``token`` keys, reject.
|
||||||
|
|
||||||
|
#. If ``mxid`` does not match ``state_key``, reject.
|
||||||
|
|
||||||
|
#. If there is no ``m.room.third_party_invite`` event in the
|
||||||
|
current room state with ``state_key`` matching ``token``, reject.
|
||||||
|
|
||||||
|
#. If ``sender`` does not match ``sender`` of the
|
||||||
|
``m.room.third_party_invite``, reject.
|
||||||
|
|
||||||
|
#. If any signature in ``signed`` matches any public key in the
|
||||||
|
``m.room.third_party_invite`` event, allow. The public keys are
|
||||||
|
in ``content`` of ``m.room.third_party_invite`` as:
|
||||||
|
|
||||||
|
#. A single public key in the ``public_key`` field.
|
||||||
|
#. A list of public keys in the ``public_keys`` field.
|
||||||
|
|
||||||
|
#. Otherwise, reject.
|
||||||
|
|
||||||
|
#. If the ``sender``'s current membership state is not ``join``, reject.
|
||||||
|
|
||||||
#. If *target user*'s current membership state is ``join`` or ``ban``,
|
#. If *target user*'s current membership state is ``join`` or ``ban``,
|
||||||
reject.
|
reject.
|
||||||
|
@ -403,7 +454,7 @@ The rules are as follows:
|
||||||
|
|
||||||
#. Otherwise, reject.
|
#. Otherwise, reject.
|
||||||
|
|
||||||
c. If ``membership`` is ``leave``:
|
#. If ``membership`` is ``leave``:
|
||||||
|
|
||||||
i. If the ``sender`` matches ``state_key``, allow if and only if that user's
|
i. If the ``sender`` matches ``state_key``, allow if and only if that user's
|
||||||
current membership state is ``invite`` or ``join``.
|
current membership state is ``invite`` or ``join``.
|
||||||
|
@ -419,7 +470,7 @@ The rules are as follows:
|
||||||
|
|
||||||
#. Otherwise, reject.
|
#. Otherwise, reject.
|
||||||
|
|
||||||
d. If ``membership`` is ``ban``:
|
#. If ``membership`` is ``ban``:
|
||||||
|
|
||||||
i. If the ``sender``'s current membership state is not ``join``, reject.
|
i. If the ``sender``'s current membership state is not ``join``, reject.
|
||||||
|
|
||||||
|
@ -429,18 +480,30 @@ The rules are as follows:
|
||||||
|
|
||||||
#. Otherwise, reject.
|
#. Otherwise, reject.
|
||||||
|
|
||||||
e. Otherwise, the membership is unknown. Reject.
|
#. Otherwise, the membership is unknown. Reject.
|
||||||
|
|
||||||
3. If the ``sender``'s current membership state is not ``join``, reject.
|
#. If the ``sender``'s current membership state is not ``join``, reject.
|
||||||
|
|
||||||
4. If the event type's *required power level* is greater than the ``sender``'s power
|
#. If type is ``m.room.third_party_invite``:
|
||||||
|
|
||||||
|
a. Allow if and only if ``sender``'s current power level is greater than
|
||||||
|
or equal to the *invite level*.
|
||||||
|
|
||||||
|
#. If the event type's *required power level* is greater than the ``sender``'s power
|
||||||
level, reject.
|
level, reject.
|
||||||
|
|
||||||
5. If type is ``m.room.power_levels``:
|
#. If the event has a ``state_key`` that starts with an ``@`` and does not match
|
||||||
|
the ``sender``, reject.
|
||||||
|
|
||||||
a. If there is no previous ``m.room.power_levels`` event in the room, allow.
|
#. If type is ``m.room.power_levels``:
|
||||||
|
|
||||||
b. For each of the keys ``users_default``, ``events_default``,
|
a. If ``users`` key in ``content`` is not a dictionary with keys that are
|
||||||
|
valid user IDs with values that are integers (or a string that is an
|
||||||
|
integer), reject.
|
||||||
|
|
||||||
|
#. If there is no previous ``m.room.power_levels`` event in the room, allow.
|
||||||
|
|
||||||
|
#. For each of the keys ``users_default``, ``events_default``,
|
||||||
``state_default``, ``ban``, ``redact``, ``kick``, ``invite``, as well as
|
``state_default``, ``ban``, ``redact``, ``kick``, ``invite``, as well as
|
||||||
each entry being changed under the ``events`` or ``users`` keys:
|
each entry being changed under the ``events`` or ``users`` keys:
|
||||||
|
|
||||||
|
@ -450,25 +513,25 @@ The rules are as follows:
|
||||||
#. If the new value is higher than the ``sender``'s current power level,
|
#. If the new value is higher than the ``sender``'s current power level,
|
||||||
reject.
|
reject.
|
||||||
|
|
||||||
c. For each entry being changed under the ``users`` key, other than the
|
#. For each entry being changed under the ``users`` key, other than the
|
||||||
``sender``'s own entry:
|
``sender``'s own entry:
|
||||||
|
|
||||||
i. If the current value is equal to the ``sender``'s current power level,
|
i. If the current value is equal to the ``sender``'s current power level,
|
||||||
reject.
|
reject.
|
||||||
|
|
||||||
d. Otherwise, allow.
|
#. Otherwise, allow.
|
||||||
|
|
||||||
6. If type is ``m.room.redaction``:
|
#. If type is ``m.room.redaction``:
|
||||||
|
|
||||||
a. If the ``sender``'s power level is greater than or equal to the *redact
|
a. If the ``sender``'s power level is greater than or equal to the *redact
|
||||||
level*, allow.
|
level*, allow.
|
||||||
|
|
||||||
#. If the ``sender`` of the event being redacted is the same as the
|
#. If the domain of the ``event_id`` of the event being redacted is the same
|
||||||
``sender`` of the ``m.room.redaction``, allow.
|
as the domain of the ``event_id`` of the ``m.room.redaction``, allow.
|
||||||
|
|
||||||
#. Otherwise, reject.
|
#. Otherwise, reject.
|
||||||
|
|
||||||
7. Otherwise, allow.
|
#. Otherwise, allow.
|
||||||
|
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
|
|
||||||
|
@ -482,9 +545,30 @@ The rules are as follows:
|
||||||
the kick *and* ban levels, *and* greater than the target user's power
|
the kick *and* ban levels, *and* greater than the target user's power
|
||||||
level.
|
level.
|
||||||
|
|
||||||
.. TODO-spec
|
|
||||||
|
|
||||||
I think there is some magic about 3pid invites too.
|
Rejection
|
||||||
|
+++++++++
|
||||||
|
|
||||||
|
If an event is rejected it should neither be relayed to clients nor be included
|
||||||
|
as a prev event in any new events generated by the server. Subsequent events
|
||||||
|
from other servers that reference rejected events should be allowed if they
|
||||||
|
still pass the auth rules. The state used in the checks should be calculated as
|
||||||
|
normal, except not updating with the rejected event where it is a state event.
|
||||||
|
|
||||||
|
If an event in an incoming transaction is rejected, this should not cause the
|
||||||
|
transaction request to be responded to with an error response.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
|
||||||
|
This means that events may be included in the room DAG even though they
|
||||||
|
should be rejected.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
|
||||||
|
This is in contrast to redacted events which can still affect the
|
||||||
|
state of the room. For example, a redacted ``join`` event will still
|
||||||
|
result in the user being considered joined.
|
||||||
|
|
||||||
|
|
||||||
Retrieving event authorization information
|
Retrieving event authorization information
|
||||||
++++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
@ -752,6 +836,10 @@ event to other servers in the room.
|
||||||
Third-party invites
|
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
|
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
|
ID to invite, they can do so using a third-party identifier (e.g. an e-mail or a
|
||||||
phone number).
|
phone number).
|
||||||
|
@ -822,7 +910,7 @@ identifier.
|
||||||
Public Room Directory
|
Public Room Directory
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
To compliment the `Client-Server API`_'s room directory, homeservers need a
|
To complement the `Client-Server API`_'s room directory, homeservers need a
|
||||||
way to query the public rooms for another server. This can be done by making
|
way to query the public rooms for another server. This can be done by making
|
||||||
a request to the ``/publicRooms`` endpoint for the server the room directory
|
a request to the ``/publicRooms`` endpoint for the server the room directory
|
||||||
should be retrieved for.
|
should be retrieved for.
|
||||||
|
@ -908,6 +996,19 @@ nothing else.
|
||||||
|
|
||||||
{{openid_ss_http_api}}
|
{{openid_ss_http_api}}
|
||||||
|
|
||||||
|
|
||||||
|
End-to-End Encryption
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
This section complements the `End-to-End Encryption module`_ of the Client-Server
|
||||||
|
API. For detailed information about end-to-end encryption, please see that module.
|
||||||
|
|
||||||
|
The APIs defined here are designed to be able to proxy much of the client's request
|
||||||
|
through to federation, and have the response also be proxied through to the client.
|
||||||
|
|
||||||
|
{{user_keys_ss_http_api}}
|
||||||
|
|
||||||
|
|
||||||
Send-to-device messaging
|
Send-to-device messaging
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -979,143 +1080,115 @@ Signing Events
|
||||||
Signing events is complicated by the fact that servers can choose to redact
|
Signing events is complicated by the fact that servers can choose to redact
|
||||||
non-essential parts of an event.
|
non-essential parts of an event.
|
||||||
|
|
||||||
Before signing the event, the ``unsigned`` and ``signature`` members are
|
Adding hashes and signatures to outgoing events
|
||||||
removed, it is encoded as `Canonical JSON`_, and then hashed using SHA-256. The
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
resulting hash is then stored in the event JSON in a ``hash`` object under a
|
|
||||||
``sha256`` key.
|
Before signing the event, the *content hash* of the event is calculated as
|
||||||
|
described below. The hash is encoded using `Unpadded Base64`_ and stored in the
|
||||||
|
event object, in a ``hashes`` object, under a ``sha256`` key.
|
||||||
|
|
||||||
|
The event object is then *redacted*, following the `redaction
|
||||||
|
algorithm`_. Finally it is signed as described in `Signing JSON`_, using the
|
||||||
|
server's signing key (see also `Retrieving server keys`_).
|
||||||
|
|
||||||
|
The signature is then copied back to the original event object.
|
||||||
|
|
||||||
|
See `Persistent Data Unit schema`_ for an example of a signed event.
|
||||||
|
|
||||||
|
|
||||||
|
Validating hashes and signatures on received events
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
When a server receives an event over federation from another server, the
|
||||||
|
receiving server should check the hashes and signatures on that event.
|
||||||
|
|
||||||
|
First the signature is checked. The event is redacted following the `redaction
|
||||||
|
algorithm`_, and the resultant object is checked for a signature from the
|
||||||
|
originating server, following the algorithm described in `Checking for a signature`_.
|
||||||
|
Note that this step should succeed whether we have been sent the full event or
|
||||||
|
a redacted copy.
|
||||||
|
|
||||||
|
If the signature is found to be valid, the expected content hash is calculated
|
||||||
|
as described below. The content hash in the ``hashes`` property of the received
|
||||||
|
event is base64-decoded, and the two are compared for equality.
|
||||||
|
|
||||||
|
If the hash check fails, then it is assumed that this is because we have only
|
||||||
|
been given a redacted version of the event. To enforce this, the receiving
|
||||||
|
server should use the redacted copy it calculated rather than the full copy it
|
||||||
|
received.
|
||||||
|
|
||||||
|
Calculating the content hash for an event
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The *content hash* of an event covers the complete event including the
|
||||||
|
*unredacted* contents. It is calculated as follows.
|
||||||
|
|
||||||
|
First, any existing ``unsigned``, ``signature``, and ``hashes`` members are
|
||||||
|
removed. The resulting object is then encoded as `Canonical JSON`_, and the
|
||||||
|
JSON is hashed using SHA-256.
|
||||||
|
|
||||||
|
|
||||||
|
Example code
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
def hash_event(event_json_object):
|
def hash_and_sign_event(event_object, signing_key, signing_name):
|
||||||
|
# First we need to hash the event object.
|
||||||
# Keys under "unsigned" can be modified by other servers.
|
content_hash = compute_content_hash(event_object)
|
||||||
# They are useful for conveying information like the age of an
|
event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)}
|
||||||
# event that will change in transit.
|
|
||||||
# Since they can be modifed we need to exclude them from the hash.
|
|
||||||
unsigned = event_json_object.pop("unsigned", None)
|
|
||||||
|
|
||||||
# Signatures will depend on the current value of the "hashes" key.
|
|
||||||
# We cannot add new hashes without invalidating existing signatures.
|
|
||||||
signatures = event_json_object.pop("signatures", None)
|
|
||||||
|
|
||||||
# The "hashes" key might contain multiple algorithms if we decide to
|
|
||||||
# migrate away from SHA-2. We don't want to include an existing hash
|
|
||||||
# output in our hash so we exclude the "hashes" dict from the hash.
|
|
||||||
hashes = event_json_object.pop("hashes", {})
|
|
||||||
|
|
||||||
# Encode the JSON using a canonical encoding so that we get the same
|
|
||||||
# bytes on every server for the same JSON object.
|
|
||||||
event_json_bytes = encode_canonical_json(event_json_bytes)
|
|
||||||
|
|
||||||
# Add the base64 encoded bytes of the hash to the "hashes" dict.
|
|
||||||
hashes["sha256"] = encode_base64(sha256(event_json_bytes).digest())
|
|
||||||
|
|
||||||
# Add the "hashes" dict back the event JSON under a "hashes" key.
|
|
||||||
event_json_object["hashes"] = hashes
|
|
||||||
if unsigned is not None:
|
|
||||||
event_json_object["unsigned"] = unsigned
|
|
||||||
return event_json_object
|
|
||||||
|
|
||||||
The event is then stripped of all non-essential keys both at the top level and
|
|
||||||
within the ``content`` object. Any top-level keys not in the following list
|
|
||||||
MUST be removed:
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
auth_events
|
|
||||||
depth
|
|
||||||
event_id
|
|
||||||
hashes
|
|
||||||
membership
|
|
||||||
origin
|
|
||||||
origin_server_ts
|
|
||||||
prev_events
|
|
||||||
prev_state
|
|
||||||
room_id
|
|
||||||
sender
|
|
||||||
signatures
|
|
||||||
state_key
|
|
||||||
type
|
|
||||||
|
|
||||||
A new ``content`` object is constructed for the resulting event that contains
|
|
||||||
only the essential keys of the original ``content`` object. If the original
|
|
||||||
event lacked a ``content`` object at all, a new empty JSON object is created
|
|
||||||
for it.
|
|
||||||
|
|
||||||
The keys that are considered essential for the ``content`` object depend on the
|
|
||||||
the ``type`` of the event. These are:
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
type is "m.room.aliases":
|
|
||||||
aliases
|
|
||||||
|
|
||||||
type is "m.room.create":
|
|
||||||
creator
|
|
||||||
|
|
||||||
type is "m.room.history_visibility":
|
|
||||||
history_visibility
|
|
||||||
|
|
||||||
type is "m.room.join_rules":
|
|
||||||
join_rule
|
|
||||||
|
|
||||||
type is "m.room.member":
|
|
||||||
membership
|
|
||||||
|
|
||||||
type is "m.room.power_levels":
|
|
||||||
ban
|
|
||||||
events
|
|
||||||
events_default
|
|
||||||
kick
|
|
||||||
redact
|
|
||||||
state_default
|
|
||||||
users
|
|
||||||
users_default
|
|
||||||
|
|
||||||
The resulting stripped object with the new ``content`` object and the original
|
|
||||||
``hashes`` key is then signed using the JSON signing algorithm outlined below:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
def sign_event(event_json_object, name, key):
|
|
||||||
|
|
||||||
# Make sure the event has a "hashes" key.
|
|
||||||
if "hashes" not in event_json_object:
|
|
||||||
event_json_object = hash_event(event_json_object)
|
|
||||||
|
|
||||||
# Strip all the keys that would be removed if the event was redacted.
|
# Strip all the keys that would be removed if the event was redacted.
|
||||||
# The hashes are not stripped and cover all the keys in the event.
|
# The hashes are not stripped and cover all the keys in the event.
|
||||||
# This means that we can tell if any of the non-essential keys are
|
# This means that we can tell if any of the non-essential keys are
|
||||||
# modified or removed.
|
# modified or removed.
|
||||||
stripped_json_object = strip_non_essential_keys(event_json_object)
|
stripped_object = strip_non_essential_keys(event_object)
|
||||||
|
|
||||||
# Sign the stripped JSON object. The signature only covers the
|
# Sign the stripped JSON object. The signature only covers the
|
||||||
# essential keys and the hashes. This means that we can check the
|
# essential keys and the hashes. This means that we can check the
|
||||||
# signature even if the event is redacted.
|
# signature even if the event is redacted.
|
||||||
signed_json_object = sign_json(stripped_json_object)
|
signed_object = sign_json(stripped_object, signing_key, signing_name)
|
||||||
|
|
||||||
# Copy the signatures from the stripped event to the original event.
|
# Copy the signatures from the stripped event to the original event.
|
||||||
event_json_object["signatures"] = signed_json_oject["signatures"]
|
event_object["signatures"] = signed_object["signatures"]
|
||||||
return event_json_object
|
|
||||||
|
|
||||||
Servers can then transmit the entire event or the event with the non-essential
|
def compute_content_hash(event_object):
|
||||||
keys removed. If the entire event is present, receiving servers can then check
|
# take a copy of the event before we remove any keys.
|
||||||
the event by computing the SHA-256 of the event, excluding the ``hash`` object.
|
event_object = dict(event_object)
|
||||||
If the keys have been redacted, then the ``hash`` object is included when
|
|
||||||
calculating the SHA-256 hash instead.
|
|
||||||
|
|
||||||
New hash functions can be introduced by adding additional keys to the ``hash``
|
# Keys under "unsigned" can be modified by other servers.
|
||||||
object. Since the ``hash`` object cannot be redacted a server shouldn't allow
|
# They are useful for conveying information like the age of an
|
||||||
too many hashes to be listed, otherwise a server might embed illict data within
|
# event that will change in transit.
|
||||||
the ``hash`` object. For similar reasons a server shouldn't allow hash values
|
# Since they can be modifed we need to exclude them from the hash.
|
||||||
that are too long.
|
event_object.pop("unsigned", None)
|
||||||
|
|
||||||
|
# Signatures will depend on the current value of the "hashes" key.
|
||||||
|
# We cannot add new hashes without invalidating existing signatures.
|
||||||
|
event_object.pop("signatures", None)
|
||||||
|
|
||||||
|
# The "hashes" key might contain multiple algorithms if we decide to
|
||||||
|
# migrate away from SHA-2. We don't want to include an existing hash
|
||||||
|
# output in our hash so we exclude the "hashes" dict from the hash.
|
||||||
|
event_object.pop("hashes", None)
|
||||||
|
|
||||||
|
# Encode the JSON using a canonical encoding so that we get the same
|
||||||
|
# bytes on every server for the same JSON object.
|
||||||
|
event_json_bytes = encode_canonical_json(event_object)
|
||||||
|
|
||||||
|
return hashlib.sha256(event_json_bytes)
|
||||||
|
|
||||||
.. TODO
|
.. TODO
|
||||||
[[TODO(markjh): We might want to specify a maximum number of keys for the
|
|
||||||
|
[[TODO(markjh): Since the ``hash`` object cannot be redacted a server
|
||||||
|
shouldn't allow too many hashes to be listed, otherwise a server might embed
|
||||||
|
illict data within the ``hash`` object.
|
||||||
|
|
||||||
|
We might want to specify a maximum number of keys for the
|
||||||
``hash`` and we might want to specify the maximum output size of a hash]]
|
``hash`` and we might want to specify the maximum output size of a hash]]
|
||||||
|
|
||||||
[[TODO(markjh) We might want to allow the server to omit the output of well
|
[[TODO(markjh) We might want to allow the server to omit the output of well
|
||||||
known hash functions like SHA-256 when none of the keys have been redacted]]
|
known hash functions like SHA-256 when none of the keys have been redacted]]
|
||||||
|
|
||||||
|
|
||||||
.. |/query/directory| replace:: ``/query/directory``
|
.. |/query/directory| replace:: ``/query/directory``
|
||||||
.. _/query/directory: #get-matrix-federation-v1-query-directory
|
.. _/query/directory: #get-matrix-federation-v1-query-directory
|
||||||
|
|
||||||
|
@ -1125,4 +1198,9 @@ that are too long.
|
||||||
.. _`Inviting to a room`: #inviting-to-a-room
|
.. _`Inviting to a room`: #inviting-to-a-room
|
||||||
.. _`Canonical JSON`: ../appendices.html#canonical-json
|
.. _`Canonical JSON`: ../appendices.html#canonical-json
|
||||||
.. _`Unpadded Base64`: ../appendices.html#unpadded-base64
|
.. _`Unpadded Base64`: ../appendices.html#unpadded-base64
|
||||||
.. _`Server ACLs`: ../client_server/unstable.html#module-server-acls
|
.. _`Server ACLs`: ../client_server/%CLIENT_RELEASE_LABEL%.html#module-server-acls
|
||||||
|
.. _`redaction algorithm`: ../client_server/%CLIENT_RELEASE_LABEL%.html#redactions
|
||||||
|
.. _`Signing JSON`: ../appendices.html#signing-json
|
||||||
|
.. _`Checking for a signature`: ../appendices.html#checking-for-a-signature
|
||||||
|
.. _`Device Management module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#device-management
|
||||||
|
.. _`End-to-End Encryption module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#end-to-end-encryption
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue