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

Adjust event auth rules when there is no PL event #3397

Merged
merged 5 commits into from
Jun 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion synapse/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ def check_can_change_room_list(self, room_id, user):
auth_events[(EventTypes.PowerLevels, "")] = power_level_event

send_level = event_auth.get_send_level(
EventTypes.Aliases, "", auth_events
EventTypes.Aliases, "", power_level_event,
)
user_level = event_auth.get_user_power_level(user_id, auth_events)

Expand Down
109 changes: 66 additions & 43 deletions synapse/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
event: the event being checked.
auth_events (dict: event-key -> event): the existing room state.

Raises:
AuthError if the checks fail

Returns:
True if the auth checks pass.
if the auth checks pass.
"""
if do_size_check:
_check_size_limits(event)
Expand Down Expand Up @@ -71,7 +73,7 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
# Oh, we don't know what the state of the room was, so we
# are trusting that this is allowed (at least for now)
logger.warn("Trusting event: %s", event.event_id)
return True
return

if event.type == EventTypes.Create:
room_id_domain = get_domain_from_id(event.room_id)
Expand All @@ -81,7 +83,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
"Creation event's room_id domain does not match sender's"
)
# FIXME
return True
logger.debug("Allowing! %s", event)
return

creation_event = auth_events.get((EventTypes.Create, ""), None)

Expand Down Expand Up @@ -118,7 +121,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
403,
"Alias event's state_key does not match sender's domain"
)
return True
logger.debug("Allowing! %s", event)
return

if logger.isEnabledFor(logging.DEBUG):
logger.debug(
Expand All @@ -127,14 +131,9 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
)

if event.type == EventTypes.Member:
allowed = _is_membership_change_allowed(
event, auth_events
)
if allowed:
logger.debug("Allowing! %s", event)
else:
logger.debug("Denying! %s", event)
return allowed
_is_membership_change_allowed(event, auth_events)
logger.debug("Allowing! %s", event)
return

_check_event_sender_in_room(event, auth_events)

Expand All @@ -153,7 +152,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
)
)
else:
return True
logger.debug("Allowing! %s", event)
return

_can_send_event(event, auth_events)

Expand Down Expand Up @@ -200,7 +200,7 @@ def _is_membership_change_allowed(event, auth_events):
create = auth_events.get(key)
if create and event.prev_events[0][0] == create.event_id:
if create.content["creator"] == event.state_key:
return True
return

target_user_id = event.state_key

Expand Down Expand Up @@ -265,13 +265,13 @@ def _is_membership_change_allowed(event, auth_events):
raise AuthError(
403, "%s is banned from the room" % (target_user_id,)
)
return True
return

if Membership.JOIN != membership:
if (caller_invited
and Membership.LEAVE == membership
and target_user_id == event.user_id):
return True
return

if not caller_in_room: # caller isn't joined
raise AuthError(
Expand Down Expand Up @@ -334,8 +334,6 @@ def _is_membership_change_allowed(event, auth_events):
else:
raise AuthError(500, "Unknown membership %s" % membership)

return True


def _check_event_sender_in_room(event, auth_events):
key = (EventTypes.Member, event.user_id, )
Expand All @@ -355,35 +353,46 @@ def _check_joined_room(member, user_id, room_id):
))


def get_send_level(etype, state_key, auth_events):
key = (EventTypes.PowerLevels, "", )
send_level_event = auth_events.get(key)
send_level = None
if send_level_event:
send_level = send_level_event.content.get("events", {}).get(
etype
)
if send_level is None:
if state_key is not None:
send_level = send_level_event.content.get(
"state_default", 50
)
else:
send_level = send_level_event.content.get(
"events_default", 0
)
def get_send_level(etype, state_key, power_levels_event):
"""Get the power level required to send an event of a given type

The federation spec [1] refers to this as "Required Power Level".

https://matrix.org/docs/spec/server_server/unstable.html#definitions

if send_level:
send_level = int(send_level)
Args:
etype (str): type of event
state_key (str|None): state_key of state event, or None if it is not
a state event.
power_levels_event (synapse.events.EventBase|None): power levels event
in force at this point in the room
Returns:
int: power level required to send this event.
"""

if power_levels_event:
power_levels_content = power_levels_event.content
else:
send_level = 0
power_levels_content = {}

# see if we have a custom level for this event type
send_level = power_levels_content.get("events", {}).get(etype)

# otherwise, fall back to the state_default/events_default.
if send_level is None:
if state_key is not None:
send_level = power_levels_content.get("state_default", 50)
else:
send_level = power_levels_content.get("events_default", 0)

return send_level
return int(send_level)


def _can_send_event(event, auth_events):
power_levels_event = _get_power_level_event(auth_events)

send_level = get_send_level(
event.type, event.get("state_key", None), auth_events
event.type, event.get("state_key"), power_levels_event,
)
user_level = get_user_power_level(event.user_id, auth_events)

Expand Down Expand Up @@ -524,13 +533,22 @@ def _check_power_levels(event, auth_events):


def _get_power_level_event(auth_events):
key = (EventTypes.PowerLevels, "", )
return auth_events.get(key)
return auth_events.get((EventTypes.PowerLevels, ""))


def get_user_power_level(user_id, auth_events):
power_level_event = _get_power_level_event(auth_events)
"""Get a user's power level

Args:
user_id (str): user's id to look up in power_levels
auth_events (dict[(str, str), synapse.events.EventBase]):
state in force at this point in the room (or rather, a subset of
it including at least the create event and power levels event.

Returns:
int: the user's power level in this room.
"""
power_level_event = _get_power_level_event(auth_events)
if power_level_event:
level = power_level_event.content.get("users", {}).get(user_id)
if not level:
Expand All @@ -541,6 +559,11 @@ def get_user_power_level(user_id, auth_events):
else:
return int(level)
else:
# if there is no power levels event, the creator gets 100 and everyone
# else gets 0.

# some things which call this don't pass the create event: hack around
# that.
key = (EventTypes.Create, "", )
create_event = auth_events.get(key)
if (create_event is not None and
Expand Down
151 changes: 151 additions & 0 deletions tests/test_event_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.

from synapse import event_auth
from synapse.api.errors import AuthError
from synapse.events import FrozenEvent
import unittest


class EventAuthTestCase(unittest.TestCase):
def test_random_users_cannot_send_state_before_first_pl(self):
"""
Check that, before the first PL lands, the creator is the only user
that can send a state event.
"""
creator = "@creator:example.com"
joiner = "@joiner:example.com"
auth_events = {
("m.room.create", ""): _create_event(creator),
("m.room.member", creator): _join_event(creator),
("m.room.member", joiner): _join_event(joiner),
}

# creator should be able to send state
event_auth.check(
_random_state_event(creator), auth_events,
do_sig_check=False,
)

# joiner should not be able to send state
self.assertRaises(
AuthError,
event_auth.check,
_random_state_event(joiner),
auth_events,
do_sig_check=False,
),

def test_state_default_level(self):
"""
Check that users above the state_default level can send state and
those below cannot
"""
creator = "@creator:example.com"
pleb = "@joiner:example.com"
king = "@joiner2:example.com"

auth_events = {
("m.room.create", ""): _create_event(creator),
("m.room.member", creator): _join_event(creator),
("m.room.power_levels", ""): _power_levels_event(creator, {
"state_default": "30",
"users": {
pleb: "29",
king: "30",
},
}),
("m.room.member", pleb): _join_event(pleb),
("m.room.member", king): _join_event(king),
}

# pleb should not be able to send state
self.assertRaises(
AuthError,
event_auth.check,
_random_state_event(pleb),
auth_events,
do_sig_check=False,
),

# king should be able to send state
event_auth.check(
_random_state_event(king), auth_events,
do_sig_check=False,
)


# helpers for making events

TEST_ROOM_ID = "!test:room"


def _create_event(user_id):
return FrozenEvent({
"room_id": TEST_ROOM_ID,
"event_id": _get_event_id(),
"type": "m.room.create",
"sender": user_id,
"content": {
"creator": user_id,
},
})


def _join_event(user_id):
return FrozenEvent({
"room_id": TEST_ROOM_ID,
"event_id": _get_event_id(),
"type": "m.room.member",
"sender": user_id,
"state_key": user_id,
"content": {
"membership": "join",
},
})


def _power_levels_event(sender, content):
return FrozenEvent({
"room_id": TEST_ROOM_ID,
"event_id": _get_event_id(),
"type": "m.room.power_levels",
"sender": sender,
"state_key": "",
"content": content,
})


def _random_state_event(sender):
return FrozenEvent({
"room_id": TEST_ROOM_ID,
"event_id": _get_event_id(),
"type": "test.state",
"sender": sender,
"state_key": "",
"content": {
"membership": "join",
},
})


event_count = 0


def _get_event_id():
global event_count
c = event_count
event_count += 1
return "!%i:example.com" % (c, )
Loading