mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-24 18:45:52 +03:00
add default_power_level_content_override config option. (#12618)
Co-authored-by: Matthew Hodgson <matthew@matrix.org>
This commit is contained in:
parent
409573f6d0
commit
de1e599b9d
6 changed files with 381 additions and 1 deletions
1
changelog.d/12618.feature
Normal file
1
changelog.d/12618.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add a `default_power_level_content_override` config option to set default room power levels per room preset.
|
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue