From 241e01c3321d16e37d6e1a1b78bcc2b0ca38d1f9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 1 Nov 2021 08:49:15 -0600 Subject: [PATCH] Fully specify room versions (#3432) * Fully specify room versions * Misc typo clarifications * Try to clarify redactions a bit * Update content/client-server-api/_index.md Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update content/rooms/v6.md Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update content/rooms/v6.md Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Better describe client considerations * Doc template params * Move redaction "new stuff" to v3 * Remove unhelpful sentences about "here follows unchanged stuff" * Simplify event signing text * Clean up handling redactions sections * Move v4's event schema to unchanged section * Update content/rooms/v4.md Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- content/client-server-api/_index.md | 4 +- content/rooms/fragments/v1-auth-rules.md | 144 ++++++++++++ content/rooms/fragments/v1-canonical-json.md | 3 + content/rooms/fragments/v1-redactions.md | 29 +++ content/rooms/fragments/v2-state-res.md | 165 ++++++++++++++ content/rooms/fragments/v3-auth-rules.md | 149 +++++++++++++ .../rooms/fragments/v3-handling-redactions.md | 25 +++ content/rooms/fragments/v4-event-explainer.md | 15 ++ .../fragments/v5-signing-requirements.md | 15 ++ content/rooms/fragments/v6-canonical-json.md | 6 + content/rooms/fragments/v6-redactions.md | 28 +++ content/rooms/v1.md | 183 +-------------- content/rooms/v2.md | 199 +++-------------- content/rooms/v3.md | 60 +++-- content/rooms/v4.md | 51 +++-- content/rooms/v5.md | 53 +++-- content/rooms/v6.md | 211 ++++++++++++++---- content/rooms/v7.md | 208 +++++++++++++++-- layouts/shortcodes/rver-fragment.html | 25 +++ 19 files changed, 1108 insertions(+), 465 deletions(-) create mode 100644 content/rooms/fragments/v1-auth-rules.md create mode 100644 content/rooms/fragments/v1-canonical-json.md create mode 100644 content/rooms/fragments/v1-redactions.md create mode 100644 content/rooms/fragments/v2-state-res.md create mode 100644 content/rooms/fragments/v3-auth-rules.md create mode 100644 content/rooms/fragments/v3-handling-redactions.md create mode 100644 content/rooms/fragments/v4-event-explainer.md create mode 100644 content/rooms/fragments/v5-signing-requirements.md create mode 100644 content/rooms/fragments/v6-canonical-json.md create mode 100644 content/rooms/fragments/v6-redactions.md create mode 100644 layouts/shortcodes/rver-fragment.html diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 04d20b52..43b87250 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1598,7 +1598,9 @@ content from the databases. Servers should include a copy of the when serving the redacted event to clients. The exact algorithm to apply against an event is defined in the [room -version specification](/rooms). +version specification](/rooms), as are the criteria homeservers should +use when deciding whether to accept a redaction event from a remote +homeserver. When a client receives an `m.room.redaction` event, it should change the affected event in the same way a server does. diff --git a/content/rooms/fragments/v1-auth-rules.md b/content/rooms/fragments/v1-auth-rules.md new file mode 100644 index 00000000..4c611093 --- /dev/null +++ b/content/rooms/fragments/v1-auth-rules.md @@ -0,0 +1,144 @@ +The types of state events that affect authorization are: + +- `m.room.create` +- `m.room.member` +- `m.room.join_rules` +- `m.room.power_levels` +- `m.room.third_party_invite` + +{{% boxes/note %}} +Power levels are inferred from defaults when not explicitly supplied. +For example, mentions of the `sender`'s power level can also refer to +the default power level for users in the room. +{{% /boxes/note %}} + +The rules are as follows: + +1. If type is `m.room.create`: + 1. If it has any previous events, reject. + 2. If the domain of the `room_id` does not match the domain of the + `sender`, reject. + 3. If `content.room_version` is present and is not a recognised + version, reject. + 4. If `content` has no `creator` field, reject. + 5. Otherwise, allow. +2. Reject if event has `auth_events` that: + 1. have duplicate entries for a given `type` and `state_key` pair + 2. have entries whose `type` and `state_key` don't match those + specified by the [auth events + selection](/server-server-api#auth-events-selection) + algorithm described in the server specification. +3. If event does not have a `m.room.create` in its `auth_events`, + reject. +4. If type is `m.room.aliases`: + 1. If event has no `state_key`, reject. + 2. If sender's domain doesn't matches `state_key`, reject. + 3. Otherwise, allow. +5. If type is `m.room.member`: + 1. If no `state_key` key or `membership` key in `content`, reject. + 2. If `membership` is `join`: + 1. If the only previous event is an `m.room.create` and the + `state_key` is the creator, allow. + 2. If the `sender` does not match `state_key`, reject. + 3. If the `sender` is banned, reject. + 4. If the `join_rule` is `invite` then allow if membership + state is `invite` or `join`. + 5. If the `join_rule` is `public`, allow. + 6. Otherwise, reject. + 3. If `membership` is `invite`: + 1. If `content` has `third_party_invite` key: + 1. If *target user* is banned, reject. + 2. If `content.third_party_invite` does not have a `signed` + key, reject. + 3. If `signed` does not have `mxid` and `token` keys, + reject. + 4. If `mxid` does not match `state_key`, reject. + 5. If there is no `m.room.third_party_invite` event in the + current room state with `state_key` matching `token`, + reject. + 6. If `sender` does not match `sender` of the + `m.room.third_party_invite`, reject. + 7. 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: + 1. A single public key in the `public_key` field. + 2. A list of public keys in the `public_keys` field. + 8. Otherwise, reject. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If *target user*'s current membership state is `join` or + `ban`, reject. + 4. If the `sender`'s power level is greater than or equal to + the *invite level*, allow. + 5. Otherwise, reject. + 4. If `membership` is `leave`: + 1. If the `sender` matches `state_key`, allow if and only if + that user's current membership state is `invite` or `join`. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If the *target user*'s current membership state is `ban`, + and the `sender`'s power level is less than the *ban level*, + reject. + 4. If the `sender`'s power level is greater than or equal to + the *kick level*, and the *target user*'s power level is + less than the `sender`'s power level, allow. + 5. Otherwise, reject. + 5. If `membership` is `ban`: + 1. If the `sender`'s current membership state is not `join`, + reject. + 2. If the `sender`'s power level is greater than or equal to + the *ban level*, and the *target user*'s power level is less + than the `sender`'s power level, allow. + 3. Otherwise, reject. + 6. Otherwise, the membership is unknown. Reject. +6. If the `sender`'s current membership state is not `join`, reject. +7. If type is `m.room.third_party_invite`: + 1. Allow if and only if `sender`'s current power level is greater + than or equal to the *invite level*. +8. If the event type's *required power level* is greater than the + `sender`'s power level, reject. +9. If the event has a `state_key` that starts with an `@` and does not + match the `sender`, reject. +10. If type is `m.room.power_levels`: + 1. 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. + 2. If there is no previous `m.room.power_levels` event in the room, + allow. + 3. For the keys `users_default`, `events_default`, `state_default`, + `ban`, `redact`, `kick`, `invite` check if they were added, + changed or removed. For each found alteration: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 4. For each entry being added, changed or removed in both the + `events` and `users` keys: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 5. For each entry being changed under the `users` key, other than + the `sender`'s own entry: + 1. If the current value is equal to the `sender`'s current + power level, reject. + 6. Otherwise, allow. +11. If type is `m.room.redaction`: + 1. If the `sender`'s power level is greater than or equal to the + *redact level*, allow. + 2. If the domain of the `event_id` of the event being redacted is + the same as the domain of the `event_id` of the + `m.room.redaction`, allow. + 3. Otherwise, reject. +12. Otherwise, allow. + +{{% boxes/note %}} +Some consequences of these rules: + +- Unless you are a member of the room, the only permitted operations + (apart from the initial create/join) are: joining a public room; + accepting or rejecting an invitation to a room. +- To unban somebody, you must have power level greater than or equal + to both the kick *and* ban levels, *and* greater than the target + user's power level. +{{% /boxes/note %}} diff --git a/content/rooms/fragments/v1-canonical-json.md b/content/rooms/fragments/v1-canonical-json.md new file mode 100644 index 00000000..cdff4a5c --- /dev/null +++ b/content/rooms/fragments/v1-canonical-json.md @@ -0,0 +1,3 @@ +Servers MUST NOT strictly enforce the JSON format specified in the +[appendices](/appendices#canonical-json) for the reasons +described there. diff --git a/content/rooms/fragments/v1-redactions.md b/content/rooms/fragments/v1-redactions.md new file mode 100644 index 00000000..16bda11d --- /dev/null +++ b/content/rooms/fragments/v1-redactions.md @@ -0,0 +1,29 @@ +Upon receipt of a redaction event, the server must strip off any keys +not in the following list: + +- `event_id` +- `type` +- `room_id` +- `sender` +- `state_key` +- `content` +- `hashes` +- `signatures` +- `depth` +- `prev_events` +- `prev_state` +- `auth_events` +- `origin` +- `origin_server_ts` +- `membership` + +The content object must also be stripped of all keys, unless it is one +of one of the following event types: + +- `m.room.member` allows key `membership`. +- `m.room.create` allows key `creator`. +- `m.room.join_rules` allows key `join_rule`. +- `m.room.power_levels` allows keys `ban`, `events`, `events_default`, + `kick`, `redact`, `state_default`, `users`, `users_default`. +- `m.room.aliases` allows key `aliases`. +- `m.room.history_visibility` allows key `history_visibility`. diff --git a/content/rooms/fragments/v2-state-res.md b/content/rooms/fragments/v2-state-res.md new file mode 100644 index 00000000..943ef131 --- /dev/null +++ b/content/rooms/fragments/v2-state-res.md @@ -0,0 +1,165 @@ +The room state *S*′(*E*) after an event *E* is defined in terms of the +room state *S*(*E*) before *E*, and depends on whether *E* is a state +event or a message event: + +- If *E* is a message event, then *S*′(*E*) = *S*(*E*). +- If *E* is a state event, then *S*′(*E*) is *S*(*E*), except that its + entry corresponding to *E*'s `event_type` and `state_key` is + replaced by *E*'s `event_id`. + +The room state *S*(*E*) before *E* is the *resolution* of the set of +states {*S*′(*E*1), *S*′(*E*2), …} consisting of +the states after each of *E*'s `prev_event`s +{*E*1, *E*2, …}, where the resolution of a set of +states is given in the algorithm below. + +#### Definitions + +The state resolution algorithm for version 2 rooms uses the following +definitions, given the set of room states +{*S*1, *S*2, …}: + +Power events +A *power event* is a state event with type `m.room.power_levels` or +`m.room.join_rules`, or a state event with type `m.room.member` where +the `membership` is `leave` or `ban` and the `sender` does not match the +`state_key`. The idea behind this is that power events are events that +might remove someone's ability to do something in the room. + +Unconflicted state map and conflicted state set +The *unconflicted state map* is the state where the value of each key +exists and is the same in each state *S**i*. The *conflicted +state set* is the set of all other state events. Note that the +unconflicted state map only has one event per `(event_type, state_key)`, +whereas the conflicted state set may have multiple events. + +Auth difference +The *auth difference* is calculated by first calculating the full auth +chain for each state *S**i*, that is the union of the auth +chains for each event in *S**i*, and then taking every event +that doesn't appear in every auth chain. If *C**i* is the +full auth chain of *S**i*, then the auth difference is + ∪ *C**i* −  ∩ *C**i*. + +Full conflicted set +The *full conflicted set* is the union of the conflicted state set and +the auth difference. + +Reverse topological power ordering +The *reverse topological power ordering* of a set of events is the +lexicographically smallest topological ordering based on the DAG formed +by auth events. The reverse topological power ordering is ordered from +earliest event to latest. For comparing two topological orderings to +determine which is the lexicographically smallest, the following +comparison relation on events is used: for events *x* and *y*, +*x* < *y* if + +1. *x*'s sender has *greater* power level than *y*'s sender, when + looking at their respective `auth_event`s; or +2. the senders have the same power level, but *x*'s `origin_server_ts` + is *less* than *y*'s `origin_server_ts`; or +3. the senders have the same power level and the events have the same + `origin_server_ts`, but *x*'s `event_id` is *less* than *y*'s + `event_id`. + +The reverse topological power ordering can be found by sorting the +events using Kahn's algorithm for topological sorting, and at each step +selecting, among all the candidate vertices, the smallest vertex using +the above comparison relation. + +Mainline ordering +Given an `m.room.power_levels` event *P*, the *mainline of* *P* is the +list of events generated by starting with *P* and recursively taking the +`m.room.power_levels` events from the `auth_events`, ordered such that +*P* is last. Given another event *e*, the *closest mainline event to* +*e* is the first event encountered in the mainline when iteratively +descending through the `m.room.power_levels` events in the `auth_events` +starting at *e*. If no mainline event is encountered when iteratively +descending through the `m.room.power_levels` events, then the closest +mainline event to *e* can be considered to be a dummy event that is +before any other event in the mainline of *P* for the purposes of +condition 1 below. + +The *mainline ordering based on* *P* of a set of events is the ordering, +from smallest to largest, using the following comparison relation on +events: for events *x* and *y*, *x* < *y* if + +1. the closest mainline event to *x* appears *before* the closest + mainline event to *y*; or +2. the closest mainline events are the same, but *x*'s + `origin_server_ts` is *less* than *y*'s `origin_server_ts`; or +3. the closest mainline events are the same and the events have the + same `origin_server_ts`, but *x*'s `event_id` is *less* than *y*'s + `event_id`. + +Iterative auth checks +The *iterative auth checks algorithm* takes as input an initial room +state and a sorted list of state events, and constructs a new room state +by iterating through the event list and applying the state event to the +room state if the state event is allowed by the [authorization +rules](/server-server-api#authorization-rules). +If the state event is not allowed by the authorization rules, then the +event is ignored. If a `(event_type, state_key)` key that is required +for checking the authorization rules is not present in the state, then +the appropriate state event from the event's `auth_events` is used if +the auth event is not rejected. + +#### Algorithm + +The *resolution* of a set of states is obtained as follows: + +1. Take all *power events* and any events in their auth chains, + recursively, that appear in the *full conflicted set* and order them + by the *reverse topological power ordering*. +2. Apply the *iterative auth checks algorithm*, starting from the + *unconflicted state map*, to the list of events from the previous + step to get a partially resolved state. +3. 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 obtained in step 2. +4. Apply the *iterative auth checks algorithm* on the partial resolved + state and the list of events from the previous step. +5. Update the result by replacing any event with the event with the + same key from the *unconflicted state map*, if such an event exists, + to get the final resolved state. + +#### Rejected events + +Events that have been rejected due to failing auth based on the state at +the event (rather than based on their auth chain) are handled as usual +by the algorithm, unless otherwise specified. + +Note that no events rejected due to failure to auth against their auth +chain should appear in the process, as they should not appear in state +(the algorithm only uses events that appear in either the state sets or +in the auth chain of the events in the state sets). + +{{% boxes/rationale %}} +This helps ensure that different servers' view of state is more likely +to converge, since rejection state of an event may be different. This +can happen if a third server gives an incorrect version of the state +when a server joins a room via it (either due to being faulty or +malicious). Convergence of state is a desirable property as it ensures +that all users in the room have a (mostly) consistent view of the state +of the room. If the view of the state on different servers diverges it +can lead to bifurcation of the room due to e.g. servers disagreeing on +who is in the room. + +Intuitively, using rejected events feels dangerous, however: + +1. Servers cannot arbitrarily make up state, since they still need to + pass the auth checks based on the event's auth chain (e.g. they + can't grant themselves power levels if they didn't have them + before). +2. For a previously rejected event to pass auth there must be a set of + state that allows said event. A malicious server could therefore + produce a fork where it claims the state is that particular set of + state, duplicate the rejected event to point to that fork, and send + the event. The duplicated event would then pass the auth checks. + Ignoring rejected events would therefore not eliminate any potential + attack vectors. +{{% /boxes/rationale %}} + +Rejected auth events are deliberately excluded from use in the iterative +auth checks, as auth events aren't re-authed (although non-auth events +are) during the iterative auth checks. diff --git a/content/rooms/fragments/v3-auth-rules.md b/content/rooms/fragments/v3-auth-rules.md new file mode 100644 index 00000000..93195af3 --- /dev/null +++ b/content/rooms/fragments/v3-auth-rules.md @@ -0,0 +1,149 @@ +--- +# unused frontmatter - just fixing a hugo issue where it doesn't parse +# shortcodes at the start of a file. +--- + +{{% added-in this=true %}} In room versions 1 and 2, events need a +signature from the domain of the `event_id` in order to be considered +valid. This room version does not include an `event_id` over federation +in the same respect, so does not need a signature from that server. +The event must still be signed by the server denoted by the `sender`, +however. + +The types of state events that affect authorization are: + +- `m.room.create` +- `m.room.member` +- `m.room.join_rules` +- `m.room.power_levels` +- `m.room.third_party_invite` + +{{% boxes/note %}} +Power levels are inferred from defaults when not explicitly supplied. +For example, mentions of the `sender`'s power level can also refer to +the default power level for users in the room. +{{% /boxes/note %}} + +The complete list of rules, as of room version 3, is as follows: + +1. If type is `m.room.create`: + 1. If it has any previous events, reject. + 2. If the domain of the `room_id` does not match the domain of the + `sender`, reject. + 3. If `content.room_version` is present and is not a recognised + version, reject. + 4. If `content` has no `creator` field, reject. + 5. Otherwise, allow. +2. Reject if event has `auth_events` that: + 1. have duplicate entries for a given `type` and `state_key` pair + 2. have entries whose `type` and `state_key` don't match those + specified by the [auth events + selection](/server-server-api#auth-events-selection) + algorithm described in the server specification. +3. If event does not have a `m.room.create` in its `auth_events`, + reject. +4. If type is `m.room.aliases`: + 1. If event has no `state_key`, reject. + 2. If sender's domain doesn't matches `state_key`, reject. + 3. Otherwise, allow. +5. If type is `m.room.member`: + 1. If no `state_key` key or `membership` key in `content`, reject. + 2. If `membership` is `join`: + 1. If the only previous event is an `m.room.create` and the + `state_key` is the creator, allow. + 2. If the `sender` does not match `state_key`, reject. + 3. If the `sender` is banned, reject. + 4. If the `join_rule` is `invite` then allow if membership + state is `invite` or `join`. + 5. If the `join_rule` is `public`, allow. + 6. Otherwise, reject. + 3. If `membership` is `invite`: + 1. If `content` has `third_party_invite` key: + 1. If *target user* is banned, reject. + 2. If `content.third_party_invite` does not have a `signed` + key, reject. + 3. If `signed` does not have `mxid` and `token` keys, + reject. + 4. If `mxid` does not match `state_key`, reject. + 5. If there is no `m.room.third_party_invite` event in the + current room state with `state_key` matching `token`, + reject. + 6. If `sender` does not match `sender` of the + `m.room.third_party_invite`, reject. + 7. 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: + 1. A single public key in the `public_key` field. + 2. A list of public keys in the `public_keys` field. + 8. Otherwise, reject. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If *target user*'s current membership state is `join` or + `ban`, reject. + 4. If the `sender`'s power level is greater than or equal to + the *invite level*, allow. + 5. Otherwise, reject. + 4. If `membership` is `leave`: + 1. If the `sender` matches `state_key`, allow if and only if + that user's current membership state is `invite` or `join`. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If the *target user*'s current membership state is `ban`, + and the `sender`'s power level is less than the *ban level*, + reject. + 4. If the `sender`'s power level is greater than or equal to + the *kick level*, and the *target user*'s power level is + less than the `sender`'s power level, allow. + 5. Otherwise, reject. + 5. If `membership` is `ban`: + 1. If the `sender`'s current membership state is not `join`, + reject. + 2. If the `sender`'s power level is greater than or equal to + the *ban level*, and the *target user*'s power level is less + than the `sender`'s power level, allow. + 3. Otherwise, reject. + 6. Otherwise, the membership is unknown. Reject. +6. If the `sender`'s current membership state is not `join`, reject. +7. If type is `m.room.third_party_invite`: + 1. Allow if and only if `sender`'s current power level is greater + than or equal to the *invite level*. +8. If the event type's *required power level* is greater than the + `sender`'s power level, reject. +9. If the event has a `state_key` that starts with an `@` and does not + match the `sender`, reject. +10. If type is `m.room.power_levels`: + 1. 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. + 2. If there is no previous `m.room.power_levels` event in the room, + allow. + 3. For the keys `users_default`, `events_default`, `state_default`, + `ban`, `redact`, `kick`, `invite` check if they were added, + changed or removed. For each found alteration: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 4. For each entry being added, changed or removed in both the + `events` and `users` keys: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 5. For each entry being changed under the `users` key, other than + the `sender`'s own entry: + 1. If the current value is equal to the `sender`'s current + power level, reject. + 6. Otherwise, allow. +11. Otherwise, allow. + +{{% boxes/note %}} +Some consequences of these rules: + +- Unless you are a member of the room, the only permitted operations + (apart from the initial create/join) are: joining a public room; + accepting or rejecting an invitation to a room. +- To unban somebody, you must have power level greater than or equal + to both the kick *and* ban levels, *and* greater than the target + user's power level. +{{% /boxes/note %}} diff --git a/content/rooms/fragments/v3-handling-redactions.md b/content/rooms/fragments/v3-handling-redactions.md new file mode 100644 index 00000000..60fac3e4 --- /dev/null +++ b/content/rooms/fragments/v3-handling-redactions.md @@ -0,0 +1,25 @@ +--- +# unused frontmatter - just fixing a hugo issue where it doesn't parse +# shortcodes at the start of a file. +--- + +{{% added-in this=true %}} In room versions 1 and 2, redactions were +explicitly part of the [authorization rules](/rooms/v1/#authorization-rules) +under Rule 11. As of room version 3, these conditions no longer exist as +represented by the above rules. + +While redactions are always accepted by the authorization rules for +events, they should not be sent to clients until both the redaction +event and the event the redaction affects have been received, and can +be validated. If both events are valid and have been seen by the server, +then the server applies the redaction if one of the following conditions +is met: + +1. The power level of the redaction event's `sender` is greater than or + equal to the *redact level*. +2. The domain of the redaction event's `sender` matches that of the + original event's `sender`. + +If the server would apply a redaction, the redaction event is also sent +to clients. Otherwise, the server simply waits for a valid partner event +to arrive where it can then re-check the above. diff --git a/content/rooms/fragments/v4-event-explainer.md b/content/rooms/fragments/v4-event-explainer.md new file mode 100644 index 00000000..43a45f04 --- /dev/null +++ b/content/rooms/fragments/v4-event-explainer.md @@ -0,0 +1,15 @@ +The event ID is the [reference +hash](/server-server-api#calculating-the-reference-hash-for-an-event) of +the event encoded using a variation of [Unpadded +Base64](/appendices#unpadded-base64) which replaces the 62nd and +63rd characters with `-` and `_` instead of using `+` and `/`. This +matches [RFC4648's definition of URL-safe +base64](https://tools.ietf.org/html/rfc4648#section-5). Event IDs are +still prefixed with `$` and may result in looking like +`$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg`. + +Just like in room version 3, event IDs should not be sent over +federation to servers when the room uses this room version. On the +receiving end of an event, the server should compute the relevant event +ID for itself. Room version 3 also changes the format of `auth_events` +and `prev_events` in a PDU. diff --git a/content/rooms/fragments/v5-signing-requirements.md b/content/rooms/fragments/v5-signing-requirements.md new file mode 100644 index 00000000..b1a10e2c --- /dev/null +++ b/content/rooms/fragments/v5-signing-requirements.md @@ -0,0 +1,15 @@ +When validating event signatures, servers MUST enforce the +`valid_until_ts` property from a key request is at least as large as the +`origin_server_ts` for the event being validated. Servers missing a copy +of the signing key MUST try to obtain one via the [GET +/\_matrix/key/v2/server](/server-server-api#get_matrixkeyv2serverkeyid) +or [POST +/\_matrix/key/v2/query](/server-server-api#post_matrixkeyv2query) +APIs. When using the `/query` endpoint, servers MUST set the +`minimum_valid_until_ts` property to prompt the notary server to attempt +to refresh the key if appropriate. + +Servers MUST use the lesser of `valid_until_ts` and 7 days into the +future when determining if a key is valid. This is to avoid a situation +where an attacker publishes a key which is valid for a significant +amount of time without a way for the homeserver owner to revoke it. diff --git a/content/rooms/fragments/v6-canonical-json.md b/content/rooms/fragments/v6-canonical-json.md new file mode 100644 index 00000000..5f008bd1 --- /dev/null +++ b/content/rooms/fragments/v6-canonical-json.md @@ -0,0 +1,6 @@ +Servers MUST strictly enforce the JSON format specified in the +[appendices](/appendices#canonical-json). This translates to a +400 `M_BAD_JSON` error on most endpoints, or discarding of events over +federation. For example, the Federation API's `/send` endpoint would +discard the event whereas the Client Server API's `/send/{eventType}` +endpoint would return a `M_BAD_JSON` error. diff --git a/content/rooms/fragments/v6-redactions.md b/content/rooms/fragments/v6-redactions.md new file mode 100644 index 00000000..8343afd2 --- /dev/null +++ b/content/rooms/fragments/v6-redactions.md @@ -0,0 +1,28 @@ +Upon receipt of a redaction event, the server must strip off any keys +not in the following list: + +- `event_id` +- `type` +- `room_id` +- `sender` +- `state_key` +- `content` +- `hashes` +- `signatures` +- `depth` +- `prev_events` +- `prev_state` +- `auth_events` +- `origin` +- `origin_server_ts` +- `membership` + +The content object must also be stripped of all keys, unless it is one +of one of the following event types: + +- `m.room.member` allows key `membership`. +- `m.room.create` allows key `creator`. +- `m.room.join_rules` allows key `join_rule`. +- `m.room.power_levels` allows keys `ban`, `events`, `events_default`, + `kick`, `redact`, `state_default`, `users`, `users_default`. +- `m.room.history_visibility` allows key `history_visibility`. diff --git a/content/rooms/v1.md b/content/rooms/v1.md index b76147df..6763270a 100644 --- a/content/rooms/v1.md +++ b/content/rooms/v1.md @@ -9,40 +9,12 @@ building blocks for other room versions. ## Client considerations -Clients may need to consider some algorithms performed by the server for -their own implementation. +Clients which implement the redaction algorithm locally should refer to the +[redactions](#redactions) section below. ### Redactions -Upon receipt of a redaction event, the server must strip off any keys -not in the following list: - -- `event_id` -- `type` -- `room_id` -- `sender` -- `state_key` -- `content` -- `hashes` -- `signatures` -- `depth` -- `prev_events` -- `prev_state` -- `auth_events` -- `origin` -- `origin_server_ts` -- `membership` - -The content object must also be stripped of all keys, unless it is one -of one of the following event types: - -- `m.room.member` allows key `membership`. -- `m.room.create` allows key `creator`. -- `m.room.join_rules` allows key `join_rule`. -- `m.room.power_levels` allows keys `ban`, `events`, `events_default`, - `kick`, `redact`, `state_default`, `users`, `users_default`. -- `m.room.aliases` allows key `aliases`. -- `m.room.history_visibility` allows key `history_visibility`. +{{% rver-fragment name="v1-redactions" %}} ## Server implementation components @@ -127,150 +99,7 @@ affected are said to be *conflicting* events. ### Authorization rules -The types of state events that affect authorization are: - -- `m.room.create` -- `m.room.member` -- `m.room.join_rules` -- `m.room.power_levels` -- `m.room.third_party_invite` - -{{% boxes/note %}} -Power levels are inferred from defaults when not explicitly supplied. -For example, mentions of the `sender`'s power level can also refer to -the default power level for users in the room. -{{% /boxes/note %}} - -The rules are as follows: - -1. If type is `m.room.create`: - 1. If it has any previous events, reject. - 2. If the domain of the `room_id` does not match the domain of the - `sender`, reject. - 3. If `content.room_version` is present and is not a recognised - version, reject. - 4. If `content` has no `creator` field, reject. - 5. Otherwise, allow. -2. Reject if event has `auth_events` that: - 1. have duplicate entries for a given `type` and `state_key` pair - 2. have entries whose `type` and `state_key` don't match those - specified by the [auth events - selection](/server-server-api#auth-events-selection) - algorithm described in the server specification. -3. If event does not have a `m.room.create` in its `auth_events`, - reject. -4. If type is `m.room.aliases`: - 1. If event has no `state_key`, reject. - 2. If sender's domain doesn't matches `state_key`, reject. - 3. Otherwise, allow. -5. If type is `m.room.member`: - 1. If no `state_key` key or `membership` key in `content`, reject. - 2. If `membership` is `join`: - 1. If the only previous event is an `m.room.create` and the - `state_key` is the creator, allow. - 2. If the `sender` does not match `state_key`, reject. - 3. If the `sender` is banned, reject. - 4. If the `join_rule` is `invite` then allow if membership - state is `invite` or `join`. - 5. If the `join_rule` is `public`, allow. - 6. Otherwise, reject. - 3. If `membership` is `invite`: - 1. If `content` has `third_party_invite` key: - 1. If *target user* is banned, reject. - 2. If `content.third_party_invite` does not have a `signed` - key, reject. - 3. If `signed` does not have `mxid` and `token` keys, - reject. - 4. If `mxid` does not match `state_key`, reject. - 5. If there is no `m.room.third_party_invite` event in the - current room state with `state_key` matching `token`, - reject. - 6. If `sender` does not match `sender` of the - `m.room.third_party_invite`, reject. - 7. 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: - 1. A single public key in the `public_key` field. - 2. A list of public keys in the `public_keys` field. - 8. Otherwise, reject. - 2. If the `sender`'s current membership state is not `join`, - reject. - 3. If *target user*'s current membership state is `join` or - `ban`, reject. - 4. If the `sender`'s power level is greater than or equal to - the *invite level*, allow. - 5. Otherwise, reject. - 4. If `membership` is `leave`: - 1. If the `sender` matches `state_key`, allow if and only if - that user's current membership state is `invite` or `join`. - 2. If the `sender`'s current membership state is not `join`, - reject. - 3. If the *target user*'s current membership state is `ban`, - and the `sender`'s power level is less than the *ban level*, - reject. - 4. If the `sender`'s power level is greater than or equal to - the *kick level*, and the *target user*'s power level is - less than the `sender`'s power level, allow. - 5. Otherwise, reject. - 5. If `membership` is `ban`: - 1. If the `sender`'s current membership state is not `join`, - reject. - 2. If the `sender`'s power level is greater than or equal to - the *ban level*, and the *target user*'s power level is less - than the `sender`'s power level, allow. - 3. Otherwise, reject. - 6. Otherwise, the membership is unknown. Reject. -6. If the `sender`'s current membership state is not `join`, reject. -7. If type is `m.room.third_party_invite`: - 1. Allow if and only if `sender`'s current power level is greater - than or equal to the *invite level*. -8. If the event type's *required power level* is greater than the - `sender`'s power level, reject. -9. If the event has a `state_key` that starts with an `@` and does not - match the `sender`, reject. -10. If type is `m.room.power_levels`: - 1. 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. - 2. If there is no previous `m.room.power_levels` event in the room, - allow. - 3. For the keys `users_default`, `events_default`, `state_default`, - `ban`, `redact`, `kick`, `invite` check if they were added, - changed or removed. For each found alteration: - 1. If the current value is higher than the `sender`'s current - power level, reject. - 2. If the new value is higher than the `sender`'s current power - level, reject. - 4. For each entry being added, changed or removed in both the - `events` and `users` keys: - 1. If the current value is higher than the `sender`'s current - power level, reject. - 2. If the new value is higher than the `sender`'s current power - level, reject. - 5. For each entry being changed under the `users` key, other than - the `sender`'s own entry: - 1. If the current value is equal to the `sender`'s current - power level, reject. - 6. Otherwise, allow. -11. If type is `m.room.redaction`: - 1. If the `sender`'s power level is greater than or equal to the - *redact level*, allow. - 2. If the domain of the `event_id` of the event being redacted is - the same as the domain of the `event_id` of the - `m.room.redaction`, allow. - 3. Otherwise, reject. -12. Otherwise, allow. - -{{% boxes/note %}} -Some consequences of these rules: - -- Unless you are a member of the room, the only permitted operations - (apart from the initial create/join) are: joining a public room; - accepting or rejecting an invitation to a room. -- To unban somebody, you must have power level greater than or equal - to both the kick *and* ban levels, *and* greater than the target - user's power level. -{{% /boxes/note %}} +{{% rver-fragment name="v1-auth-rules" %}} ### Event format @@ -280,6 +109,4 @@ Events in version 1 rooms have the following structure: ### Canonical JSON -Servers MUST NOT strictly enforce the JSON format specified in the -[appendices](/appendices#canonical-json) for the reasons -described there. +{{% rver-fragment name="v1-canonical-json" %}} diff --git a/content/rooms/v2.md b/content/rooms/v2.md index f087f00e..e5c932c9 100644 --- a/content/rooms/v2.md +++ b/content/rooms/v2.md @@ -4,9 +4,15 @@ type: docs weight: 20 --- -This room version builds off of [version 1](/rooms/v1) with an improved +This room version builds on [version 1](/rooms/v1) with an improved state resolution algorithm. +## Client considerations + +There are no client considerations introduced in this room version. Clients +which implement the redaction algorithm locally should refer to the +[redactions](#redactions) section below for a full overview of the algorithm. + ## Server implementation components {{% boxes/warning %}} @@ -21,168 +27,29 @@ changing only the state resolution algorithm. ### State resolution -The room state *S*′(*E*) after an event *E* is defined in terms of the -room state *S*(*E*) before *E*, and depends on whether *E* is a state -event or a message event: - -- If *E* is a message event, then *S*′(*E*) = *S*(*E*). -- If *E* is a state event, then *S*′(*E*) is *S*(*E*), except that its - entry corresponding to *E*'s `event_type` and `state_key` is - replaced by *E*'s `event_id`. - -The room state *S*(*E*) before *E* is the *resolution* of the set of -states {*S*′(*E*1), *S*′(*E*2), …} consisting of -the states after each of *E*'s `prev_event`s -{*E*1, *E*2, …}, where the resolution of a set of -states is given in the algorithm below. - -#### Definitions - -The state resolution algorithm for version 2 rooms uses the following -definitions, given the set of room states -{*S*1, *S*2, …}: - -Power events -A *power event* is a state event with type `m.room.power_levels` or -`m.room.join_rules`, or a state event with type `m.room.member` where -the `membership` is `leave` or `ban` and the `sender` does not match the -`state_key`. The idea behind this is that power events are events that -might remove someone's ability to do something in the room. - -Unconflicted state map and conflicted state set -The *unconflicted state map* is the state where the value of each key -exists and is the same in each state *S**i*. The *conflicted -state set* is the set of all other state events. Note that the -unconflicted state map only has one event per `(event_type, state_key)`, -whereas the conflicted state set may have multiple events. - -Auth difference -The *auth difference* is calculated by first calculating the full auth -chain for each state *S**i*, that is the union of the auth -chains for each event in *S**i*, and then taking every event -that doesn't appear in every auth chain. If *C**i* is the -full auth chain of *S**i*, then the auth difference is - ∪ *C**i* −  ∩ *C**i*. - -Full conflicted set -The *full conflicted set* is the union of the conflicted state set and -the auth difference. - -Reverse topological power ordering -The *reverse topological power ordering* of a set of events is the -lexicographically smallest topological ordering based on the DAG formed -by auth events. The reverse topological power ordering is ordered from -earliest event to latest. For comparing two topological orderings to -determine which is the lexicographically smallest, the following -comparison relation on events is used: for events *x* and *y*, -*x* < *y* if - -1. *x*'s sender has *greater* power level than *y*'s sender, when - looking at their respective `auth_event`s; or -2. the senders have the same power level, but *x*'s `origin_server_ts` - is *less* than *y*'s `origin_server_ts`; or -3. the senders have the same power level and the events have the same - `origin_server_ts`, but *x*'s `event_id` is *less* than *y*'s - `event_id`. - -The reverse topological power ordering can be found by sorting the -events using Kahn's algorithm for topological sorting, and at each step -selecting, among all the candidate vertices, the smallest vertex using -the above comparison relation. - -Mainline ordering -Given an `m.room.power_levels` event *P*, the *mainline of* *P* is the -list of events generated by starting with *P* and recursively taking the -`m.room.power_levels` events from the `auth_events`, ordered such that -*P* is last. Given another event *e*, the *closest mainline event to* -*e* is the first event encountered in the mainline when iteratively -descending through the `m.room.power_levels` events in the `auth_events` -starting at *e*. If no mainline event is encountered when iteratively -descending through the `m.room.power_levels` events, then the closest -mainline event to *e* can be considered to be a dummy event that is -before any other event in the mainline of *P* for the purposes of -condition 1 below. - -The *mainline ordering based on* *P* of a set of events is the ordering, -from smallest to largest, using the following comparison relation on -events: for events *x* and *y*, *x* < *y* if - -1. the closest mainline event to *x* appears *before* the closest - mainline event to *y*; or -2. the closest mainline events are the same, but *x*'s - `origin_server_ts` is *less* than *y*'s `origin_server_ts`; or -3. the closest mainline events are the same and the events have the - same `origin_server_ts`, but *x*'s `event_id` is *less* than *y*'s - `event_id`. - -Iterative auth checks -The *iterative auth checks algorithm* takes as input an initial room -state and a sorted list of state events, and constructs a new room state -by iterating through the event list and applying the state event to the -room state if the state event is allowed by the [authorization -rules](/server-server-api#authorization-rules). -If the state event is not allowed by the authorization rules, then the -event is ignored. If a `(event_type, state_key)` key that is required -for checking the authorization rules is not present in the state, then -the appropriate state event from the event's `auth_events` is used if -the auth event is not rejected. - -#### Algorithm - -The *resolution* of a set of states is obtained as follows: - -1. Take all *power events* and any events in their auth chains, - recursively, that appear in the *full conflicted set* and order them - by the *reverse topological power ordering*. -2. Apply the *iterative auth checks algorithm*, starting from the - *unconflicted state map*, to the list of events from the previous - step to get a partially resolved state. -3. 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 obtained in step 2. -4. Apply the *iterative auth checks algorithm* on the partial resolved - state and the list of events from the previous step. -5. Update the result by replacing any event with the event with the - same key from the *unconflicted state map*, if such an event exists, - to get the final resolved state. - -#### Rejected events - -Events that have been rejected due to failing auth based on the state at -the event (rather than based on their auth chain) are handled as usual -by the algorithm, unless otherwise specified. - -Note that no events rejected due to failure to auth against their auth -chain should appear in the process, as they should not appear in state -(the algorithm only uses events that appear in either the state sets or -in the auth chain of the events in the state sets). - -{{% boxes/rationale %}} -This helps ensure that different servers' view of state is more likely -to converge, since rejection state of an event may be different. This -can happen if a third server gives an incorrect version of the state -when a server joins a room via it (either due to being faulty or -malicious). Convergence of state is a desirable property as it ensures -that all users in the room have a (mostly) consistent view of the state -of the room. If the view of the state on different servers diverges it -can lead to bifurcation of the room due to e.g. servers disagreeing on -who is in the room. - -Intuitively, using rejected events feels dangerous, however: - -1. Servers cannot arbitrarily make up state, since they still need to - pass the auth checks based on the event's auth chain (e.g. they - can't grant themselves power levels if they didn't have them - before). -2. For a previously rejected event to pass auth there must be a set of - state that allows said event. A malicious server could therefore - produce a fork where it claims the state is that particular set of - state, duplicate the rejected event to point to that fork, and send - the event. The duplicated event would then pass the auth checks. - Ignoring rejected events would therefore not eliminate any potential - attack vectors. -{{% /boxes/rationale %}} - -Rejected auth events are deliberately excluded from use in the iterative -auth checks, as auth events aren't re-authed (although non-auth events -are) during the iterative auth checks. +{{% added-in this=true %}} + +{{% rver-fragment name="v2-state-res" %}} + +## Unchanged from v1 + +The following sections have not been modified since v1, but are included for +completeness. + +### Authorization rules + +{{% rver-fragment name="v1-auth-rules" %}} + +### Event format + +Events in rooms of this version have the following structure: + +{{% definition path="api/server-server/definitions/pdu" %}} + +### Canonical JSON + +{{% rver-fragment name="v1-canonical-json" %}} + +### Redactions + +{{% rver-fragment name="v1-redactions" %}} diff --git a/content/rooms/v3.md b/content/rooms/v3.md index e1966b6e..c7fffa3b 100644 --- a/content/rooms/v3.md +++ b/content/rooms/v3.md @@ -20,6 +20,10 @@ Clients should expect to see event IDs changed from the format of `$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk` (note the lack of domain and the potentially problematic slash). +Though unchanged in this room version, clients which implement the +redaction algorithm locally should refer to the [redactions](#redactions) +section below for a full overview. + ## Server implementation components {{% boxes/warning %}} @@ -30,8 +34,8 @@ regarding client considerations is the resource that Client-Server API use cases should reference. {{% /boxes/warning %}} -Room version 3 uses the state resolution algorithm defined in [room -version 2](/rooms/v2), and the event format defined here. +Room version 3 uses the event format described here in addition to +all the remaining behaviour described by [room version 2](/rooms/v2). ### Event IDs @@ -77,24 +81,34 @@ room version during a request. ### Authorization rules for events -The authorization rules for a given event have changed in this room -version due to the change in event format: - -- The event no longer needs to be signed by the domain of the event ID - (as there is no domain in the event ID), but still needs to be - signed by the sender's domain. -- In past room versions, redactions were only permitted to enter the - DAG if the sender's domain matched the domain in the event ID being - redacted, or the sender had appropriate permissions per the power - levels. Due to servers now not being able to determine where an - event came from during event authorization, redaction events are - always accepted (provided the event is allowed by `events` and - `events_default` in the power levels). However, servers should not - apply or send redactions to clients until both the redaction event - and original event have been seen, and are valid. Servers should - only apply redactions to events where the sender's domains match, or - the sender of the redaction has the appropriate permissions per the - power levels. - -The remaining rules are the same as [room version -1](/rooms/v1#authorization-rules). +{{% added-in this=true %}} `m.room.redaction` events are no longer +explicitly part of the auth rules. They are still subject to the +minimum power level rules, but should always fall into "11. Otherwise, +allow". Instead of being authorized at the time of receipt, they are +authorized at a later stage: see the [Handling Redactions](#handling-redactions) +section below for more information. + + +{{% rver-fragment name="v3-auth-rules" withVersioning=true %}} + +### Handling redactions + + +{{% rver-fragment name="v3-handling-redactions" withVersioning=true %}} + +## Unchanged from v2 + +The following sections have not been modified since v2, but are included for +completeness. + +### State resolution + +{{% rver-fragment name="v2-state-res" %}} + +### Canonical JSON + +{{% rver-fragment name="v1-canonical-json" %}} + +### Redactions + +{{% rver-fragment name="v1-redactions" %}} diff --git a/content/rooms/v4.md b/content/rooms/v4.md index c2853a2c..d1f3347f 100644 --- a/content/rooms/v4.md +++ b/content/rooms/v4.md @@ -19,6 +19,10 @@ Clients should expect to see event IDs changed from the format of `$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg` (note the lack of domain). +Though unchanged in this room version, clients which implement the +redaction algorithm locally should refer to the [redactions](#redactions) +section below for a full overview. + ## Server implementation components {{% boxes/warning %}} @@ -42,20 +46,37 @@ being interpretted differently by some reverse proxy software, and generally made administration harder. {{% /boxes/rationale %}} -The event ID is the [reference -hash](/server-server-api#calculating-the-reference-hash-for-an-event) of -the event encoded using a variation of [Unpadded -Base64](/appendices#unpadded-base64) which replaces the 62nd and -63rd characters with `-` and `_` instead of using `+` and `/`. This -matches [RFC4648's definition of URL-safe -base64](https://tools.ietf.org/html/rfc4648#section-5). Event IDs are -still prefixed with `$` and may result in looking like -`$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg`. - -Just like in room version 3, event IDs should not be sent over -federation to servers when the room uses this room version. On the -receiving end of an event, the server should compute the relevant event -ID for itself. Room version 3 also changes the format of `auth_events` -and `prev_events` in a PDU. +{{% rver-fragment name="v4-event-explainer" %}} + +## Unchanged from v3 + +The following sections have not been modified since v3, but are included for +completeness. + +### State resolution + +{{% rver-fragment name="v2-state-res" %}} + +### Authorization rules + +{{% rver-fragment name="v3-auth-rules" %}} + +### Handling redactions + +{{% rver-fragment name="v3-handling-redactions" %}} + +### Canonical JSON + +{{% rver-fragment name="v1-canonical-json" %}} + +### Redactions + +{{% rver-fragment name="v1-redactions" %}} + +### Event format + +The event format is the same as [room version 3](/rooms/v3#event-ids), +however the event IDs in the following example are updated to reflect +the changes in this room version. {{% definition path="api/server-server/definitions/pdu_v4" %}} diff --git a/content/rooms/v5.md b/content/rooms/v5.md index 9c5ade72..117e44b8 100644 --- a/content/rooms/v5.md +++ b/content/rooms/v5.md @@ -9,9 +9,9 @@ key validity periods for events. ## Client considerations -There are no specific requirements for clients in this room version. -Clients should be aware of event ID changes in [room version -4](/rooms/v4), however. +There are no client considerations introduced in this room version. Clients +which implement the redaction algorithm locally should refer to the +[redactions](#redactions) section below for a full overview of the algorithm. ## Server implementation components @@ -28,18 +28,35 @@ Room version 5 uses the same algorithms defined in [room version ### Signing key validity period -When validating event signatures, servers MUST enforce the -`valid_until_ts` property from a key request is at least as large as the -`origin_server_ts` for the event being validated. Servers missing a copy -of the signing key MUST try to obtain one via the [GET -/\_matrix/key/v2/server](/server-server-api#get_matrixkeyv2serverkeyid) -or [POST -/\_matrix/key/v2/query](/server-server-api#post_matrixkeyv2query) -APIs. When using the `/query` endpoint, servers MUST set the -`minimum_valid_until_ts` property to prompt the notary server to attempt -to refresh the key if appropriate. - -Servers MUST use the lesser of `valid_until_ts` and 7 days into the -future when determining if a key is valid. This is to avoid a situation -where an attacker publishes a key which is valid for a significant -amount of time without a way for the homeserver owner to revoke it. +{{% rver-fragment name="v5-signing-requirements" %}} + +## Unchanged from v4 + +The following sections have not been modified since v4, but are included for +completeness. + +### State resolution + +{{% rver-fragment name="v2-state-res" %}} + +### Authorization rules + +{{% rver-fragment name="v3-auth-rules" %}} + +### Handling redactions + +{{% rver-fragment name="v3-handling-redactions" %}} + +### Event format + +{{% rver-fragment name="v4-event-explainer" %}} + +{{% definition path="api/server-server/definitions/pdu_v4" %}} + +### Canonical JSON + +{{% rver-fragment name="v1-canonical-json" %}} + +### Redactions + +{{% rver-fragment name="v1-redactions" %}} diff --git a/content/rooms/v6.md b/content/rooms/v6.md index e9cea27a..eede41d7 100644 --- a/content/rooms/v6.md +++ b/content/rooms/v6.md @@ -9,10 +9,17 @@ authorization rules performed on events. ## Client considerations -The redaction algorithm has changed from [room version 1](/rooms/v1) to -remove all rules against events of type `m.room.aliases`. Room versions -2, 3, 4, and 5 all use v1's redaction algorithm. The algorithm is -otherwise unchanged. +There are no client considerations introduced in this room version. Clients +which implement the redaction algorithm locally should refer to the +[redactions](#redactions) section below for a full overview of the algorithm. + +### Redactions + +{{% added-in this=true %}} All significant meaning for `m.room.aliases` +has been removed from the redaction algorithm. The remaining rules are +the same as past room versions. + +{{% rver-fragment name="v6-redactions" %}} ## Server implementation components @@ -29,56 +36,180 @@ in [room version 5](/rooms/v5). ### Redactions -As mentioned in the client considerations portion of this specification, -all special meaning has been removed for events of type -`m.room.aliases`. The algorithm is otherwise unchanged. +[See above](#redactions). ### Authorization rules for events -Like redactions, all rules relating specifically to events of type -`m.room.aliases` are removed. They must still pass authorization checks -relating to state events. +`m.room.redaction` events are not explicitly part of the auth rules. +They are still subject to the minimum power level rules, but should always +fall into "10. Otherwise, allow". Instead of being authorized at the time +of receipt, they are authorized at a later stage: see the +[Handling Redactions](#handling-redactions) section below for more information. -Additionally, the authorization rules for events of type -`m.room.power_levels` now include the content key `notifications`. This -new rule takes the place of the rule which checks the `events` and -`users` keys. +{{% added-in this=true %}} Rule 4, which related specifically to events +of type `m.room.aliases`, is removed. `m.room.aliases` events must still pass +authorization checks relating to state events. -For completeness, the changes to the auth rules can be represented as -follows: +{{% added-in this=true %}} Additionally, the authorization rules for events +of type `m.room.power_levels` now include the content key `notifications`. +This new rule takes the place of rule 10.4, which checked the `events` and +`users` keys. - ... +Events must be signed by the server denoted by the `sender` key. + +The types of state events that affect authorization are: + +- `m.room.create` +- `m.room.member` +- `m.room.join_rules` +- `m.room.power_levels` +- `m.room.third_party_invite` + +{{% boxes/note %}} +Power levels are inferred from defaults when not explicitly supplied. +For example, mentions of the `sender`'s power level can also refer to +the default power level for users in the room. +{{% /boxes/note %}} + +The rules are as follows: + +1. If type is `m.room.create`: + 1. If it has any previous events, reject. + 2. If the domain of the `room_id` does not match the domain of the + `sender`, reject. + 3. If `content.room_version` is present and is not a recognised + version, reject. + 4. If `content` has no `creator` field, reject. + 5. Otherwise, allow. +2. Reject if event has `auth_events` that: + 1. have duplicate entries for a given `type` and `state_key` pair + 2. have entries whose `type` and `state_key` don't match those + specified by the [auth events + selection](/server-server-api#auth-events-selection) + algorithm described in the server specification. +3. If event does not have a `m.room.create` in its `auth_events`, + reject. +4. If type is `m.room.member`: + 1. If no `state_key` key or `membership` key in `content`, reject. + 2. If `membership` is `join`: + 1. If the only previous event is an `m.room.create` and the + `state_key` is the creator, allow. + 2. If the `sender` does not match `state_key`, reject. + 3. If the `sender` is banned, reject. + 4. If the `join_rule` is `invite` then allow if membership + state is `invite` or `join`. + 5. If the `join_rule` is `public`, allow. + 6. Otherwise, reject. + 3. If `membership` is `invite`: + 1. If `content` has `third_party_invite` key: + 1. If *target user* is banned, reject. + 2. If `content.third_party_invite` does not have a `signed` + key, reject. + 3. If `signed` does not have `mxid` and `token` keys, + reject. + 4. If `mxid` does not match `state_key`, reject. + 5. If there is no `m.room.third_party_invite` event in the + current room state with `state_key` matching `token`, + reject. + 6. If `sender` does not match `sender` of the + `m.room.third_party_invite`, reject. + 7. 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: + 1. A single public key in the `public_key` field. + 2. A list of public keys in the `public_keys` field. + 8. Otherwise, reject. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If *target user*'s current membership state is `join` or + `ban`, reject. + 4. If the `sender`'s power level is greater than or equal to + the *invite level*, allow. + 5. Otherwise, reject. + 4. If `membership` is `leave`: + 1. If the `sender` matches `state_key`, allow if and only if + that user's current membership state is `invite` or `join`. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If the *target user*'s current membership state is `ban`, + and the `sender`'s power level is less than the *ban level*, + reject. + 4. If the `sender`'s power level is greater than or equal to + the *kick level*, and the *target user*'s power level is + less than the `sender`'s power level, allow. + 5. Otherwise, reject. + 5. If `membership` is `ban`: + 1. If the `sender`'s current membership state is not `join`, + reject. + 2. If the `sender`'s power level is greater than or equal to + the *ban level*, and the *target user*'s power level is less + than the `sender`'s power level, allow. + 3. Otherwise, reject. + 6. Otherwise, the membership is unknown. Reject. +5. If the `sender`'s current membership state is not `join`, reject. +6. If type is `m.room.third_party_invite`: + 1. Allow if and only if `sender`'s current power level is greater + than or equal to the *invite level*. +7. If the event type's *required power level* is greater than the + `sender`'s power level, reject. +8. If the event has a `state_key` that starts with an `@` and does not + match the `sender`, reject. +9. If type is `m.room.power_levels`: + 1. 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. + 2. If there is no previous `m.room.power_levels` event in the room, + allow. + 3. For the keys `users_default`, `events_default`, `state_default`, + `ban`, `redact`, `kick`, `invite` check if they were added, + changed or removed. For each found alteration: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 4. For each entry being added, changed or removed in both the + `events`, `users`, and `notifications` keys: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 5. For each entry being changed under the `users` key, other than + the `sender`'s own entry: + 1. If the current value is equal to the `sender`'s current + power level, reject. + 6. Otherwise, allow. +10. Otherwise, allow. + +{{% boxes/note %}} +Some consequences of these rules: + +- Unless you are a member of the room, the only permitted operations + (apart from the initial create/join) are: joining a public room; + accepting or rejecting an invitation to a room. +- To unban somebody, you must have power level greater than or equal + to both the kick *and* ban levels, *and* greater than the target + user's power level. +{{% /boxes/note %}} - -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. +### Canonical JSON - ... +{{% rver-fragment name="v6-canonical-json" %}} - If type is `m.room.power_levels`: +## Unchanged from v5 - ... +The following sections have not been modified since v5, but are included for +completeness. - - * For each entry being added, changed or removed in both the `events` and `users` keys: - + * For each entry being added, changed or removed in the `events`, `users`, and `notifications` keys: +### State resolution - i. If the current value is higher than the `sender`'s current power level, reject. +{{% rver-fragment name="v2-state-res" %}} - ii. If the new value is higher than the `sender`'s current power level, reject. +### Event format - ... +{{% rver-fragment name="v4-event-explainer" %}} -The remaining rules are the same as in [room version -3](/rooms/v3#authorization-rules-for-events) (the last inherited room -version to specify the authorization rules). +{{% definition path="api/server-server/definitions/pdu_v4" %}} -### Canonical JSON +### Handling redactions -Servers MUST strictly enforce the JSON format specified in the -[appendices](/appendices#canonical-json). This translates to a -400 `M_BAD_JSON` error on most endpoints, or discarding of events over -federation. For example, the Federation API's `/send` endpoint would -discard the event whereas the Client Server API's `/send/{eventType}` -endpoint would return a `M_BAD_JSON` error. +{{% rver-fragment name="v3-handling-redactions" %}} \ No newline at end of file diff --git a/content/rooms/v7.md b/content/rooms/v7.md index 477dd492..62c7fac6 100644 --- a/content/rooms/v7.md +++ b/content/rooms/v7.md @@ -12,6 +12,10 @@ as a possible join rule and membership state. This is the first room version to support knocking completely. As such, users will not be able to knock on rooms which are not based off v7. +Though unchanged in this room version, clients which implement the +redaction algorithm locally should refer to the [redactions](#redactions) +section below for a full overview. + ## Server implementation components {{% boxes/warning %}} @@ -26,27 +30,183 @@ Room version 7 adds new authorization rules for events to support knocking. [Room version 6](/rooms/v6) has details of other authorization rule changes, as do the versions v6 is based upon. -{{% added-in this=true %}} For checks perfomed upon `m.room.member` events, the following conditions -are added in context: - - If type is `m.room.member`: - - ... - - * If `membership` is `ban`: - - ... - - * If `membership` is `knock`: - - i. If the `join_rule` is anything other than `knock`, reject. - - ii. If `sender` does not match `state_key`, reject. - - iii. If the `sender`'s current membership is not `ban`, `invite`, or `join`, allow. - - iv. Otherwise, reject. - - ... - -The remaining rules are the same as in [room version 6](/rooms/v6#authorization-rules-for-events). +### Authorization rules + +{{% added-in this=true %}} For checks perfomed upon `m.room.member` events, a +new point for `membership=knock` is added. + +Events must be signed by the server denoted by the `sender` key. + +`m.room.redaction` events are not explicitly part of the auth rules. +They are still subject to the minimum power level rules, but should always +fall into "10. Otherwise, allow". Instead of being authorized at the time +of receipt, they are authorized at a later stage: see the +[Redactions](#redactions) section below for more information. + +The types of state events that affect authorization are: + +- `m.room.create` +- `m.room.member` +- `m.room.join_rules` +- `m.room.power_levels` +- `m.room.third_party_invite` + +{{% boxes/note %}} +Power levels are inferred from defaults when not explicitly supplied. +For example, mentions of the `sender`'s power level can also refer to +the default power level for users in the room. +{{% /boxes/note %}} + +The rules are as follows: + +1. If type is `m.room.create`: + 1. If it has any previous events, reject. + 2. If the domain of the `room_id` does not match the domain of the + `sender`, reject. + 3. If `content.room_version` is present and is not a recognised + version, reject. + 4. If `content` has no `creator` field, reject. + 5. Otherwise, allow. +2. Reject if event has `auth_events` that: + 1. have duplicate entries for a given `type` and `state_key` pair + 2. have entries whose `type` and `state_key` don't match those + specified by the [auth events + selection](/server-server-api#auth-events-selection) + algorithm described in the server specification. +3. If event does not have a `m.room.create` in its `auth_events`, + reject. +4. If type is `m.room.member`: + 1. If no `state_key` key or `membership` key in `content`, reject. + 2. If `membership` is `join`: + 1. If the only previous event is an `m.room.create` and the + `state_key` is the creator, allow. + 2. If the `sender` does not match `state_key`, reject. + 3. If the `sender` is banned, reject. + 4. If the `join_rule` is `invite` then allow if membership + state is `invite` or `join`. + 5. If the `join_rule` is `public`, allow. + 6. Otherwise, reject. + 3. If `membership` is `invite`: + 1. If `content` has `third_party_invite` key: + 1. If *target user* is banned, reject. + 2. If `content.third_party_invite` does not have a `signed` + key, reject. + 3. If `signed` does not have `mxid` and `token` keys, + reject. + 4. If `mxid` does not match `state_key`, reject. + 5. If there is no `m.room.third_party_invite` event in the + current room state with `state_key` matching `token`, + reject. + 6. If `sender` does not match `sender` of the + `m.room.third_party_invite`, reject. + 7. 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: + 1. A single public key in the `public_key` field. + 2. A list of public keys in the `public_keys` field. + 8. Otherwise, reject. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If *target user*'s current membership state is `join` or + `ban`, reject. + 4. If the `sender`'s power level is greater than or equal to + the *invite level*, allow. + 5. Otherwise, reject. + 4. If `membership` is `leave`: + 1. If the `sender` matches `state_key`, allow if and only if + that user's current membership state is `invite` or `join`. + 2. If the `sender`'s current membership state is not `join`, + reject. + 3. If the *target user*'s current membership state is `ban`, + and the `sender`'s power level is less than the *ban level*, + reject. + 4. If the `sender`'s power level is greater than or equal to + the *kick level*, and the *target user*'s power level is + less than the `sender`'s power level, allow. + 5. Otherwise, reject. + 5. If `membership` is `ban`: + 1. If the `sender`'s current membership state is not `join`, + reject. + 2. If the `sender`'s power level is greater than or equal to + the *ban level*, and the *target user*'s power level is less + than the `sender`'s power level, allow. + 3. Otherwise, reject. + 6. If `membership` is `knock`: + 1. If the `join_rule` is anything other than `knock`, reject. + 2. If `sender` does not match `state_key`, reject. + 3. If the `sender`'s current membership is not `ban`, `invite`, + or `join`, allow. + 7. Otherwise, the membership is unknown. Reject. +5. If the `sender`'s current membership state is not `join`, reject. +6. If type is `m.room.third_party_invite`: + 1. Allow if and only if `sender`'s current power level is greater + than or equal to the *invite level*. +7. If the event type's *required power level* is greater than the + `sender`'s power level, reject. +8. If the event has a `state_key` that starts with an `@` and does not + match the `sender`, reject. +9. If type is `m.room.power_levels`: + 1. 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. + 2. If there is no previous `m.room.power_levels` event in the room, + allow. + 3. For the keys `users_default`, `events_default`, `state_default`, + `ban`, `redact`, `kick`, `invite` check if they were added, + changed or removed. For each found alteration: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 4. For each entry being added, changed or removed in both the + `events`, `users`, and `notifications` keys: + 1. If the current value is higher than the `sender`'s current + power level, reject. + 2. If the new value is higher than the `sender`'s current power + level, reject. + 5. For each entry being changed under the `users` key, other than + the `sender`'s own entry: + 1. If the current value is equal to the `sender`'s current + power level, reject. + 6. Otherwise, allow. +10. Otherwise, allow. + +{{% boxes/note %}} +Some consequences of these rules: + +- Unless you are a member of the room, the only permitted operations + (apart from the initial create/join) are: joining a public room; + accepting or rejecting an invitation to a room. +- To unban somebody, you must have power level greater than or equal + to both the kick *and* ban levels, *and* greater than the target + user's power level. +{{% /boxes/note %}} + +## Unchanged from v6 + +The following sections have not been modified since v6, but are included for +completeness. + +### State resolution + +{{% rver-fragment name="v2-state-res" %}} + +### Event format + +{{% rver-fragment name="v4-event-explainer" %}} + +{{% definition path="api/server-server/definitions/pdu_v4" %}} + +### Canonical JSON + +{{% rver-fragment name="v6-canonical-json" %}} + +### Signing key validity period + +{{% rver-fragment name="v5-signing-requirements" %}} + +### Redactions + +{{% rver-fragment name="v3-handling-redactions" %}} + +{{% rver-fragment name="v6-redactions" %}} diff --git a/layouts/shortcodes/rver-fragment.html b/layouts/shortcodes/rver-fragment.html new file mode 100644 index 00000000..12c4d122 --- /dev/null +++ b/layouts/shortcodes/rver-fragment.html @@ -0,0 +1,25 @@ +{{/* + + This template is used to render a "room version fragment". Fragments are blocks of + text which describe a portion of the room version specification. They should be + prefixed with the room version which introduces the fragment, and be reusable for + two or more versions. + + The `name` parameter is the file name without extension. + + The `withVersioning` parameter is optional and defaults to false. When true, any + mentions of "New in this version" from the `added-in` shortcode are removed prior + to rendering. This is useful if needing to use a fragment where part of it describes + new functionality in a given room version but isn't new for subsequent versions. + +*/}} + +{{ $name := .Params.name }} +{{ $withVersioning := .Params.withVersioning }} + +{{ $page := .Site.GetPage (path.Join .Page.Dir "fragments" (printf "%s%s" $name ".md"))}} +{{ $content := $page.Content }} +{{ if not $withVersioning }} + {{ $content = (replace $content "[New in this version]" "") }} +{{ end }} +{{ $content | safeHTML }}