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

Commit 53969e1

Browse files
committed
Merge tag 'v0.31.2'
SECURITY UPDATE: Prevent unauthorised users from setting state events in a room when there is no `m.room.power_levels` event in force in the room. (PR #3397) Discussion around the Matrix Spec change proposal for this change can be followed at matrix-org/matrix-spec-proposals#1304.
2 parents 1032393 + 667c654 commit 53969e1

File tree

6 files changed

+243
-47
lines changed

6 files changed

+243
-47
lines changed

CHANGES.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
Changes in synapse v0.31.2 (2018-06-14)
2+
=======================================
3+
4+
SECURITY UPDATE: Prevent unauthorised users from setting state events in a room
5+
when there is no ``m.room.power_levels`` event in force in the room. (PR #3397)
6+
7+
Discussion around the Matrix Spec change proposal for this change can be
8+
followed at https://github.com/matrix-org/matrix-doc/issues/1304.
9+
110
Changes in synapse v0.31.1 (2018-06-08)
211
=======================================
312

synapse/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
# Copyright 2014-2016 OpenMarket Ltd
3+
# Copyright 2018 New Vector Ltd
34
#
45
# Licensed under the Apache License, Version 2.0 (the "License");
56
# you may not use this file except in compliance with the License.
@@ -16,4 +17,4 @@
1617
""" This is a reference implementation of a Matrix home server.
1718
"""
1819

19-
__version__ = "0.31.1"
20+
__version__ = "0.31.2"

synapse/api/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ def check_can_change_room_list(self, room_id, user):
655655
auth_events[(EventTypes.PowerLevels, "")] = power_level_event
656656

657657
send_level = event_auth.get_send_level(
658-
EventTypes.Aliases, "", auth_events
658+
EventTypes.Aliases, "", power_level_event,
659659
)
660660
user_level = event_auth.get_user_power_level(user_id, auth_events)
661661

synapse/event_auth.py

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
3434
event: the event being checked.
3535
auth_events (dict: event-key -> event): the existing room state.
3636
37+
Raises:
38+
AuthError if the checks fail
3739
3840
Returns:
39-
True if the auth checks pass.
41+
if the auth checks pass.
4042
"""
4143
if do_size_check:
4244
_check_size_limits(event)
@@ -71,7 +73,7 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
7173
# Oh, we don't know what the state of the room was, so we
7274
# are trusting that this is allowed (at least for now)
7375
logger.warn("Trusting event: %s", event.event_id)
74-
return True
76+
return
7577

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

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

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

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

129133
if event.type == EventTypes.Member:
130-
allowed = _is_membership_change_allowed(
131-
event, auth_events
132-
)
133-
if allowed:
134-
logger.debug("Allowing! %s", event)
135-
else:
136-
logger.debug("Denying! %s", event)
137-
return allowed
134+
_is_membership_change_allowed(event, auth_events)
135+
logger.debug("Allowing! %s", event)
136+
return
138137

139138
_check_event_sender_in_room(event, auth_events)
140139

@@ -153,7 +152,8 @@ def check(event, auth_events, do_sig_check=True, do_size_check=True):
153152
)
154153
)
155154
else:
156-
return True
155+
logger.debug("Allowing! %s", event)
156+
return
157157

158158
_can_send_event(event, auth_events)
159159

@@ -200,7 +200,7 @@ def _is_membership_change_allowed(event, auth_events):
200200
create = auth_events.get(key)
201201
if create and event.prev_events[0][0] == create.event_id:
202202
if create.content["creator"] == event.state_key:
203-
return True
203+
return
204204

205205
target_user_id = event.state_key
206206

@@ -265,13 +265,13 @@ def _is_membership_change_allowed(event, auth_events):
265265
raise AuthError(
266266
403, "%s is banned from the room" % (target_user_id,)
267267
)
268-
return True
268+
return
269269

270270
if Membership.JOIN != membership:
271271
if (caller_invited
272272
and Membership.LEAVE == membership
273273
and target_user_id == event.user_id):
274-
return True
274+
return
275275

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

337-
return True
338-
339337

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

357355

358-
def get_send_level(etype, state_key, auth_events):
359-
key = (EventTypes.PowerLevels, "", )
360-
send_level_event = auth_events.get(key)
361-
send_level = None
362-
if send_level_event:
363-
send_level = send_level_event.content.get("events", {}).get(
364-
etype
365-
)
366-
if send_level is None:
367-
if state_key is not None:
368-
send_level = send_level_event.content.get(
369-
"state_default", 50
370-
)
371-
else:
372-
send_level = send_level_event.content.get(
373-
"events_default", 0
374-
)
356+
def get_send_level(etype, state_key, power_levels_event):
357+
"""Get the power level required to send an event of a given type
358+
359+
The federation spec [1] refers to this as "Required Power Level".
360+
361+
https://matrix.org/docs/spec/server_server/unstable.html#definitions
375362
376-
if send_level:
377-
send_level = int(send_level)
363+
Args:
364+
etype (str): type of event
365+
state_key (str|None): state_key of state event, or None if it is not
366+
a state event.
367+
power_levels_event (synapse.events.EventBase|None): power levels event
368+
in force at this point in the room
369+
Returns:
370+
int: power level required to send this event.
371+
"""
372+
373+
if power_levels_event:
374+
power_levels_content = power_levels_event.content
378375
else:
379-
send_level = 0
376+
power_levels_content = {}
377+
378+
# see if we have a custom level for this event type
379+
send_level = power_levels_content.get("events", {}).get(etype)
380+
381+
# otherwise, fall back to the state_default/events_default.
382+
if send_level is None:
383+
if state_key is not None:
384+
send_level = power_levels_content.get("state_default", 50)
385+
else:
386+
send_level = power_levels_content.get("events_default", 0)
380387

381-
return send_level
388+
return int(send_level)
382389

383390

384391
def _can_send_event(event, auth_events):
392+
power_levels_event = _get_power_level_event(auth_events)
393+
385394
send_level = get_send_level(
386-
event.type, event.get("state_key", None), auth_events
395+
event.type, event.get("state_key"), power_levels_event,
387396
)
388397
user_level = get_user_power_level(event.user_id, auth_events)
389398

@@ -524,13 +533,22 @@ def _check_power_levels(event, auth_events):
524533

525534

526535
def _get_power_level_event(auth_events):
527-
key = (EventTypes.PowerLevels, "", )
528-
return auth_events.get(key)
536+
return auth_events.get((EventTypes.PowerLevels, ""))
529537

530538

531539
def get_user_power_level(user_id, auth_events):
532-
power_level_event = _get_power_level_event(auth_events)
540+
"""Get a user's power level
541+
542+
Args:
543+
user_id (str): user's id to look up in power_levels
544+
auth_events (dict[(str, str), synapse.events.EventBase]):
545+
state in force at this point in the room (or rather, a subset of
546+
it including at least the create event and power levels event.
533547
548+
Returns:
549+
int: the user's power level in this room.
550+
"""
551+
power_level_event = _get_power_level_event(auth_events)
534552
if power_level_event:
535553
level = power_level_event.content.get("users", {}).get(user_id)
536554
if not level:
@@ -541,6 +559,11 @@ def get_user_power_level(user_id, auth_events):
541559
else:
542560
return int(level)
543561
else:
562+
# if there is no power levels event, the creator gets 100 and everyone
563+
# else gets 0.
564+
565+
# some things which call this don't pass the create event: hack around
566+
# that.
544567
key = (EventTypes.Create, "", )
545568
create_event = auth_events.get(key)
546569
if (create_event is not None and

tests/test_event_auth.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2018 New Vector Ltd
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from synapse import event_auth
17+
from synapse.api.errors import AuthError
18+
from synapse.events import FrozenEvent
19+
import unittest
20+
21+
22+
class EventAuthTestCase(unittest.TestCase):
23+
def test_random_users_cannot_send_state_before_first_pl(self):
24+
"""
25+
Check that, before the first PL lands, the creator is the only user
26+
that can send a state event.
27+
"""
28+
creator = "@creator:example.com"
29+
joiner = "@joiner:example.com"
30+
auth_events = {
31+
("m.room.create", ""): _create_event(creator),
32+
("m.room.member", creator): _join_event(creator),
33+
("m.room.member", joiner): _join_event(joiner),
34+
}
35+
36+
# creator should be able to send state
37+
event_auth.check(
38+
_random_state_event(creator), auth_events,
39+
do_sig_check=False,
40+
)
41+
42+
# joiner should not be able to send state
43+
self.assertRaises(
44+
AuthError,
45+
event_auth.check,
46+
_random_state_event(joiner),
47+
auth_events,
48+
do_sig_check=False,
49+
),
50+
51+
def test_state_default_level(self):
52+
"""
53+
Check that users above the state_default level can send state and
54+
those below cannot
55+
"""
56+
creator = "@creator:example.com"
57+
pleb = "@joiner:example.com"
58+
king = "@joiner2:example.com"
59+
60+
auth_events = {
61+
("m.room.create", ""): _create_event(creator),
62+
("m.room.member", creator): _join_event(creator),
63+
("m.room.power_levels", ""): _power_levels_event(creator, {
64+
"state_default": "30",
65+
"users": {
66+
pleb: "29",
67+
king: "30",
68+
},
69+
}),
70+
("m.room.member", pleb): _join_event(pleb),
71+
("m.room.member", king): _join_event(king),
72+
}
73+
74+
# pleb should not be able to send state
75+
self.assertRaises(
76+
AuthError,
77+
event_auth.check,
78+
_random_state_event(pleb),
79+
auth_events,
80+
do_sig_check=False,
81+
),
82+
83+
# king should be able to send state
84+
event_auth.check(
85+
_random_state_event(king), auth_events,
86+
do_sig_check=False,
87+
)
88+
89+
90+
# helpers for making events
91+
92+
TEST_ROOM_ID = "!test:room"
93+
94+
95+
def _create_event(user_id):
96+
return FrozenEvent({
97+
"room_id": TEST_ROOM_ID,
98+
"event_id": _get_event_id(),
99+
"type": "m.room.create",
100+
"sender": user_id,
101+
"content": {
102+
"creator": user_id,
103+
},
104+
})
105+
106+
107+
def _join_event(user_id):
108+
return FrozenEvent({
109+
"room_id": TEST_ROOM_ID,
110+
"event_id": _get_event_id(),
111+
"type": "m.room.member",
112+
"sender": user_id,
113+
"state_key": user_id,
114+
"content": {
115+
"membership": "join",
116+
},
117+
})
118+
119+
120+
def _power_levels_event(sender, content):
121+
return FrozenEvent({
122+
"room_id": TEST_ROOM_ID,
123+
"event_id": _get_event_id(),
124+
"type": "m.room.power_levels",
125+
"sender": sender,
126+
"state_key": "",
127+
"content": content,
128+
})
129+
130+
131+
def _random_state_event(sender):
132+
return FrozenEvent({
133+
"room_id": TEST_ROOM_ID,
134+
"event_id": _get_event_id(),
135+
"type": "test.state",
136+
"sender": sender,
137+
"state_key": "",
138+
"content": {
139+
"membership": "join",
140+
},
141+
})
142+
143+
144+
event_count = 0
145+
146+
147+
def _get_event_id():
148+
global event_count
149+
c = event_count
150+
event_count += 1
151+
return "!%i:example.com" % (c, )

0 commit comments

Comments
 (0)