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

Commit 8ecf6be

Browse files
authored
Move some event auth checks out to a different method (#13065)
* Add auth events to events used in tests * Move some event auth checks out to a different method Some of the event auth checks apply to an event's auth_events, rather than the state at the event - which means they can play no part in state resolution. Move them out to a separate method. * Rename check_auth_rules_for_event Now it only checks the state-dependent auth rules, it needs a better name.
1 parent cba1c5c commit 8ecf6be

File tree

7 files changed

+219
-98
lines changed

7 files changed

+219
-98
lines changed

changelog.d/13065.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid rechecking event auth rules which are independent of room state.

synapse/event_auth.py

Lines changed: 79 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515

1616
import logging
1717
import typing
18-
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
18+
from typing import Any, Collection, Dict, Iterable, List, Optional, Set, Tuple, Union
1919

2020
from canonicaljson import encode_canonical_json
2121
from signedjson.key import decode_verify_key_bytes
2222
from signedjson.sign import SignatureVerifyException, verify_signed_json
23+
from typing_extensions import Protocol
2324
from unpaddedbase64 import decode_base64
2425

2526
from synapse.api.constants import (
@@ -35,7 +36,8 @@
3536
EventFormatVersions,
3637
RoomVersion,
3738
)
38-
from synapse.types import StateMap, UserID, get_domain_from_id
39+
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
40+
from synapse.types import MutableStateMap, StateMap, UserID, get_domain_from_id
3941

4042
if typing.TYPE_CHECKING:
4143
# conditional imports to avoid import cycle
@@ -45,6 +47,17 @@
4547
logger = logging.getLogger(__name__)
4648

4749

50+
class _EventSourceStore(Protocol):
51+
async def get_events(
52+
self,
53+
event_ids: Collection[str],
54+
redact_behaviour: EventRedactBehaviour,
55+
get_prev_content: bool = False,
56+
allow_rejected: bool = False,
57+
) -> Dict[str, "EventBase"]:
58+
...
59+
60+
4861
def validate_event_for_room_version(event: "EventBase") -> None:
4962
"""Ensure that the event complies with the limits, and has the right signatures
5063
@@ -112,54 +125,61 @@ def validate_event_for_room_version(event: "EventBase") -> None:
112125
raise AuthError(403, "Event not signed by authorising server")
113126

114127

115-
def check_auth_rules_for_event(
128+
async def check_state_independent_auth_rules(
129+
store: _EventSourceStore,
116130
event: "EventBase",
117-
auth_events: Iterable["EventBase"],
118131
) -> None:
119-
"""Check that an event complies with the auth rules
120-
121-
Checks whether an event passes the auth rules with a given set of state events
122-
123-
Assumes that we have already checked that the event is the right shape (it has
124-
enough signatures, has a room ID, etc). In other words:
125-
126-
- it's fine for use in state resolution, when we have already decided whether to
127-
accept the event or not, and are now trying to decide whether it should make it
128-
into the room state
132+
"""Check that an event complies with auth rules that are independent of room state
129133
130-
- when we're doing the initial event auth, it is only suitable in combination with
131-
a bunch of other tests.
134+
Runs through the first few auth rules, which are independent of room state. (Which
135+
means that we only need to them once for each received event)
132136
133137
Args:
138+
store: the datastore; used to fetch the auth events for validation
134139
event: the event being checked.
135-
auth_events: the room state to check the events against.
136140
137141
Raises:
138142
AuthError if the checks fail
139143
"""
140-
# We need to ensure that the auth events are actually for the same room, to
141-
# stop people from using powers they've been granted in other rooms for
142-
# example.
143-
#
144-
# Arguably we don't need to do this when we're just doing state res, as presumably
145-
# the state res algorithm isn't silly enough to give us events from different rooms.
146-
# Still, it's easier to do it anyway.
144+
# Check the auth events.
145+
auth_events = await store.get_events(
146+
event.auth_event_ids(),
147+
redact_behaviour=EventRedactBehaviour.as_is,
148+
allow_rejected=True,
149+
)
147150
room_id = event.room_id
148-
for auth_event in auth_events:
151+
auth_dict: MutableStateMap[str] = {}
152+
for auth_event_id in event.auth_event_ids():
153+
auth_event = auth_events.get(auth_event_id)
154+
155+
# we should have all the auth events by now, so if we do not, that suggests
156+
# a synapse programming error
157+
if auth_event is None:
158+
raise RuntimeError(
159+
f"Event {event.event_id} has unknown auth event {auth_event_id}"
160+
)
161+
162+
# We need to ensure that the auth events are actually for the same room, to
163+
# stop people from using powers they've been granted in other rooms for
164+
# example.
149165
if auth_event.room_id != room_id:
150166
raise AuthError(
151167
403,
152168
"During auth for event %s in room %s, found event %s in the state "
153169
"which is in room %s"
154-
% (event.event_id, room_id, auth_event.event_id, auth_event.room_id),
170+
% (event.event_id, room_id, auth_event_id, auth_event.room_id),
155171
)
172+
173+
# We also need to check that the auth event itself is not rejected.
156174
if auth_event.rejected_reason:
157175
raise AuthError(
158176
403,
159177
"During auth for event %s: found rejected event %s in the state"
160178
% (event.event_id, auth_event.event_id),
161179
)
162180

181+
auth_dict[(auth_event.type, auth_event.state_key)] = auth_event_id
182+
163183
# Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules
164184
#
165185
# 1. If type is m.room.create:
@@ -181,16 +201,46 @@ def check_auth_rules_for_event(
181201
"room appears to have unsupported version %s" % (room_version_prop,),
182202
)
183203

184-
logger.debug("Allowing! %s", event)
185204
return
186205

187-
auth_dict = {(e.type, e.state_key): e for e in auth_events}
188-
189206
# 3. If event does not have a m.room.create in its auth_events, reject.
190207
creation_event = auth_dict.get((EventTypes.Create, ""), None)
191208
if not creation_event:
192209
raise AuthError(403, "No create event in auth events")
193210

211+
212+
def check_state_dependent_auth_rules(
213+
event: "EventBase",
214+
auth_events: Iterable["EventBase"],
215+
) -> None:
216+
"""Check that an event complies with auth rules that depend on room state
217+
218+
Runs through the parts of the auth rules that check an event against bits of room
219+
state.
220+
221+
Note:
222+
223+
- it's fine for use in state resolution, when we have already decided whether to
224+
accept the event or not, and are now trying to decide whether it should make it
225+
into the room state
226+
227+
- when we're doing the initial event auth, it is only suitable in combination with
228+
a bunch of other tests (including, but not limited to, check_state_independent_auth_rules).
229+
230+
Args:
231+
event: the event being checked.
232+
auth_events: the room state to check the events against.
233+
234+
Raises:
235+
AuthError if the checks fail
236+
"""
237+
# there are no state-dependent auth rules for create events.
238+
if event.type == EventTypes.Create:
239+
logger.debug("Allowing! %s", event)
240+
return
241+
242+
auth_dict = {(e.type, e.state_key): e for e in auth_events}
243+
194244
# additional check for m.federate
195245
creating_domain = get_domain_from_id(event.room_id)
196246
originating_domain = get_domain_from_id(event.sender)

synapse/handlers/event_auth.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
)
2424
from synapse.api.errors import AuthError, Codes, SynapseError
2525
from synapse.api.room_versions import RoomVersion
26-
from synapse.event_auth import check_auth_rules_for_event
26+
from synapse.event_auth import (
27+
check_state_dependent_auth_rules,
28+
check_state_independent_auth_rules,
29+
)
2730
from synapse.events import EventBase
2831
from synapse.events.builder import EventBuilder
2932
from synapse.events.snapshot import EventContext
@@ -52,9 +55,10 @@ async def check_auth_rules_from_context(
5255
context: EventContext,
5356
) -> None:
5457
"""Check an event passes the auth rules at its own auth events"""
58+
await check_state_independent_auth_rules(self._store, event)
5559
auth_event_ids = event.auth_event_ids()
5660
auth_events_by_id = await self._store.get_events(auth_event_ids)
57-
check_auth_rules_for_event(event, auth_events_by_id.values())
61+
check_state_dependent_auth_rules(event, auth_events_by_id.values())
5862

5963
def compute_auth_events(
6064
self,

synapse/handlers/federation_event.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion, RoomVersions
5151
from synapse.event_auth import (
5252
auth_types_for_event,
53-
check_auth_rules_for_event,
53+
check_state_dependent_auth_rules,
54+
check_state_independent_auth_rules,
5455
validate_event_for_room_version,
5556
)
5657
from synapse.events import EventBase
@@ -1430,7 +1431,9 @@ async def _auth_and_persist_outliers_inner(
14301431
allow_rejected=True,
14311432
)
14321433

1433-
def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
1434+
events_and_contexts_to_persist: List[Tuple[EventBase, EventContext]] = []
1435+
1436+
async def prep(event: EventBase) -> None:
14341437
with nested_logging_context(suffix=event.event_id):
14351438
auth = []
14361439
for auth_event_id in event.auth_event_ids():
@@ -1444,7 +1447,7 @@ def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
14441447
event,
14451448
auth_event_id,
14461449
)
1447-
return None
1450+
return
14481451
auth.append(ae)
14491452

14501453
# we're not bothering about room state, so flag the event as an outlier.
@@ -1453,17 +1456,20 @@ def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
14531456
context = EventContext.for_outlier(self._storage_controllers)
14541457
try:
14551458
validate_event_for_room_version(event)
1456-
check_auth_rules_for_event(event, auth)
1459+
await check_state_independent_auth_rules(self._store, event)
1460+
check_state_dependent_auth_rules(event, auth)
14571461
except AuthError as e:
14581462
logger.warning("Rejecting %r because %s", event, e)
14591463
context.rejected = RejectedReason.AUTH_ERROR
14601464

1461-
return event, context
1465+
events_and_contexts_to_persist.append((event, context))
1466+
1467+
for event in fetched_events:
1468+
await prep(event)
14621469

1463-
events_to_persist = (x for x in (prep(event) for event in fetched_events) if x)
14641470
await self.persist_events_and_notify(
14651471
room_id,
1466-
tuple(events_to_persist),
1472+
events_and_contexts_to_persist,
14671473
# Mark these events backfilled as they're historic events that will
14681474
# eventually be backfilled. For example, missing events we fetch
14691475
# during backfill should be marked as backfilled as well.
@@ -1515,7 +1521,8 @@ async def _check_event_auth(
15151521

15161522
# ... and check that the event passes auth at those auth events.
15171523
try:
1518-
check_auth_rules_for_event(event, claimed_auth_events)
1524+
await check_state_independent_auth_rules(self._store, event)
1525+
check_state_dependent_auth_rules(event, claimed_auth_events)
15191526
except AuthError as e:
15201527
logger.warning(
15211528
"While checking auth of %r against auth_events: %s", event, e
@@ -1563,7 +1570,7 @@ async def _check_event_auth(
15631570
auth_events_for_auth = calculated_auth_event_map
15641571

15651572
try:
1566-
check_auth_rules_for_event(event, auth_events_for_auth.values())
1573+
check_state_dependent_auth_rules(event, auth_events_for_auth.values())
15671574
except AuthError as e:
15681575
logger.warning("Failed auth resolution for %r because %s", event, e)
15691576
context.rejected = RejectedReason.AUTH_ERROR
@@ -1663,7 +1670,7 @@ async def _check_for_soft_fail(
16631670
)
16641671

16651672
try:
1666-
check_auth_rules_for_event(event, current_auth_events)
1673+
check_state_dependent_auth_rules(event, current_auth_events)
16671674
except AuthError as e:
16681675
logger.warning(
16691676
"Soft-failing %r (from %s) because %s",

synapse/state/v1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def _resolve_auth_events(
330330
auth_events[(prev_event.type, prev_event.state_key)] = prev_event
331331
try:
332332
# The signatures have already been checked at this point
333-
event_auth.check_auth_rules_for_event(
333+
event_auth.check_state_dependent_auth_rules(
334334
event,
335335
auth_events.values(),
336336
)
@@ -347,7 +347,7 @@ def _resolve_normal_events(
347347
for event in _ordered_events(events):
348348
try:
349349
# The signatures have already been checked at this point
350-
event_auth.check_auth_rules_for_event(
350+
event_auth.check_state_dependent_auth_rules(
351351
event,
352352
auth_events.values(),
353353
)

synapse/state/v2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ async def _iterative_auth_checks(
573573
auth_events[key] = event_map[ev_id]
574574

575575
try:
576-
event_auth.check_auth_rules_for_event(
576+
event_auth.check_state_dependent_auth_rules(
577577
event,
578578
auth_events.values(),
579579
)

0 commit comments

Comments
 (0)