Room versions 8 and 9: Restricted rooms (#3387)
* Room versions 8 and 9: Restricted rooms MSCs: * https://github.com/matrix-org/matrix-doc/pull/3083 * https://github.com/matrix-org/matrix-doc/pull/3289 * https://github.com/matrix-org/matrix-doc/pull/3375 * Changelogs * Capitalization Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com> * Remove verbiage for spaces because they don't exist * Iterations on text * Another clarification * Make error code descriptions consistent * Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Incorporate from merge * Misc language update per review * Update accuracy before splitting auth rules * fix wtf moment * Fix up v8 and v9 to match "fully specify room versions" * Scope auth events selection to room version * Apply consistency * Add changelogs * Review part 1 * Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Split out redaction sections * Clarify general case of join conditions Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
parent
3475ef62ab
commit
6c4aabd053
22 changed files with 772 additions and 39 deletions
1
changelogs/client_server/newsfragments/3387.feature
Normal file
1
changelogs/client_server/newsfragments/3387.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add support for `restricted` rooms as per [MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083), [MSC3289](https://github.com/matrix-org/matrix-doc/pull/3289), and [MSC3375](https://github.com/matrix-org/matrix-doc/pull/3375).
|
1
changelogs/room_versions/newsfragments/3387.feature.1
Normal file
1
changelogs/room_versions/newsfragments/3387.feature.1
Normal file
|
@ -0,0 +1 @@
|
|||
Add Room Version 8 as per [MSC3289](https://github.com/matrix-org/matrix-doc/pull/3289).
|
1
changelogs/room_versions/newsfragments/3387.feature.2
Normal file
1
changelogs/room_versions/newsfragments/3387.feature.2
Normal file
|
@ -0,0 +1 @@
|
|||
Add Room Version 9 as per [MSC3375](https://github.com/matrix-org/matrix-doc/pull/3375).
|
1
changelogs/server_server/newsfragments/3387.feature
Normal file
1
changelogs/server_server/newsfragments/3387.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add support for `restricted` rooms as per [MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083), [MSC3289](https://github.com/matrix-org/matrix-doc/pull/3289), and [MSC3375](https://github.com/matrix-org/matrix-doc/pull/3375).
|
|
@ -1984,6 +1984,12 @@ This room can only be joined if you were invited, and allows anyone to
|
|||
request an invite to the room. Note that this join rule is only available
|
||||
in room versions [which support knocking](/rooms/#feature-matrix).
|
||||
|
||||
{{% added-in v="1.2" %}} `restricted`
|
||||
This room can be joined if you were invited or if you are a member of another
|
||||
room listed in the join rules. If the server cannot verify membership for any
|
||||
of the listed rooms then you can only join with an invite. Note that this rule
|
||||
is only expected to work in room versions [which support it](/rooms/#feature-matrix).
|
||||
|
||||
The allowable state transitions of membership are:
|
||||
|
||||

|
||||
|
@ -2033,6 +2039,51 @@ server chose to auto-accept.
|
|||
|
||||
{{% http-api spec="client-server" api="knocking" %}}
|
||||
|
||||
##### Restricted rooms
|
||||
|
||||
{{% added-in v="1.2" %}}
|
||||
|
||||
Restricted rooms are rooms with a `join_rule` of `restricted`. These rooms
|
||||
are accompanied by "allow conditions" as described in the
|
||||
[`m.room.join_rules`](#mroomjoin_rules) state event.
|
||||
|
||||
If the user has an invite to the room then the restrictions will not affect
|
||||
them. They should be able to join by simply accepting the invite.
|
||||
|
||||
When joining without an invite, the server MUST verify that the requesting
|
||||
user meets at least one of the conditions. If no conditions can be verified
|
||||
or no conditions are satisfied, the user will not be able to join. When the
|
||||
join is happening over federation, the remote server will check the conditions
|
||||
before accepting the join. See the [Server-Server Spec](/server-server-api/#restricted-rooms)
|
||||
for more information.
|
||||
|
||||
If the room is `restricted` but no valid conditions are presented then the
|
||||
room is effectively invite only.
|
||||
|
||||
The user does not need to maintain the conditions in order to stay a member
|
||||
of the room: the conditions are only checked/evaluated during the join process.
|
||||
|
||||
###### Conditions
|
||||
|
||||
Currently there is only one condition available: `m.room_membership`. This
|
||||
condition requires the user trying to join the room to be a *joined* member
|
||||
of another room (specifically, the `room_id` accompanying the condition). For
|
||||
example, if `!restricted:example.org` wanted to allow joined members of
|
||||
`!other:example.org` to join, `!restricted:example.org` would have the following
|
||||
`content` for [`m.room.join_rules`](#mroomjoin_rules):
|
||||
|
||||
```json
|
||||
{
|
||||
"join_rule": "restricted",
|
||||
"allow": [
|
||||
{
|
||||
"room_id": "!other:example.org",
|
||||
"type": "m.room_membership"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Leaving rooms
|
||||
|
||||
A user can leave a room to stop receiving events for that room. A user
|
||||
|
|
|
@ -36,9 +36,10 @@ Alternatively, consider flipping the column/row organization to be features
|
|||
up top and versions on the left.
|
||||
-->
|
||||
|
||||
| Feature \ Version | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
|-------------------|---|---|---|---|---|---|---|
|
||||
| **Knocking** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔ |
|
||||
| Feature \ Version | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|
||||
|-------------------|---|---|---|---|---|---|---|---|---|
|
||||
| **Knocking** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔ | ✔ | ✔ |
|
||||
| **Restricted join rules** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔ | ✔ |
|
||||
|
||||
## Complete list of room versions
|
||||
|
||||
|
@ -68,6 +69,10 @@ The available room versions are:
|
|||
- [Version 6](/rooms/v6) - **Stable**. Alters several
|
||||
authorization rules for events.
|
||||
- [Version 7](/rooms/v7) - **Stable**. Introduces knocking.
|
||||
- [Version 8](/rooms/v8) - **Stable**. Adds a join rule to allow members
|
||||
of another room to join without invite.
|
||||
- [Version 9](/rooms/v9) - **Stable**. Builds on v8 to fix issues when
|
||||
redacting some membership events.
|
||||
|
||||
## Room version grammar
|
||||
|
||||
|
|
160
content/rooms/fragments/v8-auth-rules.md
Normal file
160
content/rooms/fragments/v8-auth-rules.md
Normal file
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
toc_hide: true
|
||||
---
|
||||
|
||||
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 `content` has a `join_authorised_via_users_server`
|
||||
key:
|
||||
1. If the event is not validly signed by the user ID denoted
|
||||
by the key, reject.
|
||||
3. 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 `restricted`:
|
||||
1. If membership state is `join` or `invite`, allow.
|
||||
2. If the `join_authorised_via_users_server` key in `content`
|
||||
is not a user with sufficient permission to invite other
|
||||
users, reject.
|
||||
3. Otherwise, allow.
|
||||
6. If the `join_rule` is `public`, allow.
|
||||
7. Otherwise, reject.
|
||||
4. 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.
|
||||
5. 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.
|
||||
6. 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.
|
||||
7. 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.
|
||||
8. 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 %}}
|
|
@ -199,6 +199,10 @@ completeness.
|
|||
|
||||
{{% rver-fragment name="v4-event-format" %}}
|
||||
|
||||
### Handling redactions
|
||||
|
||||
{{% rver-fragment name="v3-handling-redactions" %}}
|
||||
|
||||
### Canonical JSON
|
||||
|
||||
{{% rver-fragment name="v6-canonical-json" %}}
|
||||
|
@ -209,6 +213,4 @@ completeness.
|
|||
|
||||
### Redactions
|
||||
|
||||
{{% rver-fragment name="v3-handling-redactions" %}}
|
||||
|
||||
{{% rver-fragment name="v6-redactions" %}}
|
||||
|
|
123
content/rooms/v8.md
Normal file
123
content/rooms/v8.md
Normal file
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
title: Room Version 8
|
||||
type: docs
|
||||
weight: 60
|
||||
---
|
||||
|
||||
This room version builds on [version 7](/rooms/v7) to introduce a new
|
||||
join rule that allows members to join the room based on membership in
|
||||
another room.
|
||||
|
||||
{{% boxes/warning %}}
|
||||
This room version is known to have issues relating to redactions of member
|
||||
join events. [Room version 9](/rooms/v9) should be preferred over v8 when
|
||||
creating rooms.
|
||||
{{% /boxes/warning %}}
|
||||
|
||||
## Client considerations
|
||||
|
||||
Clients are encouraged to expose the option for the join rule in their
|
||||
user interface for supported room versions.
|
||||
|
||||
The new join rule, `restricted`, is described in the
|
||||
[Client-Server API](/client-server-api/#restricted-rooms).
|
||||
|
||||
Clients which implement the redaction algorithm locally should refer to the
|
||||
[redactions](#redactions) section below for a full overview.
|
||||
|
||||
### Redactions
|
||||
|
||||
{{% added-in this=true %}} `m.room.join_rules` events now keep `allow` in addition to other
|
||||
keys in `content` when being redacted.
|
||||
|
||||
{{% boxes/warning %}}
|
||||
[Room version 9](/rooms/v9) adds additional cases of protected properties for behaviour
|
||||
related to restricted rooms (the functionality introduced in v8). v9 is preferred over
|
||||
v8 when creating new rooms.
|
||||
{{% /boxes/warning %}}
|
||||
|
||||
The full redaction algorithm follows.
|
||||
|
||||
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 keys `join_rule`, `allow`.
|
||||
- `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`.
|
||||
|
||||
## Server implementation components
|
||||
|
||||
{{% boxes/warning %}}
|
||||
The information contained in this section is strictly for server
|
||||
implementors. Applications which use the Client-Server API are generally
|
||||
unaffected by the intricacies contained here. The section above
|
||||
regarding client considerations is the resource that Client-Server API
|
||||
use cases should reference.
|
||||
{{% /boxes/warning %}}
|
||||
|
||||
Room version 8 adds a new join rule to allow members of a room to join another
|
||||
room without invite. Otherwise, the room version inherits all properties of
|
||||
[Room version 7](/rooms/v7).
|
||||
|
||||
### Authorization rules
|
||||
|
||||
{{% added-in this=true %}} For checks performed upon `m.room.member` events, new
|
||||
points for handling `content.join_authorised_via_users_server` are added (Rule 4.2
|
||||
and 4.3.5).
|
||||
|
||||
{{% rver-fragment name="v8-auth-rules" %}}
|
||||
|
||||
### Redactions
|
||||
|
||||
[See above](#redactions).
|
||||
|
||||
## Unchanged from v7
|
||||
|
||||
The following sections have not been modified since v7, but are included for
|
||||
completeness.
|
||||
|
||||
### State resolution
|
||||
|
||||
{{% rver-fragment name="v2-state-res" %}}
|
||||
|
||||
### Event IDs
|
||||
|
||||
{{% rver-fragment name="v4-event-ids" %}}
|
||||
|
||||
### Event format
|
||||
|
||||
{{% rver-fragment name="v4-event-format" %}}
|
||||
|
||||
### Handling redactions
|
||||
|
||||
{{% rver-fragment name="v3-handling-redactions" %}}
|
||||
|
||||
### Canonical JSON
|
||||
|
||||
{{% rver-fragment name="v6-canonical-json" %}}
|
||||
|
||||
### Signing key validity period
|
||||
|
||||
{{% rver-fragment name="v5-signing-requirements" %}}
|
119
content/rooms/v9.md
Normal file
119
content/rooms/v9.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
title: Room Version 9
|
||||
type: docs
|
||||
weight: 60
|
||||
---
|
||||
|
||||
This room version builds on [version 8](/rooms/v8) to add additional redaction
|
||||
rules that were unintentionally missed when incorporating v8.
|
||||
|
||||
## Client considerations
|
||||
|
||||
See [room version 8](/rooms/v8) for specific details regarding the addition of
|
||||
restricted rooms.
|
||||
|
||||
Clients which implement the redaction algorithm locally should refer to the
|
||||
[redactions](#redactions) section below for a full overview.
|
||||
|
||||
### Redactions
|
||||
|
||||
{{% added-in this=true %}} `m.room.member` now keep `join_authorised_via_users_server`
|
||||
in addition to other keys in `content` when being redacted.
|
||||
|
||||
{{% boxes/rationale %}}
|
||||
Without the `join_authorised_via_users_server` property, redacted join events
|
||||
can become invalid when verifying the auth chain of a given event, thus creating
|
||||
a split-brain scenario where the user is able to speak from one server's
|
||||
perspective but most others will continually reject their events.
|
||||
|
||||
This can theoretically be worked around with a rejoin to the room, being careful
|
||||
not to use the faulty events as `prev_events`, though instead it is encouraged
|
||||
to use v9 rooms over v8 rooms to outright avoid the situation.
|
||||
|
||||
[Issue #3373](https://github.com/matrix-org/matrix-doc/issues/3373) has further
|
||||
information.
|
||||
{{% /boxes/rationale %}}
|
||||
|
||||
The full redaction algorithm follows.
|
||||
|
||||
{{% rver-fragment name="v3-handling-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 keys `membership`, `join_authorised_via_users_server`.
|
||||
- `m.room.create` allows key `creator`.
|
||||
- `m.room.join_rules` allows keys `join_rule`, `allow`.
|
||||
- `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`.
|
||||
|
||||
## Server implementation components
|
||||
|
||||
{{% boxes/warning %}}
|
||||
The information contained in this section is strictly for server
|
||||
implementors. Applications which use the Client-Server API are generally
|
||||
unaffected by the intricacies contained here. The section above
|
||||
regarding client considerations is the resource that Client-Server API
|
||||
use cases should reference.
|
||||
{{% /boxes/warning %}}
|
||||
|
||||
Room version 8 added a new `restricted` join rule to allow members of a room
|
||||
to join another room without invite. Room version 9 is based upon v8 with the
|
||||
following considerations.
|
||||
|
||||
### Redactions
|
||||
|
||||
[See above](#redactions).
|
||||
|
||||
## Unchanged from v8
|
||||
|
||||
The following sections have not been modified since v8, but are included for
|
||||
completeness.
|
||||
|
||||
### State resolution
|
||||
|
||||
{{% rver-fragment name="v2-state-res" %}}
|
||||
|
||||
### Authorization rules
|
||||
|
||||
{{% rver-fragment name="v8-auth-rules" %}}
|
||||
|
||||
### Event IDs
|
||||
|
||||
{{% rver-fragment name="v4-event-ids" %}}
|
||||
|
||||
### Event format
|
||||
|
||||
{{% rver-fragment name="v4-event-format" %}}
|
||||
|
||||
### Handling redactions
|
||||
|
||||
{{% rver-fragment name="v3-handling-redactions" %}}
|
||||
|
||||
### Canonical JSON
|
||||
|
||||
{{% rver-fragment name="v6-canonical-json" %}}
|
||||
|
||||
### Signing key validity period
|
||||
|
||||
{{% rver-fragment name="v5-signing-requirements" %}}
|
|
@ -407,21 +407,25 @@ the sender permission to send the event. The `auth_events` for the
|
|||
`m.room.create` event in a room is empty; for other events, it should be
|
||||
the following subset of the room state:
|
||||
|
||||
- The `m.room.create` event.
|
||||
- The `m.room.create` event.
|
||||
|
||||
- The current `m.room.power_levels` event, if any.
|
||||
- The current `m.room.power_levels` event, if any.
|
||||
|
||||
- The sender's current `m.room.member` event, if any.
|
||||
- The sender's current `m.room.member` event, if any.
|
||||
|
||||
- If type is `m.room.member`:
|
||||
- If type is `m.room.member`:
|
||||
|
||||
- The target's current `m.room.member` event, if any.
|
||||
- If `membership` is `join` or `invite`, the current
|
||||
`m.room.join_rules` event, if any.
|
||||
- If membership is `invite` and `content` contains a
|
||||
`third_party_invite` property, the current
|
||||
`m.room.third_party_invite` event with `state_key` matching
|
||||
`content.third_party_invite.signed.token`, if any.
|
||||
- The target's current `m.room.member` event, if any.
|
||||
- If `membership` is `join` or `invite`, the current
|
||||
`m.room.join_rules` event, if any.
|
||||
- If membership is `invite` and `content` contains a
|
||||
`third_party_invite` property, the current
|
||||
`m.room.third_party_invite` event with `state_key` matching
|
||||
`content.third_party_invite.signed.token`, if any.
|
||||
- If `content.join_authorised_via_users_server` is present,
|
||||
and the [room version supports restricted rooms](/rooms/#feature-matrix),
|
||||
then the `m.room.member` event with `state_key` matching
|
||||
`content.join_authorised_via_users_server`.
|
||||
|
||||
#### Rejection
|
||||
|
||||
|
@ -744,19 +748,47 @@ The joining server is expected to add or replace the `origin`,
|
|||
`origin_server_ts`, and `event_id` on the templated event received by
|
||||
the resident server. This event is then signed by the joining server.
|
||||
|
||||
To complete the join handshake, the joining server must now submit this
|
||||
new event to a resident homeserver, by using the `PUT /send_join`
|
||||
To complete the join handshake, the joining server submits this new event
|
||||
to the resident server it used for `GET /make_join`, using the `PUT /send_join`
|
||||
endpoint.
|
||||
|
||||
The resident homeserver then accepts this event into the room's event
|
||||
graph, and responds to the joining server with the full set of state for
|
||||
the newly-joined room. The resident server must also send the event to
|
||||
other servers participating in the room.
|
||||
The resident homeserver then adds its signature to this event and
|
||||
accepts it into the room's event graph. The joining server receives
|
||||
the full set of state for the newly-joined room as well as the freshly
|
||||
signed membership event. The resident server must also send the event
|
||||
to other servers participating in the room.
|
||||
|
||||
{{% http-api spec="server-server" api="joins-v1" %}}
|
||||
|
||||
{{% http-api spec="server-server" api="joins-v2" %}}
|
||||
|
||||
### Restricted rooms
|
||||
|
||||
Restricted rooms are described in detail in the
|
||||
[client-server API](/client-server-api/#restricted-rooms) and are available
|
||||
in room versions [which support restricted join rules](/rooms/#feature-matrix).
|
||||
|
||||
A resident server processing a request to join a restricted room must
|
||||
ensure that the joining server satisfies at least one of the conditions
|
||||
specified by `m.room.join_rules`. If no conditions are available, or none
|
||||
match the required schema, then the joining server is considered to have
|
||||
failed all conditions.
|
||||
|
||||
The resident server uses a 400 `M_UNABLE_TO_AUTHORISE_JOIN` error on
|
||||
`/make_join` and `/send_join` to denote that the resident server is unable
|
||||
to validate any of the conditions, usually because the resident server
|
||||
does not have state information about rooms required by the conditions.
|
||||
|
||||
The resident server uses a 400 `M_UNABLE_TO_GRANT_JOIN` error on `/make_join`
|
||||
and `/send_join` to denote that the joining server should try a different
|
||||
server. This is typically because the resident server can see that the
|
||||
joining user satisfies one of the conditions, though the resident server
|
||||
would be unable to meet the auth rules governing `join_authorised_via_users_server`
|
||||
on the resulting `m.room.member` event.
|
||||
|
||||
If the joining server fails all conditions then a 403 `M_FORBIDDEN` error
|
||||
is used by the resident server.
|
||||
|
||||
## Knocking upon a room
|
||||
|
||||
Rooms can permit knocking through the join rules, and if permitted this
|
||||
|
|
|
@ -95,6 +95,7 @@ paths:
|
|||
|
||||
- The room is invite-only and the user was not invited.
|
||||
- The user has been banned from the room.
|
||||
- The room is restricted and the user failed to satisfy any of the conditions.
|
||||
examples:
|
||||
application/json: {
|
||||
"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."}
|
||||
|
@ -182,6 +183,7 @@ paths:
|
|||
|
||||
- The room is invite-only and the user was not invited.
|
||||
- The user has been banned from the room.
|
||||
- The room is restricted and the user failed to satisfy any of the conditions.
|
||||
examples:
|
||||
application/json: {
|
||||
"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."}
|
||||
|
|
|
@ -55,4 +55,14 @@ properties:
|
|||
properties: []
|
||||
example:
|
||||
$ref: "../examples/minimal_pdu.json"
|
||||
event:
|
||||
type: object
|
||||
title: SignedMembershipEvent
|
||||
x-addedInMatrixVersion: "1.2"
|
||||
description: |-
|
||||
Required if the room version [supports restricted join rules](/rooms/#feature-matrix). The signed
|
||||
copy of the membership event sent to other servers by the resident server, including the resident
|
||||
server's signature.
|
||||
example:
|
||||
$ref: "../examples/minimal_pdu.json"
|
||||
required: ["auth_chain", "state", "origin"]
|
||||
|
|
28
data/api/server-server/examples/pdu_v4_join_membership.json
Normal file
28
data/api/server-server/examples/pdu_v4_join_membership.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$ref": "unsigned_pdu_base.json",
|
||||
"hashes": {
|
||||
"sha256": "thishashcoversallfieldsincasethisisredacted"
|
||||
},
|
||||
"signatures": {
|
||||
"example.com": {
|
||||
"ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus"
|
||||
},
|
||||
"resident.example.com": {
|
||||
"ed25519:other_key_version": "a different signature"
|
||||
}
|
||||
},
|
||||
"auth_events": [
|
||||
"$urlsafe_base64_encoded_eventid",
|
||||
"$a-different-event-id"
|
||||
],
|
||||
"prev_events": [
|
||||
"$urlsafe_base64_encoded_eventid",
|
||||
"$a-different-event-id"
|
||||
],
|
||||
"type": "m.room.member",
|
||||
"state_key": "@alice:example.com",
|
||||
"content": {
|
||||
"membership": "join",
|
||||
"join_authorised_via_users_server": "@arbitrary:resident.example.com"
|
||||
}
|
||||
}
|
|
@ -115,6 +115,18 @@ paths:
|
|||
type: string
|
||||
description: The value `join`.
|
||||
example: "join"
|
||||
join_authorised_via_users_server:
|
||||
type: string
|
||||
x-addedInMatrixVersion: "1.2"
|
||||
description: |-
|
||||
Required if the room is [restricted](/client-server-api/#restricted-rooms)
|
||||
and is joining through one of the conditions available. If the
|
||||
user is responding to an invite, this is not required.
|
||||
|
||||
An arbitrary user ID belonging to the resident server in
|
||||
the room being joined that is able to issue invites to other
|
||||
users. This is used in later validation of the auth rules for
|
||||
the `m.room.member` event.
|
||||
required: ['membership']
|
||||
required:
|
||||
- state_key
|
||||
|
@ -134,18 +146,37 @@ paths:
|
|||
"origin_server_ts": 1549041175876,
|
||||
"sender": "@someone:example.org",
|
||||
"content": {
|
||||
"membership": "join"
|
||||
"membership": "join",
|
||||
"join_authorised_via_users_server": "@anyone:resident.example.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
400:
|
||||
description: |-
|
||||
The request is invalid or the room the server is attempting
|
||||
The request is invalid, the room the server is attempting
|
||||
to join has a version that is not listed in the `ver`
|
||||
parameters.
|
||||
parameters, or the server was unable to validate
|
||||
[restricted room conditions](#restricted-rooms).
|
||||
|
||||
The error should be passed through to clients so that they
|
||||
may give better feedback to users.
|
||||
|
||||
New in `v1.2`, the following error conditions might happen:
|
||||
|
||||
If the room is [restricted](/client-server-api/#restricted-rooms)
|
||||
and none of the conditions can be validated by the server then
|
||||
the `errcode` `M_UNABLE_TO_AUTHORISE_JOIN` must be used. This can
|
||||
happen if the server does not know about any of the rooms listed
|
||||
as conditions, for example.
|
||||
|
||||
`M_UNABLE_TO_GRANT_JOIN` is returned to denote that a different
|
||||
server should be attempted for the join. This is typically because
|
||||
the resident server can see that the joining user satisfies one or
|
||||
more conditions, such as in the case of
|
||||
[restricted rooms](/client-server-api/#restricted-rooms), but the
|
||||
resident server would be unable to meet the auth rules governing
|
||||
`join_authorised_via_users_server` on the resulting `m.room.member`
|
||||
event.
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "../client-server/definitions/errors/error.yaml"
|
||||
|
@ -162,7 +193,20 @@ paths:
|
|||
"error": "Your homeserver does not support the features required to join this room",
|
||||
"room_version": "3"
|
||||
}
|
||||
403:
|
||||
schema:
|
||||
$ref: "../client-server/definitions/errors/error.yaml"
|
||||
description: |-
|
||||
The room that the joining server is attempting to join does not permit the user
|
||||
to join.
|
||||
examples:
|
||||
application/json: {
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "You are not invited to this room",
|
||||
}
|
||||
404:
|
||||
schema:
|
||||
$ref: "../client-server/definitions/errors/error.yaml"
|
||||
description: |-
|
||||
The room that the joining server is attempting to join is unknown
|
||||
to the receiving server.
|
||||
|
@ -240,6 +284,23 @@ paths:
|
|||
type: string
|
||||
description: The value `join`.
|
||||
example: "join"
|
||||
join_authorised_via_users_server:
|
||||
type: string
|
||||
x-addedInMatrixVersion: "1.2"
|
||||
description: |-
|
||||
Required if the room is [restricted](/client-server-api/#restricted-rooms)
|
||||
and is joining through one of the conditions available. If the
|
||||
user is responding to an invite, this is not required.
|
||||
|
||||
An arbitrary user ID belonging to the resident server in
|
||||
the room being joined that is able to issue invites to other
|
||||
users. This is used in later validation of the auth rules for
|
||||
the `m.room.member` event.
|
||||
|
||||
The resident server which owns the provided user ID must have a
|
||||
valid signature on the event. If the resident server is receiving
|
||||
the `/send_join` request, the signature must be added before sending
|
||||
or persisting the event to other servers.
|
||||
required: ['membership']
|
||||
required:
|
||||
- state_key
|
||||
|
@ -256,7 +317,8 @@ paths:
|
|||
"origin_server_ts": 1549041175876,
|
||||
"sender": "@someone:example.org",
|
||||
"content": {
|
||||
"membership": "join"
|
||||
"membership": "join",
|
||||
"join_authorised_via_users_server": "@anyone:resident.example.org"
|
||||
}
|
||||
}
|
||||
responses:
|
||||
|
@ -278,6 +340,7 @@ paths:
|
|||
{
|
||||
"origin": "matrix.org",
|
||||
"auth_chain": [{"$ref": "examples/minimal_pdu.json"}],
|
||||
"state": [{"$ref": "examples/minimal_pdu.json"}]
|
||||
"state": [{"$ref": "examples/minimal_pdu.json"}],
|
||||
"event": {"$ref": "examples/pdu_v4_join_membership.json"}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -103,6 +103,23 @@ paths:
|
|||
type: string
|
||||
description: The value `join`.
|
||||
example: "join"
|
||||
join_authorised_via_users_server:
|
||||
type: string
|
||||
x-addedInMatrixVersion: "1.2"
|
||||
description: |-
|
||||
Required if the room is [restricted](/client-server-api/#restricted-rooms)
|
||||
and is joining through one of the conditions available. If the
|
||||
user is responding to an invite, this is not required.
|
||||
|
||||
An arbitrary user ID belonging to the resident server in
|
||||
the room being joined that is able to issue invites to other
|
||||
users. This is used in later validation of the auth rules for
|
||||
the `m.room.member` event.
|
||||
|
||||
The resident server which owns the provided user ID must have a
|
||||
valid signature on the event. If the resident server is receiving
|
||||
the `/send_join` request, the signature must be added before sending
|
||||
or persisting the event to other servers.
|
||||
required: ['membership']
|
||||
required:
|
||||
- state_key
|
||||
|
@ -119,10 +136,52 @@ paths:
|
|||
"origin_server_ts": 1549041175876,
|
||||
"sender": "@someone:example.org",
|
||||
"content": {
|
||||
"membership": "join"
|
||||
"membership": "join",
|
||||
"join_authorised_via_users_server": "@anyone:resident.example.org"
|
||||
}
|
||||
}
|
||||
responses:
|
||||
400:
|
||||
description: |-
|
||||
The request is invalid in some way.
|
||||
|
||||
The error should be passed through to clients so that they
|
||||
may give better feedback to users.
|
||||
|
||||
New in `v1.2`, the following error conditions might happen:
|
||||
|
||||
If the room is [restricted](/client-server-api/#restricted-rooms)
|
||||
and none of the conditions can be validated by the server then
|
||||
the `errcode` `M_UNABLE_TO_AUTHORISE_JOIN` must be used. This can
|
||||
happen if the server does not know about any of the rooms listed
|
||||
as conditions, for example.
|
||||
|
||||
`M_UNABLE_TO_GRANT_JOIN` is returned to denote that a different
|
||||
server should be attempted for the join. This is typically because
|
||||
the resident server can see that the joining user satisfies one or
|
||||
more conditions, such as in the case of
|
||||
[restricted rooms](/client-server-api/#restricted-rooms), but the
|
||||
resident server would be unable to meet the auth rules governing
|
||||
`join_authorised_via_users_server` on the resulting `m.room.member`
|
||||
event.
|
||||
schema:
|
||||
$ref: "../client-server/definitions/errors/error.yaml"
|
||||
examples:
|
||||
application/json: {
|
||||
"errcode": "M_UNABLE_TO_GRANT_JOIN",
|
||||
"error": "This server cannot send invites to you."
|
||||
}
|
||||
403:
|
||||
schema:
|
||||
$ref: "../client-server/definitions/errors/error.yaml"
|
||||
description: |-
|
||||
The room that the joining server is attempting to join does not permit the user
|
||||
to join.
|
||||
examples:
|
||||
application/json: {
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "You are not invited to this room",
|
||||
}
|
||||
200:
|
||||
description: |-
|
||||
The full state for the room, having accepted the join event.
|
||||
|
@ -132,5 +191,6 @@ paths:
|
|||
application/json: {
|
||||
"origin": "matrix.org",
|
||||
"auth_chain": [{"$ref": "examples/minimal_pdu.json"}],
|
||||
"state": [{"$ref": "examples/minimal_pdu.json"}]
|
||||
"state": [{"$ref": "examples/minimal_pdu.json"}],
|
||||
"event": {"$ref": "examples/pdu_v4_join_membership.json"}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$ref": "core/state_event.json",
|
||||
"type": "m.room.join_rules",
|
||||
"state_key": "",
|
||||
"content": {
|
||||
"join_rule": "restricted",
|
||||
"allow": [
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": "!other:example.org"
|
||||
},
|
||||
{
|
||||
"type": "m.room_membership",
|
||||
"room_id": "!elsewhere:example.org"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$ref": "m.room.member.yaml",
|
||||
"content": {
|
||||
"membership": "join",
|
||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||
"displayname": "Alice Margatroid",
|
||||
"join_authorised_via_users_server": "@bob:other.example.org"
|
||||
},
|
||||
"unsigned": {
|
||||
"age": 1234
|
||||
}
|
||||
}
|
|
@ -2,15 +2,17 @@
|
|||
allOf:
|
||||
- $ref: core-event-schema/state_event.yaml
|
||||
description: |
|
||||
A room may be `public` meaning anyone can join the room without any prior action.
|
||||
Alternatively, it can be `invite` meaning that a user who wishes to join the room
|
||||
must first receive an invite to the room from someone already inside of the room.
|
||||
`knock` means that users are able to ask for permission to join the room, where
|
||||
they are either allowed (invited) or denied (kicked/banned) access. Join rules
|
||||
of `knock` are otherwise the same as `invite`: the user needs an explicit invite
|
||||
to join the room.
|
||||
|
||||
Currently, `private` is a reserved keyword which is not implemented.
|
||||
A room may have one of the following designations:
|
||||
* `public` - anyone can join the room without any prior action.
|
||||
* `invite` - a user must first receive an invite from someone already in the room
|
||||
in order to join.
|
||||
* `knock` - a user can request an invite to the room. They can be allowed (invited)
|
||||
or denied (kicked/banned) access. Otherwise, users need to be invited in. Only
|
||||
available in rooms [which support knocking](/rooms/#feature-matrix).
|
||||
* `restricted` - anyone able to satisfy at least one of the allow conditions is
|
||||
able to join the room without prior action. Otherwise, an invite is required.
|
||||
Only available in rooms [which support the join rule](/rooms/#feature-matrix).
|
||||
* `private` - reserved without implementation. No significant meaning.
|
||||
properties:
|
||||
content:
|
||||
properties:
|
||||
|
@ -21,7 +23,37 @@ properties:
|
|||
- knock
|
||||
- invite
|
||||
- private
|
||||
- restricted
|
||||
type: string
|
||||
allow:
|
||||
x-addedInMatrixVersion: "1.2"
|
||||
description: |-
|
||||
For `restricted` rooms, the conditions the user will be tested against. The
|
||||
user needs only to satisfy one of the conditions to join the `restricted`
|
||||
room. If the user fails to meet any condition, or the condition is unable
|
||||
to be confirmed as satisfied, then the user requires an invite to join the
|
||||
room. Improper or no `allow` conditions on a `restricted` join rule imply
|
||||
the room is effectively invite-only (no conditions can be satisfied).
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
title: AllowCondition
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: |-
|
||||
The type of condition:
|
||||
* `m.room_membership` - the user satisfies the condition if they are
|
||||
joined to the referenced room.
|
||||
enum: ['m.room_membership']
|
||||
room_id:
|
||||
type: string
|
||||
description: |-
|
||||
Required if `type` is `m.room_membership`. The room ID to check the
|
||||
user's membership against. If the user is joined to this room, they
|
||||
satisfy the condition and thus are permitted to join the `restricted`
|
||||
room.
|
||||
required: ['type']
|
||||
required:
|
||||
- join_rule
|
||||
type: object
|
||||
|
|
|
@ -63,6 +63,18 @@ properties:
|
|||
is_direct:
|
||||
description: Flag indicating if the room containing this event was created with the intention of being a direct chat. See [Direct Messaging](/client-server-api/#direct-messaging).
|
||||
type: boolean
|
||||
join_authorised_via_users_server:
|
||||
x-addedInMatrixVersion: "1.2"
|
||||
type: string
|
||||
description: |-
|
||||
Usually found on `join` events, this field is used to denote which homeserver (through representation of a user with sufficient power level)
|
||||
authorised the user's join. More information about this field can be found in the [Restricted Rooms Specification](#restricted-rooms).
|
||||
|
||||
Client and server implementations should be aware of the [signing implications](/rooms/v8/#authorization-rules) of including this
|
||||
field in further events: in particular, the event must be signed by the server which
|
||||
owns the user ID in the field. When copying the membership event's `content`
|
||||
(for profile updates and similar) it is therefore encouraged to exclude this
|
||||
field in the copy, as otherwise the event might fail event authorization.
|
||||
reason:
|
||||
x-addedInMatrixVersion: "1.1"
|
||||
type: string
|
||||
|
|
|
@ -1 +1 @@
|
|||
<mxfile host="app.diagrams.net" modified="2021-04-28T19:35:50.494Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36" etag="-IOh23FjJiPnGlGWLseU" version="14.6.6" type="device"><diagram id="4a_pTli-mcEMNPq0ciXK" name="Page-1">3Vvdb6M4EP9rIt09NMIGA3ncZtvbh73TSn243X1ZOYmT0BIcOaZJ9q8/E0z4cggEgulVqoqHsbHHv/nwjDsyp5vDXwxv13/TBfFH0FgcRubnEYTAAa74E1GOMWViopiwYt5CMqWEF+83kURDUkNvQXY5Rk6pz71tnjinQUDmPEfDjNF9nm1J/fxXt3hFSoSXOfbL1H+9BV/HVBc6Kf0L8Vbr5MvAnsRvNjhhlivZrfGC7jMk82lkThmlPH7aHKbEj4SXyCXu93zh7XlijAS8TodvBvr96euj/xL83G+Dn2gaTvgDkMO8Yz+UKxYjvFIvkJPmx0QSZCEEI5uU8TVd0QD7Tyn1kdEwWJDoc4ZopTxfKd0KIhDEV8L5Ue4yDjkVpDXf+PJt/M3oQxfXKEk7GrI5qVqYxApmK8Ir+OB5JwSECd0Qzo6iHyM+5t57fh5YYml15pNdPzGGjxmGrZAf32VG/hYRBINUCziRmJBKYdqFrWvGLx7iGSStzFJS0gkOTaBhqaDhEyykogEb5ODx75nnH9FQY4hk8/NBDn1qHJNGIGTwPcMZtX9kX6b9Tq2kY4dIhKgmFC01FCUGjDEwHDMeqhk6S3Cy3DycoDUZ25kfx8qPGM9cDpLCrinqLVD4rG1Wot4CqIo/j/q0dzIdulzuCC/06EYzSorhBe8eL6tFHvT7teB52eITWvbCXaqM3zthnByqQXcRI9ApSBjJ9j51XcCQtHXGbSV8KgzlpNdYVHaFETEoG0VO6/nNm799cH8DO1JysRduOyW/P/4hVG3qDGuJGRK/YIxRxjOA29wC6NEt1ASMWQmYB2NsI6ulW+gBMSXAKEPMRvZySQP+jDeeH23OVIjbI5E5+Yfs72NMLUu7MXVVehcGujUvF5HVDMhAVu1kr34Uz2xnqWsrWautNks7rdrkQekLQoVDiqldX5R+KrY8hreMVhfOfG8+grYv5vI4E/KwV9FTxPOLhT7Z6XVp8Caf5ug66lg1VavtobsdKsq6Jca5EMH3sduXNq7HYKT2xqFr0QgaeigCnEEGr05W0426mq4tqVEXMBei1340vZzAUmevBuVGzWKuT7sbVUadGu1lO53RFnfCSWdG1rVdq52dPddnCgkjB+WHiFdVSv31arCNjx3F5RPW1ccjLXBraaLrIcuy6yGrs4yDsrCly82nHrtRhHdLbksHhK7mqFxogrsYLGTVg1XTWgUo1CqQWV2hs41Kfn21CqhMwA/Ae9+SNGp6tM3Fal+I/064N8cdK0lHZycRzN+iIU2BbVr5ohqYoIEAVXkuG0qlqGWV4eOitrpeJUy7A4ZffjAHFQzcEBf+L6B0NUpwEGoZJfRgpspJhbeAKozSoJMK5zBKW1IhuRXT8SWKvouC+m9YJEF6RpBPB06YMEknl1k8FP8R0JOmiN8N2cwI+7NC4qChxMsmpl1JqXCGVKXCoELY9r2EbSpzEdIAxMmIU+MBz4QwB5qQKJSVaiebu/E8r+Fmmyw0oAFJSPGsHV1JkKtxjuvCm3JuTQP0wt08YFUfPM+3EtT8Gi/Jle2S/pjrVug3O3ZmIY7ZvGNI171OejXcmvRy4ISTwoETXcmkwEr+ewBaNNNr+DF7+s8M5tN/</diagram></mxfile>
|
||||
<mxfile host="Electron" modified="2022-01-18T16:40:44.155Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/16.1.2 Chrome/96.0.4664.55 Electron/16.0.5 Safari/537.36" etag="XO6otxm1zp3-pSiWzgkD" version="16.1.2" type="device"><diagram id="4a_pTli-mcEMNPq0ciXK" name="Page-1">1Vvbbts4EP0aA7sPMUTq6sfGTdIusrvFGtht+lLQNm0rlUWDpnzp15eyKOvGeEnJkpwAQcTRkBKHZ2YOh8rAHK8PTxRtVn+SOQ4G0JgfBubHAYTABR7/E0uOQmJAkEiW1J8LWSaY+D9xqiikkT/H24IiIyRg/qYonJEwxDNWkCFKyb6otiBB8akbtMQVwWSGgqr0P3/OVonUg24m/4T95Sp9MnBGyZ01SpXFTLYrNCf7nMh8GJhjSghLrtaHMQ5i66V2Sfo9vnH3/GIUh0ylwxfD/vnh+T6YhN/2m/CbPY5G7A6IYXYoiMSM+QivxA/FS7Njagk854YRTULZiixJiIKHTHpPSRTOcfw4g7cynWdCNlwIuPAVM3YUq4wiRrhoxdaBuFudkni9LYnoDF+ah4AGokvMLujBRC+eS+4BwmBPmKwxo0euQHGAmL8rggAJLC3PeqLrB0rRMaew4fZj29zIX2IBVxB+AUcCE8IrTKe0dHr6/CJ5g7SVm0omOsFBBxqWDBoBRtwqPWADH3z2NXf9Eg81hLZofjyIoU+NY9oIuQ2+5jTj9kv+Ztbv1Eo71kcif44aFC1FKAoMGEMLABFb9NBZgZPlFeEErdHQyf24VnHEZCpikAx2uqi3QOmxjnkR9RawL+kXUZ/1Tl+HLBZbzEo9ruMZFcfww53Pqm5RBP1+xXUmG3SCz57nS8Xgt8OU4YMKRqBbsrAt2vssdQFDyFa5tJXqyTBUsJ62qZwLQcQgdBAnrccf/uzH+8o3sC0nN0aO18zJ28c/hLJFnaJeOEOaF4yhncsMoF5aAO2lBUXAmHqAuTOGjm2ZN4+YCmCkFFMrXi5IyB7R2g/ixRlz+/s4Did/4X07wdSyeg+mnszvorBvzyswMleRkYG836XdWnE988qxWtnNGi22WVlr2TLflMfYdmmbAnv3GGmmSmKP4S8G0An4K9xPuRmcZXyFgoDsuT2hMT1W78Ydv9MowNvqvd820TTwZwM4jjHEZr/3mwxhrWzodrRJshRd8urb9WZoqvokH+cN7t/Far+1cO3RGOWFs7V5jH3rJAa4N0h7a5ZDiskXtpd8VQGjynuv7ul/YMcJJn9/frr/5/Hfw/dnRl8+S5JvvEsNSbxNleaN0707NOWLq5U1+k0Sbj5JGIrQqRVrSuvacvCx/i/WwAJPubv50FMtxcrrsDdFB81y1bp3OijdP/WYv5s5YjGGtxfC4ai9pO85ntXM+c5HjaXSp2sXh0imWSlid0ogDBn+crsR47z9KOeJeDdivJvtR5GUXOYkXeDy2txCDYKWowbBqxXZpGe5ffHTjC9o0YU65dwOIKRflvWgCVqJbLalBivd4zlQOp6zzcuH0o5xUb+/4zkoPXO6gTSvERVr12QKpO4TDnaY+TPUzEna2vSfeXi7X1uYVvEcGYzsGwGqtKAgmH05wfd9VtrwnO3dgFjzxDY+gGsW5tvHmXlTxKAGR6z9GU+fQNKnDK5tN6QMHcSsailC1MXeUynizKl6K0WkufLKHxF1fSje/xdGKYXPGfLhwDDlMemUMCsb5ZCcPIX/rvF6iml1p5xZHGhaXCnEaByoljaUpsTYUGJspy1jm9IKRr4wbqhWwvtkMLBW3fs6mec1Wm/SiYYkxKkoeWu3o4qIPsvxPFirUqfL1kvfpgLr8i70/FWOXL/Hj0Srcal/0tUJ9PMIR3TWDNGqX1Prs61RJ5tPOCptPm29qkpJvw0882b2XyiJevbPPObDLw==</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
Loading…
Add table
Add a link
Reference in a new issue