Add syntax highlighting

pull/977/head
Will 4 years ago
parent 6c6bd57ebf
commit ab64bda76d
No known key found for this signature in database
GPG Key ID: 385872BB265E8BF8

@ -87,19 +87,21 @@ Note
Float values are not permitted by this encoding. Float values are not permitted by this encoding.
import json ```py
import json
def canonical_json(value):
return json.dumps( def canonical_json(value):
value, return json.dumps(
# Encode code-points outside of ASCII as UTF-8 rather than \u escapes value,
ensure_ascii=False, # Encode code-points outside of ASCII as UTF-8 rather than \u escapes
# Remove unnecessary white space. ensure_ascii=False,
separators=(',',':'), # Remove unnecessary white space.
# Sort the keys of dictionaries. separators=(',',':'),
sort_keys=True, # Sort the keys of dictionaries.
# Encode the resulting Unicode as UTF-8 bytes. sort_keys=True,
).encode("UTF-8") # Encode the resulting Unicode as UTF-8 bytes.
).encode("UTF-8")
```
#### Grammar #### Grammar
@ -138,108 +140,144 @@ transformation code.
Given the following JSON object: Given the following JSON object:
{} ```json
{}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{} ```json
{}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"one": 1, {
"two": "Two" "one": 1,
} "two": "Two"
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"one":1,"two":"Two"} ```json
{"one":1,"two":"Two"}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"b": "2", {
"a": "1" "b": "2",
} "a": "1"
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"a":"1","b":"2"} ```json
{"a":"1","b":"2"}
```
Given the following JSON object: Given the following JSON object:
{"b":"2","a":"1"} ```json
{"b":"2","a":"1"}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"a":"1","b":"2"} ```json
{"a":"1","b":"2"}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"auth": { {
"success": true, "auth": {
"mxid": "@john.doe:example.com", "success": true,
"profile": { "mxid": "@john.doe:example.com",
"display_name": "John Doe", "profile": {
"three_pids": [ "display_name": "John Doe",
{ "three_pids": [
"medium": "email", {
"address": "john.doe@example.org" "medium": "email",
}, "address": "john.doe@example.org"
{ },
"medium": "msisdn", {
"address": "123456789" "medium": "msisdn",
} "address": "123456789"
] }
} ]
} }
} }
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}} ```json
{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"a": "日本語" {
} "a": "日本語"
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"a":"日本語"} ```json
{"a":"日本語"}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"本": 2, {
"日": 1 "本": 2,
} "日": 1
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"日":1,"本":2} ```json
{"日":1,"本":2}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"a": "\u65E5" {
} "a": "\u65E5"
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"a":"日"} ```json
{"a":"日"}
```
Given the following JSON object: Given the following JSON object:
{ ```json
"a": null {
} "a": null
}
```
The following canonical JSON should be produced: The following canonical JSON should be produced:
{"a":null} ```json
{"a":null}
```
### Signing Details ### Signing Details
@ -263,36 +301,40 @@ The `unsigned` object and the `signatures` object are not covered by the
signature. Therefore intermediate entities can add unsigned data such as signature. Therefore intermediate entities can add unsigned data such as
timestamps and additional signatures. timestamps and additional signatures.
{ ```json
"name": "example.org", {
"signing_keys": { "name": "example.org",
"ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ" "signing_keys": {
}, "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
"unsigned": { },
"age_ts": 922834800000 "unsigned": {
}, "age_ts": 922834800000
"signatures": { },
"example.org": { "signatures": {
"ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw" "example.org": {
} "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
} }
} }
}
def sign_json(json_object, signing_key, signing_name): ```
signatures = json_object.pop("signatures", {})
unsigned = json_object.pop("unsigned", None) ```py
def sign_json(json_object, signing_key, signing_name):
signed = signing_key.sign(encode_canonical_json(json_object)) signatures = json_object.pop("signatures", {})
signature_base64 = encode_base64(signed.signature) unsigned = json_object.pop("unsigned", None)
key_id = "%s:%s" % (signing_key.alg, signing_key.version) signed = signing_key.sign(encode_canonical_json(json_object))
signatures.setdefault(signing_name, {})[key_id] = signature_base64 signature_base64 = encode_base64(signed.signature)
json_object["signatures"] = signatures key_id = "%s:%s" % (signing_key.alg, signing_key.version)
if unsigned is not None: signatures.setdefault(signing_name, {})[key_id] = signature_base64
json_object["unsigned"] = unsigned
json_object["signatures"] = signatures
return json_object if unsigned is not None:
json_object["unsigned"] = unsigned
return json_object
```
### Checking for a Signature ### Checking for a Signature
@ -872,122 +914,138 @@ In each case, the server name and key ID are as follows:
Given an empty JSON object: Given an empty JSON object:
{} ```json
{}
```
The JSON signing algorithm should emit the following signed data: The JSON signing algorithm should emit the following signed data:
{ ```json
"signatures": { {
"domain": { "signatures": {
"ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ" "domain": {
} "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
} }
} }
}
```
Given the following JSON object with data values in it: Given the following JSON object with data values in it:
{ ```json
"one": 1, {
"two": "Two" "one": 1,
} "two": "Two"
}
```
The JSON signing algorithm should emit the following signed JSON: The JSON signing algorithm should emit the following signed JSON:
{ ```json
"one": 1, {
"signatures": { "one": 1,
"domain": { "signatures": {
"ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw" "domain": {
} "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"
}, }
"two": "Two" },
} "two": "Two"
}
```
### Event Signing ### Event Signing
Given the following minimally-sized event: Given the following minimally-sized event:
{ ```json
"room_id": "!x:domain", {
"sender": "@a:domain", "room_id": "!x:domain",
"origin": "domain", "sender": "@a:domain",
"origin_server_ts": 1000000, "origin": "domain",
"signatures": {}, "origin_server_ts": 1000000,
"hashes": {}, "signatures": {},
"type": "X", "hashes": {},
"content": {}, "type": "X",
"prev_events": [], "content": {},
"auth_events": [], "prev_events": [],
"depth": 3, "auth_events": [],
"unsigned": { "depth": 3,
"age_ts": 1000000 "unsigned": {
} "age_ts": 1000000
} }
}
```
The event signing algorithm should emit the following signed event: The event signing algorithm should emit the following signed event:
{ ```json
"auth_events": [], {
"content": {}, "auth_events": [],
"depth": 3, "content": {},
"hashes": { "depth": 3,
"sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos" "hashes": {
}, "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
"origin": "domain", },
"origin_server_ts": 1000000, "origin": "domain",
"prev_events": [], "origin_server_ts": 1000000,
"room_id": "!x:domain", "prev_events": [],
"sender": "@a:domain", "room_id": "!x:domain",
"signatures": { "sender": "@a:domain",
"domain": { "signatures": {
"ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg" "domain": {
} "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
},
"type": "X",
"unsigned": {
"age_ts": 1000000
} }
},
"type": "X",
"unsigned": {
"age_ts": 1000000
} }
}
```
Given the following event containing redactable content: Given the following event containing redactable content:
{ ```json
"content": { {
"body": "Here is the message content" "content": {
}, "body": "Here is the message content"
"event_id": "$0:domain", },
"origin": "domain", "event_id": "$0:domain",
"origin_server_ts": 1000000, "origin": "domain",
"type": "m.room.message", "origin_server_ts": 1000000,
"room_id": "!r:domain", "type": "m.room.message",
"sender": "@u:domain", "room_id": "!r:domain",
"signatures": {}, "sender": "@u:domain",
"unsigned": { "signatures": {},
"age_ts": 1000000 "unsigned": {
} "age_ts": 1000000
} }
}
```
The event signing algorithm should emit the following signed event: The event signing algorithm should emit the following signed event:
{ ```json
"content": { {
"body": "Here is the message content" "content": {
}, "body": "Here is the message content"
"event_id": "$0:domain", },
"hashes": { "event_id": "$0:domain",
"sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g" "hashes": {
}, "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"
"origin": "domain", },
"origin_server_ts": 1000000, "origin": "domain",
"type": "m.room.message", "origin_server_ts": 1000000,
"room_id": "!r:domain", "type": "m.room.message",
"sender": "@u:domain", "room_id": "!r:domain",
"signatures": { "sender": "@u:domain",
"domain": { "signatures": {
"ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA" "domain": {
} "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA"
},
"unsigned": {
"age_ts": 1000000
} }
},
"unsigned": {
"age_ts": 1000000
} }
}
```

@ -71,10 +71,12 @@ inconsistency.
Any errors which occur at the Matrix API level MUST return a "standard Any errors which occur at the Matrix API level MUST return a "standard
error response". This is a JSON object which looks like: error response". This is a JSON object which looks like:
{ ```json
"errcode": "<error code>", {
"error": "<error message>" "errcode": "<error code>",
} "error": "<error message>"
}
```
The `error` string will be a human-readable error message, usually a The `error` string will be a human-readable error message, usually a
sentence explaining what went wrong. The `errcode` string will be a sentence explaining what went wrong. The `errcode` string will be a
@ -439,22 +441,24 @@ homeserver returns an HTTP 401 response, with a JSON body, as follows:
HTTP/1.1 401 Unauthorized HTTP/1.1 401 Unauthorized
Content-Type: application/json Content-Type: application/json
```json
{
"flows": [
{ {
"flows": [ "stages": [ "example.type.foo", "example.type.bar" ]
{ },
"stages": [ "example.type.foo", "example.type.bar" ] {
}, "stages": [ "example.type.foo", "example.type.baz" ]
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
} }
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
}
```
In addition to the `flows`, this object contains some extra information: In addition to the `flows`, this object contains some extra information:
@ -482,15 +486,17 @@ type `example.type.foo`, it might submit something like this:
POST /_matrix/client/r0/endpoint HTTP/1.1 POST /_matrix/client/r0/endpoint HTTP/1.1
Content-Type: application/json Content-Type: application/json
{ ```json
"a_request_parameter": "something", {
"another_request_parameter": "something else", "a_request_parameter": "something",
"auth": { "another_request_parameter": "something else",
"type": "example.type.foo", "auth": {
"session": "xxxxxx", "type": "example.type.foo",
"example_credential": "verypoorsharedsecret" "session": "xxxxxx",
} "example_credential": "verypoorsharedsecret"
} }
}
```
If the homeserver deems the authentication attempt to be successful but If the homeserver deems the authentication attempt to be successful but
still requires more stages to be completed, it returns HTTP status 401 still requires more stages to be completed, it returns HTTP status 401
@ -501,23 +507,25 @@ client has completed successfully:
HTTP/1.1 401 Unauthorized HTTP/1.1 401 Unauthorized
Content-Type: application/json Content-Type: application/json
```json
{
"completed": [ "example.type.foo" ],
"flows": [
{ {
"completed": [ "example.type.foo" ], "stages": [ "example.type.foo", "example.type.bar" ]
"flows": [ },
{ {
"stages": [ "example.type.foo", "example.type.bar" ] "stages": [ "example.type.foo", "example.type.baz" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
} }
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
}
```
Individual stages may require more than one request to complete, in Individual stages may require more than one request to complete, in
which case the response will be as if the request was unauthenticated which case the response will be as if the request was unauthenticated
@ -531,25 +539,27 @@ status 401 response as above, with the addition of the standard
HTTP/1.1 401 Unauthorized HTTP/1.1 401 Unauthorized
Content-Type: application/json Content-Type: application/json
```json
{
"errcode": "M_FORBIDDEN",
"error": "Invalid password",
"completed": [ "example.type.foo" ],
"flows": [
{ {
"errcode": "M_FORBIDDEN", "stages": [ "example.type.foo", "example.type.bar" ]
"error": "Invalid password", },
"completed": [ "example.type.foo" ], {
"flows": [ "stages": [ "example.type.foo", "example.type.baz" ]
{
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
} }
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
}
```
If the request fails for a reason other than authentication, the server If the request fails for a reason other than authentication, the server
returns an error message in the standard format. For example: returns an error message in the standard format. For example:
@ -557,10 +567,12 @@ returns an error message in the standard format. For example:
HTTP/1.1 400 Bad request HTTP/1.1 400 Bad request
Content-Type: application/json Content-Type: application/json
{ ```json
"errcode": "M_EXAMPLE_ERROR", {
"error": "Something was wrong" "errcode": "M_EXAMPLE_ERROR",
} "error": "Something was wrong"
}
```
If the client has completed all stages of a flow, the homeserver If the client has completed all stages of a flow, the homeserver
performs the API call and returns the result as normal. Completed stages performs the API call and returns the result as normal. Completed stages
@ -641,14 +653,16 @@ plain-text.
To use this authentication type, clients should submit an auth dict as To use this authentication type, clients should submit an auth dict as
follows: follows:
{ ```
"type": "m.login.password", {
"identifier": { "type": "m.login.password",
... "identifier": {
}, ...
"password": "<password>", },
"session": "<session ID>" "password": "<password>",
} "session": "<session ID>"
}
```
where the `identifier` property is a user identifier object, as where the `identifier` property is a user identifier object, as
described in [Identifier types](#identifier-types). described in [Identifier types](#identifier-types).
@ -656,30 +670,34 @@ described in [Identifier types](#identifier-types).
For example, to authenticate using the user's Matrix ID, clients would For example, to authenticate using the user's Matrix ID, clients would
submit: submit:
{ ```json
"type": "m.login.password", {
"identifier": { "type": "m.login.password",
"type": "m.id.user", "identifier": {
"user": "<user_id or user localpart>" "type": "m.id.user",
}, "user": "<user_id or user localpart>"
"password": "<password>", },
"session": "<session ID>" "password": "<password>",
} "session": "<session ID>"
}
```
Alternatively reply using a 3PID bound to the user's account on the Alternatively reply using a 3PID bound to the user's account on the
homeserver using the `/account/3pid`\_ API rather than giving the `user` homeserver using the `/account/3pid`\_ API rather than giving the `user`
explicitly as follows: explicitly as follows:
{ ```json
"type": "m.login.password", {
"identifier": { "type": "m.login.password",
"type": "m.id.thirdparty", "identifier": {
"medium": "<The medium of the third party identifier.>", "type": "m.id.thirdparty",
"address": "<The third party address of the user>" "medium": "<The medium of the third party identifier.>",
}, "address": "<The third party address of the user>"
"password": "<password>", },
"session": "<session ID>" "password": "<password>",
} "session": "<session ID>"
}
```
In the case that the homeserver does not know about the supplied 3PID, In the case that the homeserver does not know about the supplied 3PID,
the homeserver must respond with 403 Forbidden. the homeserver must respond with 403 Forbidden.
@ -695,11 +713,13 @@ The user completes a Google ReCaptcha 2.0 challenge
To use this authentication type, clients should submit an auth dict as To use this authentication type, clients should submit an auth dict as
follows: follows:
{ ```json
"type": "m.login.recaptcha", {
"response": "<captcha response>", "type": "m.login.recaptcha",
"session": "<session ID>" "response": "<captcha response>",
} "session": "<session ID>"
}
```
#### Single Sign-On #### Single Sign-On
@ -730,18 +750,20 @@ information should be submitted to the homeserver.
To use this authentication type, clients should submit an auth dict as To use this authentication type, clients should submit an auth dict as
follows: follows:
```json
{
"type": "m.login.email.identity",
"threepidCreds": [
{ {
"type": "m.login.email.identity", "sid": "<identity server session id>",
"threepidCreds": [ "client_secret": "<identity server client secret>",
{ "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"sid": "<identity server session id>", "id_access_token": "<access token previously registered with the identity server>"
"client_secret": "<identity server client secret>",
"id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"id_access_token": "<access token previously registered with the identity server>"
}
],
"session": "<session ID>"
} }
],
"session": "<session ID>"
}
```
Note that `id_server` (and therefore `id_access_token`) is optional if Note that `id_server` (and therefore `id_access_token`) is optional if
the `/requestToken` request did not include them. the `/requestToken` request did not include them.
@ -762,18 +784,20 @@ information should be submitted to the homeserver.
To use this authentication type, clients should submit an auth dict as To use this authentication type, clients should submit an auth dict as
follows: follows:
```json
{
"type": "m.login.msisdn",
"threepidCreds": [
{ {
"type": "m.login.msisdn", "sid": "<identity server session id>",
"threepidCreds": [ "client_secret": "<identity server client secret>",
{ "id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"sid": "<identity server session id>", "id_access_token": "<access token previously registered with the identity server>"
"client_secret": "<identity server client secret>",
"id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"id_access_token": "<access token previously registered with the identity server>"
}
],
"session": "<session ID>"
} }
],
"session": "<session ID>"
}
```
Note that `id_server` (and therefore `id_access_token`) is optional if Note that `id_server` (and therefore `id_access_token`) is optional if
the `/requestToken` request did not include them. the `/requestToken` request did not include them.
@ -798,10 +822,12 @@ server can instead send flows `m.login.recaptcha, m.login.dummy` and
To use this authentication type, clients should submit an auth dict with To use this authentication type, clients should submit an auth dict with
just the type and session, if provided: just the type and session, if provided:
{ ```json
"type": "m.login.dummy", {
"session": "<session ID>" "type": "m.login.dummy",
} "session": "<session ID>"
}
```
##### Fallback ##### Fallback
@ -820,11 +846,13 @@ This MUST return an HTML page which can perform this authentication
stage. This page must use the following JavaScript when the stage. This page must use the following JavaScript when the
authentication has been completed: authentication has been completed:
if (window.onAuthDone) { ```js
window.onAuthDone(); if (window.onAuthDone) {
} else if (window.opener && window.opener.postMessage) { window.onAuthDone();
window.opener.postMessage("authDone", "*"); } else if (window.opener && window.opener.postMessage) {
} window.opener.postMessage("authDone", "*");
}
```
This allows the client to either arrange for the global function This allows the client to either arrange for the global function
`onAuthDone` to be defined in an embedded browser, or to use the HTML5 `onAuthDone` to be defined in an embedded browser, or to use the HTML5
@ -836,64 +864,67 @@ Once a client receives the notification that the authentication stage
has been completed, it should resubmit the request with an auth dict has been completed, it should resubmit the request with an auth dict
with just the session ID: with just the session ID:
{ ```json
"session": "<session ID>" {
} "session": "<session ID>"
}
```
#### Example #### Example
A client webapp might use the following JavaScript to open a popup A client webapp might use the following JavaScript to open a popup
window which will handle unknown login types: window which will handle unknown login types:
/** ```js
* Arguments: /**
* homeserverUrl: the base url of the homeserver (e.g. "https://matrix.org") * Arguments:
* * homeserverUrl: the base url of the homeserver (e.g. "https://matrix.org")
* apiEndpoint: the API endpoint being used (e.g. *
* "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password") * apiEndpoint: the API endpoint being used (e.g.
* * "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password")
* loginType: the loginType being attempted (e.g. "m.login.recaptcha") *
* * loginType: the loginType being attempted (e.g. "m.login.recaptcha")
* sessionID: the session ID given by the homeserver in earlier requests *
* * sessionID: the session ID given by the homeserver in earlier requests
* onComplete: a callback which will be called with the results of the request *
*/ * onComplete: a callback which will be called with the results of the request
function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) { */
var popupWindow; function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) {
var popupWindow;
var eventListener = function(ev) {
// check it's the right message from the right place. var eventListener = function(ev) {
if (ev.data !== "authDone" || ev.origin !== homeserverUrl) { // check it's the right message from the right place.
return; if (ev.data !== "authDone" || ev.origin !== homeserverUrl) {
} return;
}
// close the popup
popupWindow.close(); // close the popup
window.removeEventListener("message", eventListener); popupWindow.close();
window.removeEventListener("message", eventListener);
// repeat the request
var requestBody = { // repeat the request
auth: { var requestBody = {
session: sessionID, auth: {
}, session: sessionID,
}; },
request({
method:'POST', url:apiEndpoint, json:requestBody,
}, onComplete);
}; };
window.addEventListener("message", eventListener); request({
method:'POST', url:apiEndpoint, json:requestBody,
}, onComplete);
};
var url = homeserverUrl + window.addEventListener("message", eventListener);
"/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" +
encodeURIComponent(loginType) +
"/fallback/web?session=" +
encodeURIComponent(sessionID);
var url = homeserverUrl +
"/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" +
encodeURIComponent(loginType) +
"/fallback/web?session=" +
encodeURIComponent(sessionID);
popupWindow = window.open(url); popupWindow = window.open(url);
} }
```
##### Identifier types ##### Identifier types
@ -920,10 +951,12 @@ A client can identify a user using their Matrix ID. This can either be
the fully qualified Matrix user ID, or just the localpart of the user the fully qualified Matrix user ID, or just the localpart of the user
ID. ID.
"identifier": { ```json
"type": "m.id.user", "identifier": {
"user": "<user_id or user localpart>" "type": "m.id.user",
} "user": "<user_id or user localpart>"
}
```
#### Third-party ID #### Third-party ID
@ -940,11 +973,13 @@ using the `/account/3pid`\_ API. See the [3PID
Types](../appendices.html#pid-types) Appendix for a list of Third-party Types](../appendices.html#pid-types) Appendix for a list of Third-party
ID media. ID media.
"identifier": { ```json
"type": "m.id.thirdparty", "identifier": {
"medium": "<The medium of the third party identifier>", "type": "m.id.thirdparty",
"address": "<The canonicalised third party address of the user>" "medium": "<The medium of the third party identifier>",
} "address": "<The canonicalised third party address of the user>"
}
```
#### Phone number #### Phone number
@ -962,11 +997,13 @@ If the client wishes to canonicalise the phone number, then it can use
the `m.id.thirdparty` identifier type with a `medium` of `msisdn` the `m.id.thirdparty` identifier type with a `medium` of `msisdn`
instead. instead.
"identifier": { ```json
"type": "m.id.phone", "identifier": {
"country": "<The country that the phone number is from>", "type": "m.id.phone",
"phone": "<The phone number>" "country": "<The country that the phone number is from>",
} "phone": "<The phone number>"
}
```
The `country` is the two-letter uppercase ISO-3166-1 alpha-2 country The `country` is the two-letter uppercase ISO-3166-1 alpha-2 country
code that the number in `phone` should be parsed as if it were dialled code that the number in `phone` should be parsed as if it were dialled
@ -983,27 +1020,31 @@ API](#user-interactive-authentication-api).
For a simple username/password login, clients should submit a `/login` For a simple username/password login, clients should submit a `/login`
request as follows: request as follows:
{ ```json
"type": "m.login.password", {
"identifier": { "type": "m.login.password",
"type": "m.id.user", "identifier": {
"user": "<user_id or user localpart>" "type": "m.id.user",
}, "user": "<user_id or user localpart>"
"password": "<password>" },
} "password": "<password>"
}
```
Alternatively, a client can use a 3PID bound to the user's account on Alternatively, a client can use a 3PID bound to the user's account on
the homeserver using the `/account/3pid`\_ API rather than giving the the homeserver using the `/account/3pid`\_ API rather than giving the
`user` explicitly, as follows: `user` explicitly, as follows:
{ ```json
"type": "m.login.password", {
"identifier": { "type": "m.login.password",
"medium": "<The medium of the third party identifier>", "identifier": {
"address": "<The canonicalised third party address of the user>" "medium": "<The medium of the third party identifier>",
}, "address": "<The canonicalised third party address of the user>"
"password": "<password>" },
} "password": "<password>"
}
```
In the case that the homeserver does not know about the supplied 3PID, In the case that the homeserver does not know about the supplied 3PID,
the homeserver must respond with `403 Forbidden`. the homeserver must respond with `403 Forbidden`.
@ -1011,10 +1052,12 @@ the homeserver must respond with `403 Forbidden`.
To log in using a login token, clients should submit a `/login` request To log in using a login token, clients should submit a `/login` request
as follows: as follows:
{ ```json
"type": "m.login.token", {
"token": "<login token>" "type": "m.login.token",
} "token": "<login token>"
}
```
As with [token-based]() interactive login, the `token` must encode the As with [token-based]() interactive login, the `token` must encode the
user ID. In the case that the token is not valid, the homeserver must user ID. In the case that the token is not valid, the homeserver must
@ -1166,13 +1209,15 @@ to change their password.
An example of the capability API's response for this capability is: An example of the capability API's response for this capability is:
{ ```json
"capabilities": { {
"m.change_password": { "capabilities": {
"enabled": false "m.change_password": {
} "enabled": false
}
} }
}
}
```
### `m.room_versions` capability ### `m.room_versions` capability
@ -1183,19 +1228,21 @@ upgrade their rooms.
An example of the capability API's response for this capability is: An example of the capability API's response for this capability is:
{ ```json
"capabilities": { {
"m.room_versions": { "capabilities": {
"default": "1", "m.room_versions": {
"available": { "default": "1",
"1": "stable", "available": {
"2": "stable", "1": "stable",
"3": "unstable", "2": "stable",
"custom-version": "unstable" "3": "unstable",
} "custom-version": "unstable"
}
} }
} }
}
}
```
This capability mirrors the same restrictions of [room This capability mirrors the same restrictions of [room
versions](../index.html#room-versions) to describe which versions are versions](../index.html#room-versions) to describe which versions are
@ -1626,36 +1673,48 @@ There are several APIs provided to `GET` events for a room:
Valid requests look like: Valid requests look like:
PUT /rooms/!roomid:domain/state/m.example.event ```
{ "key" : "without a state key" } PUT /rooms/!roomid:domain/state/m.example.event
{ "key" : "without a state key" }
PUT /rooms/!roomid:domain/state/m.another.example.event/foo ```
{ "key" : "with 'foo' as the state key" } ```
PUT /rooms/!roomid:domain/state/m.another.example.event/foo
{ "key" : "with 'foo' as the state key" }
```
In contrast, these requests are invalid: In contrast, these requests are invalid:
POST /rooms/!roomid:domain/state/m.example.event/ ```
{ "key" : "cannot use POST here" } POST /rooms/!roomid:domain/state/m.example.event/
{ "key" : "cannot use POST here" }
PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11 ```
{ "key" : "txnIds are not supported" } ```
PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
{ "key" : "txnIds are not supported" }
```
Care should be taken to avoid setting the wrong `state key`: Care should be taken to avoid setting the wrong `state key`:
PUT /rooms/!roomid:domain/state/m.another.example.event/11 ```
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" } PUT /rooms/!roomid:domain/state/m.another.example.event/11
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }
```
The `state_key` is often used to store state about individual users, by The `state_key` is often used to store state about individual users, by
using the user ID as the `state_key` value. For example: using the user ID as the `state_key` value. For example:
PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org ```
{ "animal" : "cat", "reason": "fluffy" } PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org
{ "animal" : "cat", "reason": "fluffy" }
```
In some cases, there may be no need for a `state_key`, so it can be In some cases, there may be no need for a `state_key`, so it can be
omitted: omitted:
PUT /rooms/!roomid:domain/state/m.room.bgd.color ```
{ "color": "red", "hex": "#ff0000" } PUT /rooms/!roomid:domain/state/m.room.bgd.color
{ "color": "red", "hex": "#ff0000" }
```
{{room\_send\_cs\_http\_api}} {{room\_send\_cs\_http\_api}}
@ -1872,19 +1931,23 @@ someone, the user performing the ban MUST have the required power level.
To ban a user, a request should be made to `/rooms/<room_id>/ban`\_ To ban a user, a request should be made to `/rooms/<room_id>/ban`\_
with: with:
{ ```json
"user_id": "<user id to ban>" {
"reason": "string: <reason for the ban>" "user_id": "<user id to ban>",
} "reason": "string: <reason for the ban>"
}
````
Banning a user adjusts the banned member's membership state to `ban`. Banning a user adjusts the banned member's membership state to `ban`.
Like with other membership changes, a user can directly adjust the Like with other membership changes, a user can directly adjust the
target member's state, by making a request to target member's state, by making a request to
`/rooms/<room id>/state/m.room.member/<user id>`: `/rooms/<room id>/state/m.room.member/<user id>`:
{ ```json
"membership": "ban" {
} "membership": "ban"
}
```
A user must be explicitly unbanned with a request to A user must be explicitly unbanned with a request to
`/rooms/<room_id>/unban`\_ before they can re-join the room or be `/rooms/<room_id>/unban`\_ before they can re-join the room or be
@ -1938,11 +2001,13 @@ Homeservers SHOULD implement rate limiting to reduce the risk of being
overloaded. If a request is refused due to rate limiting, it should overloaded. If a request is refused due to rate limiting, it should
return a standard error response of the form: return a standard error response of the form:
{ ```json
"errcode": "M_LIMIT_EXCEEDED", {
"error": "string", "errcode": "M_LIMIT_EXCEEDED",
"retry_after_ms": integer (optional) "error": "string",
} "retry_after_ms": integer (optional)
}
```
The `retry_after_ms` key SHOULD be included to tell the client how long The `retry_after_ms` key SHOULD be included to tell the client how long
they have to wait in milliseconds before they can try again. they have to wait in milliseconds before they can try again.

@ -99,14 +99,16 @@ with the following properties:
Example: Example:
{ ```json
"key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8", {
"signatures": { "key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8",
"@user:example.com": { "signatures": {
"ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ" "@user:example.com": {
} "ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ"
}
} }
}
}
```
##### Device keys ##### Device keys
@ -1239,22 +1241,24 @@ keys in [Server-side key backups](#server-side-key-backups) but adds the
Example: Example:
[ ```
{ [
"algorithm": "m.megolm.v1.aes-sha2", {
"forwarding_curve25519_key_chain": [ "algorithm": "m.megolm.v1.aes-sha2",
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" "forwarding_curve25519_key_chain": [
], "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
"room_id": "!Cuyf34gef24t:localhost", ],
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", "room_id": "!Cuyf34gef24t:localhost",
"sender_claimed_keys": { "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"ed25519": "<device ed25519 identity key>", "sender_claimed_keys": {
}, "ed25519": "<device ed25519 identity key>",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
}, },
... "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
] "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
},
...
]
```
#### Messaging Algorithms #### Messaging Algorithms
@ -1297,19 +1301,21 @@ device key, and must publish Curve25519 one-time keys.
An event encrypted using Olm has the following format: An event encrypted using Olm has the following format:
{ ```json
"type": "m.room.encrypted", {
"content": { "type": "m.room.encrypted",
"algorithm": "m.olm.v1.curve25519-aes-sha2", "content": {
"sender_key": "<sender_curve25519_key>", "algorithm": "m.olm.v1.curve25519-aes-sha2",
"ciphertext": { "sender_key": "<sender_curve25519_key>",
"<device_curve25519_key>": { "ciphertext": {
"type": 0, "<device_curve25519_key>": {
"body": "<encrypted_payload_base_64>" "type": 0,
} "body": "<encrypted_payload_base_64>"
}
} }
} }
}
}
```
`ciphertext` is a mapping from device Curve25519 key to an encrypted `ciphertext` is a mapping from device Curve25519 key to an encrypted
payload for that device. `body` is a Base64-encoded Olm message body. payload for that device. `body` is a Base64-encoded Olm message body.
@ -1334,18 +1340,20 @@ message.
The plaintext payload is of the form: The plaintext payload is of the form:
{ ```json
"type": "<type of the plaintext event>", {
"content": "<content for the plaintext event>", "type": "<type of the plaintext event>",
"sender": "<sender_user_id>", "content": "<content for the plaintext event>",
"recipient": "<recipient_user_id>", "sender": "<sender_user_id>",
"recipient_keys": { "recipient": "<recipient_user_id>",
"ed25519": "<our_ed25519_key>" "recipient_keys": {
}, "ed25519": "<our_ed25519_key>"
"keys": { },
"ed25519": "<sender_ed25519_key>" "keys": {
} "ed25519": "<sender_ed25519_key>"
} }
}
```
The type and content of the plaintext message event are given in the The type and content of the plaintext message event are given in the
payload. payload.
@ -1418,25 +1426,29 @@ Devices that support Megolm must support Olm, and include
An event encrypted using Megolm has the following format: An event encrypted using Megolm has the following format:
{ ```json
"type": "m.room.encrypted", {
"content": { "type": "m.room.encrypted",
"algorithm": "m.megolm.v1.aes-sha2", "content": {
"sender_key": "<sender_curve25519_key>", "algorithm": "m.megolm.v1.aes-sha2",
"device_id": "<sender_device_id>", "sender_key": "<sender_curve25519_key>",
"session_id": "<outbound_group_session_id>", "device_id": "<sender_device_id>",
"ciphertext": "<encrypted_payload_base_64>" "session_id": "<outbound_group_session_id>",
} "ciphertext": "<encrypted_payload_base_64>"
} }
}
```
The encrypted payload can contain any message event. The plaintext is of The encrypted payload can contain any message event. The plaintext is of
the form: the form:
{ ```json
"type": "<event_type>", {
"content": "<event_content>", "type": "<event_type>",
"room_id": "<the room_id>" "content": "<event_content>",
} "room_id": "<the room_id>"
}
```
We include the room ID in the payload, because otherwise the homeserver We include the room ID in the payload, because otherwise the homeserver
would be able to change the room a message was sent in. would be able to change the room a message was sent in.
@ -1554,22 +1566,24 @@ already shared a room.
Example response: Example response:
{ ```json
"next_batch": "s72595_4483_1934", {
"rooms": {"leave": {}, "join": {}, "invite": {}}, "next_batch": "s72595_4483_1934",
"device_lists": { "rooms": {"leave": {}, "join": {}, "invite": {}},
"changed": [ "device_lists": {
"@alice:example.com", "changed": [
], "@alice:example.com",
"left": [ ],
"@bob:example.com", "left": [
], "@bob:example.com",
}, ],
"device_one_time_keys_count": { },
"curve25519": 10, "device_one_time_keys_count": {
"signed_curve25519": 20 "curve25519": 10,
} "signed_curve25519": 20
} }
}
```
#### Reporting that decryption keys are withheld #### Reporting that decryption keys are withheld

@ -332,21 +332,23 @@ a rich reply, infinitely.
An `m.in_reply_to` relationship looks like the following: An `m.in_reply_to` relationship looks like the following:
{ ```
... {
"type": "m.room.message", ...
"content": { "type": "m.room.message",
"msgtype": "m.text", "content": {
"body": "<body including fallback>", "msgtype": "m.text",
"format": "org.matrix.custom.html", "body": "<body including fallback>",
"formatted_body": "<HTML including fallback>", "format": "org.matrix.custom.html",
"m.relates_to": { "formatted_body": "<HTML including fallback>",
"m.in_reply_to": { "m.relates_to": {
"event_id": "$another:event.com" "m.in_reply_to": {
} "event_id": "$another:event.com"
}
} }
} }
}
}
```
####### Fallbacks and event representation ####### Fallbacks and event representation

@ -18,12 +18,14 @@ Mentions apply only to [m.room.message]() events where the `msgtype` is
To make a mention, reference the entity being mentioned in the To make a mention, reference the entity being mentioned in the
`formatted_body` using an anchor, like so: `formatted_body` using an anchor, like so:
{ ```json
"body": "Hello Alice!", {
"msgtype": "m.text", "body": "Hello Alice!",
"format": "org.matrix.custom.html", "msgtype": "m.text",
"formatted_body": "Hello <a href='https://matrix.to/#/@alice:example.org'>Alice</a>!" "format": "org.matrix.custom.html",
} "formatted_body": "Hello <a href='https://matrix.to/#/@alice:example.org'>Alice</a>!"
}
```
#### Client behaviour #### Client behaviour

@ -282,15 +282,17 @@ user. By default this rule is disabled.
Definition Definition
{ ```json
"rule_id": ".m.rule.master", {
"default": true, "rule_id": ".m.rule.master",
"enabled": false, "default": true,
"conditions": [], "enabled": false,
"actions": [ "conditions": [],
"dont_notify" "actions": [
] "dont_notify"
} ]
}
```
######## `.m.rule.suppress_notices` ######## `.m.rule.suppress_notices`
@ -298,21 +300,23 @@ Matches messages with a `msgtype` of `notice`.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.suppress_notices", {
"default": true, "rule_id": ".m.rule.suppress_notices",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "event_match", {
"key": "content.msgtype", "kind": "event_match",
"pattern": "m.notice", "key": "content.msgtype",
} "pattern": "m.notice",
], }
"actions": [ ],
"dont_notify", "actions": [
] "dont_notify",
} ]
}
```
######## `.m.rule.invite_for_me` ######## `.m.rule.invite_for_me`
@ -320,35 +324,37 @@ Matches any invites to a new room for this user.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.invite_for_me", {
"default": true, "rule_id": ".m.rule.invite_for_me",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"key": "type", {
"kind": "event_match", "key": "type",
"pattern": "m.room.member" "kind": "event_match",
}, "pattern": "m.room.member"
{ },
"key": "content.membership", {
"kind": "event_match", "key": "content.membership",
"pattern": "invite" "kind": "event_match",
}, "pattern": "invite"
{ },
"key": "state_key", {
"kind": "event_match", "key": "state_key",
"pattern": "[the user's Matrix ID]" "kind": "event_match",
} "pattern": "[the user's Matrix ID]"
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "sound", {
"value": "default" "set_tweak": "sound",
} "value": "default"
] }
} ]
}
```
######## `.m.rule.member_event` ######## `.m.rule.member_event`
@ -356,21 +362,23 @@ Matches any `m.room.member_event`.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.member_event", {
"default": true, "rule_id": ".m.rule.member_event",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"key": "type", {
"kind": "event_match", "key": "type",
"pattern": "m.room.member" "kind": "event_match",
} "pattern": "m.room.member"
], }
"actions": [ ],
"dont_notify" "actions": [
] "dont_notify"
} ]
}
```
######## `.m.rule.contains_display_name` ######## `.m.rule.contains_display_name`
@ -379,26 +387,28 @@ current display name in the room in which it was sent.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.contains_display_name", {
"default": true, "rule_id": ".m.rule.contains_display_name",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "contains_display_name" {
} "kind": "contains_display_name"
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "sound", {
"value": "default" "set_tweak": "sound",
}, "value": "default"
{ },
"set_tweak": "highlight" {
} "set_tweak": "highlight"
] }
} ]
}
```
######## `.m.rule.tombstone` ######## `.m.rule.tombstone`
@ -408,29 +418,31 @@ an `@room` notification would accomplish.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.tombstone", {
"default": true, "rule_id": ".m.rule.tombstone",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "event_match", {
"key": "type", "kind": "event_match",
"pattern": "m.room.tombstone" "key": "type",
}, "pattern": "m.room.tombstone"
{ },
"kind": "event_match", {
"key": "state_key", "kind": "event_match",
"pattern": "" "key": "state_key",
} "pattern": ""
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "highlight" {
} "set_tweak": "highlight"
] }
} ]
}
```
######## `.m.rule.roomnotif` ######## `.m.rule.roomnotif`
@ -439,28 +451,30 @@ Matches any message whose content is unencrypted and contains the text
Definition: Definition:
{ ```json
"rule_id": ".m.rule.roomnotif", {
"default": true, "rule_id": ".m.rule.roomnotif",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "event_match", {
"key": "content.body", "kind": "event_match",
"pattern": "@room" "key": "content.body",
}, "pattern": "@room"
{ },
"kind": "sender_notification_permission", {
"key": "room" "kind": "sender_notification_permission",
} "key": "room"
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "highlight" {
} "set_tweak": "highlight"
] }
} ]
}
```
####### Default Content Rules ####### Default Content Rules
@ -471,22 +485,24 @@ part of the user's Matrix ID, separated by word boundaries.
Definition (as a `content` rule): Definition (as a `content` rule):
{ ```json
"rule_id": ".m.rule.contains_user_name", {
"default": true, "rule_id": ".m.rule.contains_user_name",
"enabled": true, "default": true,
"pattern": "[the local part of the user's Matrix ID]", "enabled": true,
"actions": [ "pattern": "[the local part of the user's Matrix ID]",
"notify", "actions": [
{ "notify",
"set_tweak": "sound", {
"value": "default" "set_tweak": "sound",
}, "value": "default"
{ },
"set_tweak": "highlight" {
} "set_tweak": "highlight"
] }
} ]
}
```
####### Default Underride Rules ####### Default Underride Rules
@ -496,25 +512,27 @@ Matches any incoming VOIP call.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.call", {
"default": true, "rule_id": ".m.rule.call",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"key": "type", {
"kind": "event_match", "key": "type",
"pattern": "m.call.invite" "kind": "event_match",
} "pattern": "m.call.invite"
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "sound", {
"value": "ring" "set_tweak": "sound",
} "value": "ring"
] }
} ]
}
```
######## `.m.rule.encrypted_room_one_to_one` ######## `.m.rule.encrypted_room_one_to_one`
@ -526,29 +544,31 @@ encrypted (in 1:1 rooms) or none.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.encrypted_room_one_to_one", {
"default": true, "rule_id": ".m.rule.encrypted_room_one_to_one",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "room_member_count", {
"is": "2" "kind": "room_member_count",
}, "is": "2"
{ },
"kind": "event_match", {
"key": "type", "kind": "event_match",
"pattern": "m.room.encrypted" "key": "type",
} "pattern": "m.room.encrypted"
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "sound", {
"value": "default" "set_tweak": "sound",
} "value": "default"
] }
} ]
}
```
######## `.m.rule.room_one_to_one` ######## `.m.rule.room_one_to_one`
@ -556,29 +576,31 @@ Matches any message sent in a room with exactly two members.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.room_one_to_one", {
"default": true, "rule_id": ".m.rule.room_one_to_one",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "room_member_count", {
"is": "2" "kind": "room_member_count",
}, "is": "2"
{ },
"kind": "event_match", {
"key": "type", "kind": "event_match",
"pattern": "m.room.message" "key": "type",
} "pattern": "m.room.message"
], }
"actions": [ ],
"notify", "actions": [
{ "notify",
"set_tweak": "sound", {
"value": "default" "set_tweak": "sound",
} "value": "default"
] }
} ]
}
```
######## `.m.rule.message` ######## `.m.rule.message`
@ -586,21 +608,23 @@ Matches all chat messages.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.message", {
"default": true, "rule_id": ".m.rule.message",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "event_match", {
"key": "type", "kind": "event_match",
"pattern": "m.room.message" "key": "type",
} "pattern": "m.room.message"
], }
"actions": [ ],
"notify" "actions": [
] "notify"
} ]
}
```
######## `.m.rule.encrypted` ######## `.m.rule.encrypted`
@ -611,21 +635,23 @@ either matches *all* events that are encrypted (in group rooms) or none.
Definition: Definition:
{ ```json
"rule_id": ".m.rule.encrypted", {
"default": true, "rule_id": ".m.rule.encrypted",
"enabled": true, "default": true,
"conditions": [ "enabled": true,
{ "conditions": [
"kind": "event_match", {
"key": "type", "kind": "event_match",
"pattern": "m.room.encrypted" "key": "type",
} "pattern": "m.room.encrypted"
], }
"actions": [ ],
"notify" "actions": [
] "notify"
} ]
}
```
##### Push Rules: API ##### Push Rules: API

@ -68,15 +68,17 @@ before delivering them to clients.
Receipts are sent across federation as EDUs with type `m.receipt`. The Receipts are sent across federation as EDUs with type `m.receipt`. The
format of the EDUs are: format of the EDUs are:
{ ```
<room_id>: { {
<receipt_type>: { <room_id>: {
<user_id>: { <content> } <receipt_type>: {
}, <user_id>: { <content> }
...
}, },
... ...
} },
...
}
```
These are always sent as deltas to previously sent receipts. Currently These are always sent as deltas to previously sent receipts. Currently
only a single `<receipt_type>` should be used: `m.read`. only a single `<receipt_type>` should be used: `m.read`.

@ -114,37 +114,43 @@ Some secret is encrypted using keys with ID `key_id_1` and `key_id_2`:
`org.example.some.secret`: `org.example.some.secret`:
{ ```
"encrypted": { {
"key_id_1": { "encrypted": {
"ciphertext": "base64+encoded+encrypted+data", "key_id_1": {
"mac": "base64+encoded+mac", "ciphertext": "base64+encoded+encrypted+data",
// ... other properties according to algorithm property in "mac": "base64+encoded+mac",
// m.secret_storage.key.key_id_1 // ... other properties according to algorithm property in
}, // m.secret_storage.key.key_id_1
"key_id_2": { },
// ... "key_id_2": {
} // ...
}
} }
}
}
```
and the key descriptions for the keys would be: and the key descriptions for the keys would be:
`m.secret_storage.key.key_id_1`: `m.secret_storage.key.key_id_1`:
{ ```
"name": "Some key", {
"algorithm": "m.secret_storage.v1.aes-hmac-sha2", "name": "Some key",
// ... other properties according to algorithm "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
} // ... other properties according to algorithm
}
```
`m.secret_storage.key.key_id_2`: `m.secret_storage.key.key_id_2`:
{ ```
"name": "Some other key", {
"algorithm": "m.secret_storage.v1.aes-hmac-sha2", "name": "Some other key",
// ... other properties according to algorithm "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
} // ... other properties according to algorithm
}
```
###### `m.secret_storage.v1.aes-hmac-sha2` ###### `m.secret_storage.v1.aes-hmac-sha2`
@ -247,24 +253,28 @@ correctly entered the key, clients should:
For example, the `m.secret_storage.key.key_id` for a key using this For example, the `m.secret_storage.key.key_id` for a key using this
algorithm could look like: algorithm could look like:
{ ```json
"name": "m.default", {
"algorithm": "m.secret_storage.v1.aes-hmac-sha2", "name": "m.default",
"iv": "random+data", "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"mac": "mac+of+encrypted+zeros" "iv": "random+data",
} "mac": "mac+of+encrypted+zeros"
}
```
and data encrypted using this algorithm could look like this: and data encrypted using this algorithm could look like this:
{ ```json
"encrypted": { {
"key_id": { "encrypted": {
"iv": "16+bytes+base64", "key_id": {
"ciphertext": "base64+encoded+encrypted+data", "iv": "16+bytes+base64",
"mac": "base64+encoded+mac" "ciphertext": "base64+encoded+encrypted+data",
} "mac": "base64+encoded+mac"
} }
} }
}
```
###### Key representation ###### Key representation
@ -339,15 +349,17 @@ in the `iterations` parameter.
Example: Example:
{ ```
"passphrase": { {
"algorithm": "m.pbkdf2", "passphrase": {
"salt": "MmMsAlty", "algorithm": "m.pbkdf2",
"iterations": 100000, "salt": "MmMsAlty",
"bits": 256 "iterations": 100000,
}, "bits": 256
... },
} ...
}
```
#### Sharing #### Sharing
@ -407,12 +419,14 @@ previous request. It is sent as an unencrypted to-device event.
Example: Example:
{ ```json
"name": "org.example.some.secret", {
"action": "request", "name": "org.example.some.secret",
"requesting_device_id": "ABCDEFG", "action": "request",
"request_id": "randomly_generated_id_9573" "requesting_device_id": "ABCDEFG",
} "request_id": "randomly_generated_id_9573"
}
```
###### `m.secret.send` ###### `m.secret.send`
@ -444,7 +458,9 @@ an `m.secret.request` event. It must be encrypted as an
Example: Example:
{ ```json
"request_id": "randomly_generated_id_9573", {
"secret": "ThisIsASecretDon'tTellAnyone" "request_id": "randomly_generated_id_9573",
} "secret": "ThisIsASecretDon'tTellAnyone"
}
```

@ -125,19 +125,21 @@ This module adds the following properties to the \_ response:
Example response: Example response:
{ ```json
"next_batch": "s72595_4483_1934", {
"rooms": {"leave": {}, "join": {}, "invite": {}}, "next_batch": "s72595_4483_1934",
"to_device": { "rooms": {"leave": {}, "join": {}, "invite": {}},
"events": [ "to_device": {
{ "events": [
"sender": "@alice:example.com", {
"type": "m.new_device", "sender": "@alice:example.com",
"content": { "type": "m.new_device",
"device_id": "XYZABCDE", "content": {
"rooms": ["!726s6s6q:example.com"] "device_id": "XYZABCDE",
} "rooms": ["!726s6s6q:example.com"]
} }
]
} }
} ]
}
}
```

@ -69,10 +69,12 @@ communication, and all API calls use a Content-Type of
Any errors which occur at the Matrix API level MUST return a "standard Any errors which occur at the Matrix API level MUST return a "standard
error response". This is a JSON object which looks like: error response". This is a JSON object which looks like:
{ ```json
"errcode": "<error code>", {
"error": "<error message>" "errcode": "<error code>",
} "error": "<error message>"
}
```
The `error` string will be a human-readable error message, usually a The `error` string will be a human-readable error message, usually a
sentence explaining what went wrong. The `errcode` string will be a sentence explaining what went wrong. The `errcode` string will be a

@ -248,18 +248,20 @@ and any query parameters if present, but should not include the leading
Step 1 sign JSON: Step 1 sign JSON:
{ ```
"method": "GET", {
"uri": "/target", "method": "GET",
"origin": "origin.hs.example.com", "uri": "/target",
"destination": "destination.hs.example.com", "origin": "origin.hs.example.com",
"content": <request body>, "destination": "destination.hs.example.com",
"signatures": { "content": <request body>,
"origin.hs.example.com": { "signatures": {
"ed25519:key1": "ABCDEF..." "origin.hs.example.com": {
} "ed25519:key1": "ABCDEF..."
} }
} }
}
```
The server names in the JSON above are the server names for each The server names in the JSON above are the server names for each
homeserver involved. Delegation from the [server name resolution homeserver involved. Delegation from the [server name resolution
@ -277,31 +279,33 @@ Step 2 add Authorization header:
Example python code: Example python code:
def authorization_headers(origin_name, origin_signing_key, ```py
destination_name, request_method, request_target, def authorization_headers(origin_name, origin_signing_key,
content=None): destination_name, request_method, request_target,
request_json = { content=None):
"method": request_method, request_json = {
"uri": request_target, "method": request_method,
"origin": origin_name, "uri": request_target,
"destination": destination_name, "origin": origin_name,
} "destination": destination_name,
}
if content is not None: if content is not None:
request_json["content"] = content request_json["content"] = content
signed_json = sign_json(request_json, origin_name, origin_signing_key) signed_json = sign_json(request_json, origin_name, origin_signing_key)
authorization_headers = [] authorization_headers = []
for key, sig in signed_json["signatures"][origin_name].items(): for key, sig in signed_json["signatures"][origin_name].items():
authorization_headers.append(bytes( authorization_headers.append(bytes(
"X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % ( "X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
origin_name, key, sig, origin_name, key, sig,
) )
)) ))
return ("Authorization", authorization_headers) return ("Authorization", authorization_headers)
```
### Response Authentication ### Response Authentication
@ -1121,49 +1125,51 @@ SHA-256.
### Example code ### Example code
def hash_and_sign_event(event_object, signing_key, signing_name): ```py
# First we need to hash the event object. def hash_and_sign_event(event_object, signing_key, signing_name):
content_hash = compute_content_hash(event_object) # First we need to hash the event object.
event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)} content_hash = compute_content_hash(event_object)
event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)}
# 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. # Strip all the keys that would be removed if the event was redacted.
# This means that we can tell if any of the non-essential keys are # The hashes are not stripped and cover all the keys in the event.
# modified or removed. # This means that we can tell if any of the non-essential keys are
stripped_object = strip_non_essential_keys(event_object) # modified or removed.
stripped_object = strip_non_essential_keys(event_object)
# Sign the stripped JSON object. The signature only covers the
# essential keys and the hashes. This means that we can check the # Sign the stripped JSON object. The signature only covers the
# signature even if the event is redacted. # essential keys and the hashes. This means that we can check the
signed_object = sign_json(stripped_object, signing_key, signing_name) # signature even if the event is redacted.
signed_object = sign_json(stripped_object, signing_key, signing_name)
# Copy the signatures from the stripped event to the original event.
event_object["signatures"] = signed_object["signatures"] # Copy the signatures from the stripped event to the original event.
event_object["signatures"] = signed_object["signatures"]
def compute_content_hash(event_object):
# take a copy of the event before we remove any keys. def compute_content_hash(event_object):
event_object = dict(event_object) # take a copy of the event before we remove any keys.
event_object = dict(event_object)
# Keys under "unsigned" can be modified by other servers.
# They are useful for conveying information like the age of an # Keys under "unsigned" can be modified by other servers.
# event that will change in transit. # They are useful for conveying information like the age of an
# Since they can be modified we need to exclude them from the hash. # event that will change in transit.
event_object.pop("unsigned", None) # Since they can be modified we need to exclude them from the hash.
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. # Signatures will depend on the current value of the "hashes" key.
event_object.pop("signatures", None) # 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 # The "hashes" key might contain multiple algorithms if we decide to
# output in our hash so we exclude the "hashes" dict from the hash. # migrate away from SHA-2. We don't want to include an existing hash
event_object.pop("hashes", None) # 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. # Encode the JSON using a canonical encoding so that we get the same
event_json_bytes = encode_canonical_json(event_object) # bytes on every server for the same JSON object.
event_json_bytes = encode_canonical_json(event_object)
return hashlib.sha256(event_json_bytes)
return hashlib.sha256(event_json_bytes)
```
## Security considerations ## Security considerations

Loading…
Cancel
Save