Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #6790 from matrix-org/rav/msc2260.1
Browse files Browse the repository at this point in the history
* commit '281551f72':
  changelog
  Make /directory/room/<alias> handle restrictive power levels
  Set the PL for aliases events to 0.
  Factor out a `copy_power_levels_contents` method
  • Loading branch information
anoadragon453 committed Mar 23, 2020
2 parents 70e37d5 + 281551f commit fc0fd6e
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 18 deletions.
1 change: 1 addition & 0 deletions changelog.d/6790.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement updated authorization rules for aliases events, from [MSC2260](https://github.com/matrix-org/matrix-doc/pull/2260).
37 changes: 36 additions & 1 deletion synapse/events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import re
from typing import Mapping, Union

from six import string_types

Expand Down Expand Up @@ -422,3 +423,37 @@ def serialize_events(self, events, time_now, **kwargs):
return yieldable_gather_results(
self.serialize_event, events, time_now=time_now, **kwargs
)


def copy_power_levels_contents(
old_power_levels: Mapping[str, Union[int, Mapping[str, int]]]
):
"""Copy the content of a power_levels event, unfreezing frozendicts along the way
Raises:
TypeError if the input does not look like a valid power levels event content
"""
if not isinstance(old_power_levels, collections.Mapping):
raise TypeError("Not a valid power-levels content: %r" % (old_power_levels,))

power_levels = {}
for k, v in old_power_levels.items():

if isinstance(v, int):
power_levels[k] = v
continue

if isinstance(v, collections.Mapping):
power_levels[k] = h = {}
for k1, v1 in v.items():
# we should only have one level of nesting
if not isinstance(v1, int):
raise TypeError(
"Invalid power_levels value for %s.%s: %r" % (k, k1, v1)
)
h[k1] = v1
continue

raise TypeError("Invalid power_levels value for %s: %r" % (k, v))

return power_levels
7 changes: 6 additions & 1 deletion synapse/handlers/directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ def create_association(

yield self._create_association(room_alias, room_id, servers, creator=user_id)
if send_event:
yield self.send_room_alias_update_event(requester, room_id)
try:
yield self.send_room_alias_update_event(requester, room_id)
except AuthError as e:
# sending the aliases event may fail due to the user not having
# permission in the room; this is permitted.
logger.info("Skipping updating aliases event due to auth error %s", e)

@defer.inlineCallbacks
def delete_association(self, requester, room_alias, send_event=True):
Expand Down
40 changes: 26 additions & 14 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from synapse.api.constants import EventTypes, JoinRules, RoomCreationPreset
from synapse.api.errors import AuthError, Codes, NotFoundError, StoreError, SynapseError
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.events.utils import copy_power_levels_contents
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.storage.state import StateFilter
from synapse.types import (
Expand Down Expand Up @@ -288,15 +289,24 @@ def _update_upgraded_room_pls(
except AuthError as e:
logger.warning("Unable to update PLs in old room: %s", e)

logger.info("Setting correct PLs in new room to %s", old_room_pl_state.content)
new_pl_content = copy_power_levels_contents(old_room_pl_state.content)

# pre-msc2260 rooms may not have the right setting for aliases. If no other
# value is set, set it now.
events_default = new_pl_content.get("events_default", 0)
new_pl_content.setdefault("events", {}).setdefault(
EventTypes.Aliases, events_default
)

logger.info("Setting correct PLs in new room to %s", new_pl_content)
yield self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.PowerLevels,
"state_key": "",
"room_id": new_room_id,
"sender": requester.user.to_string(),
"content": old_room_pl_state.content,
"content": new_pl_content,
},
ratelimit=False,
)
Expand Down Expand Up @@ -381,6 +391,15 @@ def clone_existing_room(
if old_event:
initial_state[k] = old_event.content

# deep-copy the power-levels event before we start modifying it
# note that if frozen_dicts are enabled, `power_levels` will be a frozen
# dict so we can't just copy.deepcopy it.
initial_state[
(EventTypes.PowerLevels, "")
] = power_levels = copy_power_levels_contents(
initial_state[(EventTypes.PowerLevels, "")]
)

# Resolve the minimum power level required to send any state event
# We will give the upgrading user this power level temporarily (if necessary) such that
# they are able to copy all of the state events over, then revert them back to their
Expand All @@ -389,8 +408,6 @@ def clone_existing_room(
# Copy over user power levels now as this will not be possible with >100PL users once
# the room has been created

power_levels = initial_state[(EventTypes.PowerLevels, "")]

# Calculate the minimum power level needed to clone the room
event_power_levels = power_levels.get("events", {})
state_default = power_levels.get("state_default", 0)
Expand All @@ -400,16 +417,7 @@ def clone_existing_room(
# Raise the requester's power level in the new room if necessary
current_power_level = power_levels["users"][user_id]
if current_power_level < needed_power_level:
# make sure we copy the event content rather than overwriting it.
# note that if frozen_dicts are enabled, `power_levels` will be a frozen
# dict so we can't just copy.deepcopy it.

new_power_levels = {k: v for k, v in power_levels.items() if k != "users"}
new_power_levels["users"] = {
k: v for k, v in power_levels.get("users", {}).items() if k != user_id
}
new_power_levels["users"][user_id] = needed_power_level
initial_state[(EventTypes.PowerLevels, "")] = new_power_levels
power_levels["users"][user_id] = needed_power_level

yield self._send_events_for_new_room(
requester,
Expand Down Expand Up @@ -833,6 +841,10 @@ def send(etype, content, **kwargs):
EventTypes.RoomHistoryVisibility: 100,
EventTypes.CanonicalAlias: 50,
EventTypes.RoomAvatar: 50,
# MSC2260: Allow everybody to send alias events by default
# This will be reudundant on pre-MSC2260 rooms, since the
# aliases event is special-cased.
EventTypes.Aliases: 0,
},
"events_default": 0,
"state_default": 50,
Expand Down
45 changes: 43 additions & 2 deletions tests/events/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@


from synapse.events import FrozenEvent
from synapse.events.utils import prune_event, serialize_event
from synapse.events.utils import (
copy_power_levels_contents,
prune_event,
serialize_event,
)
from synapse.util.frozenutils import freeze

from .. import unittest
from tests import unittest


def MockEvent(**kwargs):
Expand Down Expand Up @@ -241,3 +246,39 @@ def test_event_fields_fail_if_fields_not_str(self):
self.serialize(
MockEvent(room_id="!foo:bar", content={"foo": "bar"}), ["room_id", 4]
)


class CopyPowerLevelsContentTestCase(unittest.TestCase):
def setUp(self) -> None:
self.test_content = {
"ban": 50,
"events": {"m.room.name": 100, "m.room.power_levels": 100},
"events_default": 0,
"invite": 50,
"kick": 50,
"notifications": {"room": 20},
"redact": 50,
"state_default": 50,
"users": {"@example:localhost": 100},
"users_default": 0,
}

def _test(self, input):
a = copy_power_levels_contents(input)

self.assertEqual(a["ban"], 50)
self.assertEqual(a["events"]["m.room.name"], 100)

# make sure that changing the copy changes the copy and not the orig
a["ban"] = 10
a["events"]["m.room.power_levels"] = 20

self.assertEqual(input["ban"], 50)
self.assertEqual(input["events"]["m.room.power_levels"], 100)

def test_unfrozen(self):
self._test(self.test_content)

def test_frozen(self):
input = freeze(self.test_content)
self._test(input)

0 comments on commit fc0fd6e

Please sign in to comment.