You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
9.9 KiB
Markdown
214 lines
9.9 KiB
Markdown
# Restricting room membership based on space membership
|
|
|
|
A desirable feature is to give room admins the power to restrict membership of
|
|
their room based on the membership of one or more spaces from
|
|
[MSC1772](https://github.com/matrix-org/matrix-doc/pull/1772), for example:
|
|
|
|
> members of the #doglovers space can join this room without an invitation<sup id="a1">[1](#f1)</sup>
|
|
|
|
## Proposal
|
|
|
|
In a future room version a new `join_rule` (`restricted`) will be used to reflect
|
|
a cross between `invite` and `public` join rules. The content of the join rules
|
|
would include the rooms to trust for membership. For example:
|
|
|
|
```json
|
|
{
|
|
"type": "m.room.join_rules",
|
|
"state_key": "",
|
|
"content": {
|
|
"join_rule": "restricted",
|
|
"allow": [
|
|
{
|
|
"type": "m.room_membership",
|
|
"room_id": "!mods:example.org"
|
|
},
|
|
{
|
|
"type": "m.room_membership",
|
|
"room_id": "!users:example.org"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
This means that a user must be a member of the `!mods:example.org` room or
|
|
`!users:example.org` room in order to join without an invite<sup id="a2">[2](#f2)</sup>.
|
|
Membership in a single allowed room is enough.
|
|
|
|
If the `allow` key is an empty list (or not a list at all), then no users are
|
|
allowed to join without an invite. Each entry is expected to be an object with the
|
|
following keys:
|
|
|
|
* `type`: `"m.room_membership"` to describe that we are allowing access via room
|
|
membership. Future MSCs may define other types.
|
|
* `room_id`: The room ID to check the membership of.
|
|
|
|
Any entries in the list which do not match the expected format are ignored. Thus,
|
|
if all entries are invalid, the list behaves as if empty and all users without
|
|
an invite are rejected.
|
|
|
|
When an homeserver receives a `/join` request from a client or a `/make_join` /
|
|
`/send_join` request from another homeserver, the request should only be permitted
|
|
if the user has a valid invite or is in one of the listed rooms. If the user is
|
|
not a member of at least one of the rooms, the homeserver should return an error
|
|
response with HTTP status code of 403 and an `errcode` of `M_FORBIDDEN`.
|
|
|
|
It is possible for a homeserver receiving a `/make_join` / `/send_join` request
|
|
to not know if the user is in any of the allowed room (due to not participating
|
|
in them). In this case the homeserver should reject the join, the requesting
|
|
server may wish to attempt to join via another homeserver. If no servers are in
|
|
an allowed room its membership cannot be checked (and this is a misconfiguration).
|
|
|
|
From the perspective of the [auth rules](https://spec.matrix.org/unstable/rooms/v1/#authorization-rules),
|
|
the `restricted` join rule has the same behavior as `public`, with the additional
|
|
caveat that servers must ensure that:
|
|
|
|
* The user's previous membership was `invite` or `join`, or
|
|
* The `m.room.member` event with a `membership` of `join` has a valid signature
|
|
from a homeserver whose users have the power to issue invites.
|
|
|
|
As normal, the above check is also performed against the current room state during
|
|
[soft-failure](https://matrix.org/docs/spec/server_server/r0.1.4#soft-failure),
|
|
to guard against servers issuing new membership events by referencing old
|
|
events in the room.
|
|
|
|
Note that the homeservers whose users can issue invites are trusted to confirm
|
|
that the `allow` rules were properly checked (since this cannot easily be
|
|
enforced over federation by event authorisation).<sup id="a3">[3](#f3)</sup>
|
|
|
|
## Summary of the behaviour of join rules
|
|
|
|
See the [join rules](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-join-rules)
|
|
specification for full details; the summary below is meant to highlight the differences
|
|
between `public`, `invite`, and `restricted`. Note that all join rules are subject
|
|
to `ban` and `server_acls`.
|
|
|
|
* `public`: anyone can join, as today.
|
|
* `invite`: only people with membership `invite` can join, as today.
|
|
* `knock`: the same as `invite`, except anyone can knock. See
|
|
[MSC2403](https://github.com/matrix-org/matrix-doc/pull/2403).
|
|
* `private`: This is reserved, but unspecified.
|
|
* `restricted`: the same as `public`, with the additional caveat that servers must
|
|
verify the `m.room.member` event is signed by a homeserver whose users may issue
|
|
invites if the joining member was not previously invited or joined into the room.
|
|
|
|
## Security considerations
|
|
|
|
Increased trust to enforce the join rules during calls to `/join`, `/make_join`,
|
|
and `/send_join` is placed in the homeservers whose users can issue invites.
|
|
Although it is possible for those homeservers to issue a join event in bad faith,
|
|
there is no real-world benefit to doing this as those homeservers could easily
|
|
side-step the restriction by issuing an invite first anyway.
|
|
|
|
## Unstable prefix
|
|
|
|
The `restricted` join rule will be included in a future room version to allow
|
|
servers and clients to opt-into the new functionality.
|
|
|
|
During development, an unstable room version of `org.matrix.msc3083.v2` will be used.
|
|
Since the room version namespaces the behaviour, the `allow` key and value, as well
|
|
as the `restricted` join rule value do not need unstable prefixes.
|
|
|
|
## Alternatives
|
|
|
|
It may seem that just having the `allow` key with `public` join rules is enough
|
|
(as originally suggested in [MSC2962](https://github.com/matrix-org/matrix-doc/pull/2962)),
|
|
but there are concerns that changing the behaviour of a pre-existing a `public`
|
|
join rule may cause security issues in older implementations (that do not yet
|
|
understand the new behaviour). This could be solved by introducing a new room
|
|
version, thus it seems clearer to introduce a new join rule -- `restricted`.
|
|
|
|
Using an `allow` key with the `invite` join rules to broaden who can join was rejected
|
|
as an option since it requires weakening the [auth rules](https://spec.matrix.org/unstable/rooms/v1/#authorization-rules).
|
|
From the perspective of the auth rules, the `restricted` join rule is identical
|
|
to `public` with additional checks on the signature of the event.
|
|
|
|
## Future extensions
|
|
|
|
### Checking room membership over federation
|
|
|
|
If a homeserver is not in an allowed room (and thus doesn't know the
|
|
membership of it) then the server cannot enforce the membership checks while
|
|
generating a join event. Peeking over federation, as described in
|
|
[MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444),
|
|
could be used to establish if the user is in any of the proper rooms.
|
|
|
|
This would then delegate power out to a (potentially) untrusted server, giving that
|
|
the peek server significant power. For example, a poorly chosen peek
|
|
server could lie about the room membership and add an `@evil_user:example.org`
|
|
to an allowed room to gain membership to a room.
|
|
|
|
As iterated above, this MSC recommends rejecting the join, potentially allowing
|
|
the requesting homeserver to retry via another homeserver.
|
|
|
|
### Kicking users out when they leave the allowed room
|
|
|
|
In the above example, suppose `@bob:server.example` leaves `!users:example.org`:
|
|
should they be removed from the room? Likely not, by analogy with what happens
|
|
when you switch the join rules from public to invite. Join rules currently govern
|
|
joins, not existing room membership.
|
|
|
|
It is left to a future MSC to consider this, but some potential thoughts are
|
|
given below.
|
|
|
|
If you assume that a user *should* be removed in this case, one option is to
|
|
leave the departure up to Bob's server `server.example`, but this places a
|
|
relatively high level of trust in that server. Additionally, if `server.example`
|
|
were offline, other users in the room would still see Bob in the room (and their
|
|
servers would attempt to send message traffic to it).
|
|
|
|
Another consideration is that users may have joined via a direct invite, not via
|
|
access through a room.
|
|
|
|
Fixing this is thorny. Some sort of annotation on the membership events might
|
|
help. but it's unclear what the desired semantics are:
|
|
|
|
* Assuming that users in an allowed room are *not* kicked when that room is
|
|
removed from `allow`, are those users then given a pass to remain
|
|
in the room indefinitely? What happens if the room is added back to
|
|
`allow` and *then* the user leaves it?
|
|
* Suppose a user joins a room via an allowed room (RoomA). Later, RoomB is added
|
|
to the `allow` list and RoomA is removed. What should happen when the
|
|
user leaves RoomB? Are they exempt from the kick?
|
|
|
|
It is possible that completely different state should be kept, or a different
|
|
`m.room.member` state could be used in a more reasonable way to track this.
|
|
|
|
### Inheriting join rules
|
|
|
|
If an allowed room is a space and you make a parent space invite-only, should that
|
|
(optionally?) cascade into child rooms? This would have some of the same problems
|
|
as inheriting power levels, as discussed in [MSC2962](https://github.com/matrix-org/matrix-doc/pull/2962).
|
|
|
|
### Additional allow types
|
|
|
|
Future MSCs may wish to define additional values for the `type` argument, potentially
|
|
restricting access via:
|
|
|
|
* MXIDs or servers.
|
|
* A shared secret (room password).
|
|
|
|
These are just examples are not fully thought through for this MSC, but it should
|
|
be possible to add these behaviors in the future.
|
|
|
|
## Footnotes
|
|
|
|
<a id="f1"/>[1]: The converse restriction, "anybody can join, provided they are not members
|
|
of the '#catlovers' space" is less useful since:
|
|
|
|
1. Users in the banned room could simply leave it at any time
|
|
2. This functionality is already partially provided by
|
|
[Moderation policy lists](https://matrix.org/docs/spec/client_server/r0.6.1#moderation-policy-lists). [↩](#a1)
|
|
|
|
<a id="f2"/>[2]: Note that there is nothing stopping users sending and
|
|
receiving invites in `public` rooms today, and they work as you might expect.
|
|
The only difference is that you are not *required* to hold an invite when
|
|
joining the room. [↩](#a2)
|
|
|
|
<a id="f3"/>[3]: This has the downside of increased centralisation, as some
|
|
homeservers that are already in the room may not issue a join event for another
|
|
user on that server. (It must go through the `/make_join` / `/send_join` flow of
|
|
a server whose users may issue invites.) This is considered a reasonable
|
|
trade-off. [↩](#a3)
|