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 #3397 from matrix-org/rav/adjust_auth_rules
Browse files Browse the repository at this point in the history
Adjust event auth rules when there is no PL event
  • Loading branch information
richvdh authored Jun 14, 2018
2 parents aefcc0f + 1e77ac6 commit 61ab08a
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 46 deletions.
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

0 comments on commit 61ab08a

Please sign in to comment.