add default_power_level_content_override config option. (#12618)

Co-authored-by: Matthew Hodgson <matthew@matrix.org>
This commit is contained in:
Andy Balaam 2022-05-12 11:41:35 +01:00 committed by GitHub
parent 409573f6d0
commit de1e599b9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 381 additions and 1 deletions

View file

@ -0,0 +1 @@
Add a `default_power_level_content_override` config option to set default room power levels per room preset.

View file

@ -2468,6 +2468,40 @@ push:
# #
#encryption_enabled_by_default_for_room_type: invite #encryption_enabled_by_default_for_room_type: invite
# Override the default power levels for rooms created on this server, per
# room creation preset.
#
# The appropriate dictionary for the room preset will be applied on top
# of the existing power levels content.
#
# Useful if you know that your users need special permissions in rooms
# that they create (e.g. to send particular types of state events without
# needing an elevated power level). This takes the same shape as the
# `power_level_content_override` parameter in the /createRoom API, but
# is applied before that parameter.
#
# Valid keys are some or all of `private_chat`, `trusted_private_chat`
# and `public_chat`. Inside each of those should be any of the
# properties allowed in `power_level_content_override` in the
# /createRoom API. If any property is missing, its default value will
# continue to be used. If any property is present, it will overwrite
# the existing default completely (so if the `events` property exists,
# the default event power levels will be ignored).
#
#default_power_level_content_override:
# private_chat:
# "events":
# "com.example.myeventtype" : 0
# "m.room.avatar": 50
# "m.room.canonical_alias": 50
# "m.room.encryption": 100
# "m.room.history_visibility": 100
# "m.room.name": 50
# "m.room.power_levels": 100
# "m.room.server_acl": 100
# "m.room.tombstone": 100
# "events_default": 1
# Uncomment to allow non-server-admin users to create groups on this server # Uncomment to allow non-server-admin users to create groups on this server
# #

View file

@ -3315,6 +3315,32 @@ room_list_publication_rules:
room_id: "*" room_id: "*"
action: allow action: allow
``` ```
---
Config option: `default_power_level_content_override`
The `default_power_level_content_override` option controls the default power
levels for rooms.
Useful if you know that your users need special permissions in rooms
that they create (e.g. to send particular types of state events without
needing an elevated power level). This takes the same shape as the
`power_level_content_override` parameter in the /createRoom API, but
is applied before that parameter.
Note that each key provided inside a preset (for example `events` in the example
below) will overwrite all existing defaults inside that key. So in the example
below, newly-created private_chat rooms will have no rules for any event types
except `com.example.foo`.
Example configuration:
```yaml
default_power_level_content_override:
private_chat: { "events": { "com.example.foo" : 0 } }
trusted_private_chat: null
public_chat: null
```
--- ---
## Opentracing ## ## Opentracing ##
Configuration options related to Opentracing support. Configuration options related to Opentracing support.

View file

@ -63,6 +63,19 @@ class RoomConfig(Config):
"Invalid value for encryption_enabled_by_default_for_room_type" "Invalid value for encryption_enabled_by_default_for_room_type"
) )
self.default_power_level_content_override = config.get(
"default_power_level_content_override",
None,
)
if self.default_power_level_content_override is not None:
for preset in self.default_power_level_content_override:
if preset not in vars(RoomCreationPreset).values():
raise ConfigError(
"Unrecognised room preset %s in default_power_level_content_override"
% preset
)
# We validate the actual overrides when we try to apply them.
def generate_config_section(self, **kwargs: Any) -> str: def generate_config_section(self, **kwargs: Any) -> str:
return """\ return """\
## Rooms ## ## Rooms ##
@ -83,4 +96,38 @@ class RoomConfig(Config):
# will also not affect rooms created by other servers. # will also not affect rooms created by other servers.
# #
#encryption_enabled_by_default_for_room_type: invite #encryption_enabled_by_default_for_room_type: invite
# Override the default power levels for rooms created on this server, per
# room creation preset.
#
# The appropriate dictionary for the room preset will be applied on top
# of the existing power levels content.
#
# Useful if you know that your users need special permissions in rooms
# that they create (e.g. to send particular types of state events without
# needing an elevated power level). This takes the same shape as the
# `power_level_content_override` parameter in the /createRoom API, but
# is applied before that parameter.
#
# Valid keys are some or all of `private_chat`, `trusted_private_chat`
# and `public_chat`. Inside each of those should be any of the
# properties allowed in `power_level_content_override` in the
# /createRoom API. If any property is missing, its default value will
# continue to be used. If any property is present, it will overwrite
# the existing default completely (so if the `events` property exists,
# the default event power levels will be ignored).
#
#default_power_level_content_override:
# private_chat:
# "events":
# "com.example.myeventtype" : 0
# "m.room.avatar": 50
# "m.room.canonical_alias": 50
# "m.room.encryption": 100
# "m.room.history_visibility": 100
# "m.room.name": 50
# "m.room.power_levels": 100
# "m.room.server_acl": 100
# "m.room.tombstone": 100
# "events_default": 1
""" """

View file

@ -149,6 +149,10 @@ class RoomCreationHandler:
) )
preset_config["encrypted"] = encrypted preset_config["encrypted"] = encrypted
self._default_power_level_content_override = (
self.config.room.default_power_level_content_override
)
self._replication = hs.get_replication_data_handler() self._replication = hs.get_replication_data_handler()
# linearizer to stop two upgrades happening at once # linearizer to stop two upgrades happening at once
@ -1042,9 +1046,19 @@ class RoomCreationHandler:
for invitee in invite_list: for invitee in invite_list:
power_level_content["users"][invitee] = 100 power_level_content["users"][invitee] = 100
# Power levels overrides are defined per chat preset # If the user supplied a preset name e.g. "private_chat",
# we apply that preset
power_level_content.update(config["power_level_content_override"]) power_level_content.update(config["power_level_content_override"])
# If the server config contains default_power_level_content_override,
# and that contains information for this room preset, apply it.
if self._default_power_level_content_override:
override = self._default_power_level_content_override.get(preset_config)
if override is not None:
power_level_content.update(override)
# Finally, if the user supplied specific permissions for this room,
# apply those.
if power_level_content_override: if power_level_content_override:
power_level_content.update(power_level_content_override) power_level_content.update(power_level_content_override)

View file

@ -1116,6 +1116,264 @@ class RoomMessagesTestCase(RoomBase):
self.assertEqual(200, channel.code, msg=channel.result["body"]) self.assertEqual(200, channel.code, msg=channel.result["body"])
class RoomPowerLevelOverridesTestCase(RoomBase):
"""Tests that the power levels can be overridden with server config."""
user_id = "@sid1:red"
servlets = [
admin.register_servlets,
room.register_servlets,
login.register_servlets,
]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.admin_user_id = self.register_user("admin", "pass")
self.admin_access_token = self.login("admin", "pass")
def power_levels(self, room_id: str) -> Dict[str, Any]:
return self.helper.get_state(
room_id, "m.room.power_levels", self.admin_access_token
)
def test_default_power_levels_with_room_override(self) -> None:
"""
Create a room, providing power level overrides.
Confirm that the room's power levels reflect the overrides.
See https://github.com/matrix-org/matrix-spec/issues/492
- currently we overwrite each key of power_level_content_override
completely.
"""
room_id = self.helper.create_room_as(
self.user_id,
extra_content={
"power_level_content_override": {"events": {"custom.event": 0}}
},
)
self.assertEqual(
{
"custom.event": 0,
},
self.power_levels(room_id)["events"],
)
@unittest.override_config(
{
"default_power_level_content_override": {
"public_chat": {"events": {"custom.event": 0}},
}
},
)
def test_power_levels_with_server_override(self) -> None:
"""
With a server configured to modify the room-level defaults,
Create a room, without providing any extra power level overrides.
Confirm that the room's power levels reflect the server-level overrides.
Similar to https://github.com/matrix-org/matrix-spec/issues/492,
we overwrite each key of power_level_content_override completely.
"""
room_id = self.helper.create_room_as(self.user_id)
self.assertEqual(
{
"custom.event": 0,
},
self.power_levels(room_id)["events"],
)
@unittest.override_config(
{
"default_power_level_content_override": {
"public_chat": {
"events": {"server.event": 0},
"ban": 13,
},
}
},
)
def test_power_levels_with_server_and_room_overrides(self) -> None:
"""
With a server configured to modify the room-level defaults,
create a room, providing different overrides.
Confirm that the room's power levels reflect both overrides, and
choose the room overrides where they clash.
"""
room_id = self.helper.create_room_as(
self.user_id,
extra_content={
"power_level_content_override": {"events": {"room.event": 0}}
},
)
# Room override wins over server config
self.assertEqual(
{"room.event": 0},
self.power_levels(room_id)["events"],
)
# But where there is no room override, server config wins
self.assertEqual(13, self.power_levels(room_id)["ban"])
class RoomPowerLevelOverridesInPracticeTestCase(RoomBase):
"""
Tests that we can really do various otherwise-prohibited actions
based on overriding the power levels in config.
"""
user_id = "@sid1:red"
def test_creator_can_post_state_event(self) -> None:
# Given I am the creator of a room
room_id = self.helper.create_room_as(self.user_id)
# When I send a state event
path = "/rooms/{room_id}/state/custom.event/my_state_key".format(
room_id=urlparse.quote(room_id),
)
channel = self.make_request("PUT", path, "{}")
# Then I am allowed
self.assertEqual(200, channel.code, msg=channel.result["body"])
def test_normal_user_can_not_post_state_event(self) -> None:
# Given I am a normal member of a room
room_id = self.helper.create_room_as("@some_other_guy:red")
self.helper.join(room=room_id, user=self.user_id)
# When I send a state event
path = "/rooms/{room_id}/state/custom.event/my_state_key".format(
room_id=urlparse.quote(room_id),
)
channel = self.make_request("PUT", path, "{}")
# Then I am not allowed because state events require PL>=50
self.assertEqual(403, channel.code, msg=channel.result["body"])
self.assertEqual(
"You don't have permission to post that to the room. "
"user_level (0) < send_level (50)",
channel.json_body["error"],
)
@unittest.override_config(
{
"default_power_level_content_override": {
"public_chat": {"events": {"custom.event": 0}},
}
},
)
def test_with_config_override_normal_user_can_post_state_event(self) -> None:
# Given the server has config allowing normal users to post my event type,
# and I am a normal member of a room
room_id = self.helper.create_room_as("@some_other_guy:red")
self.helper.join(room=room_id, user=self.user_id)
# When I send a state event
path = "/rooms/{room_id}/state/custom.event/my_state_key".format(
room_id=urlparse.quote(room_id),
)
channel = self.make_request("PUT", path, "{}")
# Then I am allowed
self.assertEqual(200, channel.code, msg=channel.result["body"])
@unittest.override_config(
{
"default_power_level_content_override": {
"public_chat": {"events": {"custom.event": 0}},
}
},
)
def test_any_room_override_defeats_config_override(self) -> None:
# Given the server has config allowing normal users to post my event type
# And I am a normal member of a room
# But the room was created with special permissions
extra_content: Dict[str, Any] = {
"power_level_content_override": {"events": {}},
}
room_id = self.helper.create_room_as(
"@some_other_guy:red", extra_content=extra_content
)
self.helper.join(room=room_id, user=self.user_id)
# When I send a state event
path = "/rooms/{room_id}/state/custom.event/my_state_key".format(
room_id=urlparse.quote(room_id),
)
channel = self.make_request("PUT", path, "{}")
# Then I am not allowed
self.assertEqual(403, channel.code, msg=channel.result["body"])
@unittest.override_config(
{
"default_power_level_content_override": {
"public_chat": {"events": {"custom.event": 0}},
}
},
)
def test_specific_room_override_defeats_config_override(self) -> None:
# Given the server has config allowing normal users to post my event type,
# and I am a normal member of a room,
# but the room was created with special permissions for this event type
extra_content = {
"power_level_content_override": {"events": {"custom.event": 1}},
}
room_id = self.helper.create_room_as(
"@some_other_guy:red", extra_content=extra_content
)
self.helper.join(room=room_id, user=self.user_id)
# When I send a state event
path = "/rooms/{room_id}/state/custom.event/my_state_key".format(
room_id=urlparse.quote(room_id),
)
channel = self.make_request("PUT", path, "{}")
# Then I am not allowed
self.assertEqual(403, channel.code, msg=channel.result["body"])
self.assertEqual(
"You don't have permission to post that to the room. "
+ "user_level (0) < send_level (1)",
channel.json_body["error"],
)
@unittest.override_config(
{
"default_power_level_content_override": {
"public_chat": {"events": {"custom.event": 0}},
"private_chat": None,
"trusted_private_chat": None,
}
},
)
def test_config_override_applies_only_to_specific_preset(self) -> None:
# Given the server has config for public_chats,
# and I am a normal member of a private_chat room
room_id = self.helper.create_room_as("@some_other_guy:red", is_public=False)
self.helper.invite(room=room_id, src="@some_other_guy:red", targ=self.user_id)
self.helper.join(room=room_id, user=self.user_id)
# When I send a state event
path = "/rooms/{room_id}/state/custom.event/my_state_key".format(
room_id=urlparse.quote(room_id),
)
channel = self.make_request("PUT", path, "{}")
# Then I am not allowed because the public_chat config does not
# affect this room, because this room is a private_chat
self.assertEqual(403, channel.code, msg=channel.result["body"])
self.assertEqual(
"You don't have permission to post that to the room. "
+ "user_level (0) < send_level (50)",
channel.json_body["error"],
)
class RoomInitialSyncTestCase(RoomBase): class RoomInitialSyncTestCase(RoomBase):
"""Tests /rooms/$room_id/initialSync.""" """Tests /rooms/$room_id/initialSync."""