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

Commit

Permalink
Move some event auth checks out to a different method (#13065)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
richvdh authored Jun 15, 2022
1 parent cba1c5c commit 8ecf6be
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 98 deletions.
1 change: 1 addition & 0 deletions changelog.d/13065.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Avoid rechecking event auth rules which are independent of room state.
108 changes: 79 additions & 29 deletions synapse/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@

import logging
import typing
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
from typing import Any, Collection, Dict, Iterable, List, Optional, Set, Tuple, Union

from canonicaljson import encode_canonical_json
from signedjson.key import decode_verify_key_bytes
from signedjson.sign import SignatureVerifyException, verify_signed_json
from typing_extensions import Protocol
from unpaddedbase64 import decode_base64

from synapse.api.constants import (
Expand All @@ -35,7 +36,8 @@
EventFormatVersions,
RoomVersion,
)
from synapse.types import StateMap, UserID, get_domain_from_id
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
from synapse.types import MutableStateMap, StateMap, UserID, get_domain_from_id

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


class _EventSourceStore(Protocol):
async def get_events(
self,
event_ids: Collection[str],
redact_behaviour: EventRedactBehaviour,
get_prev_content: bool = False,
allow_rejected: bool = False,
) -> Dict[str, "EventBase"]:
...


def validate_event_for_room_version(event: "EventBase") -> None:
"""Ensure that the event complies with the limits, and has the right signatures
Expand Down Expand Up @@ -112,54 +125,61 @@ def validate_event_for_room_version(event: "EventBase") -> None:
raise AuthError(403, "Event not signed by authorising server")


def check_auth_rules_for_event(
async def check_state_independent_auth_rules(
store: _EventSourceStore,
event: "EventBase",
auth_events: Iterable["EventBase"],
) -> None:
"""Check that an event complies with the auth rules
Checks whether an event passes the auth rules with a given set of state events
Assumes that we have already checked that the event is the right shape (it has
enough signatures, has a room ID, etc). In other words:
- it's fine for use in state resolution, when we have already decided whether to
accept the event or not, and are now trying to decide whether it should make it
into the room state
"""Check that an event complies with auth rules that are independent of room state
- when we're doing the initial event auth, it is only suitable in combination with
a bunch of other tests.
Runs through the first few auth rules, which are independent of room state. (Which
means that we only need to them once for each received event)
Args:
store: the datastore; used to fetch the auth events for validation
event: the event being checked.
auth_events: the room state to check the events against.
Raises:
AuthError if the checks fail
"""
# We need to ensure that the auth events are actually for the same room, to
# stop people from using powers they've been granted in other rooms for
# example.
#
# Arguably we don't need to do this when we're just doing state res, as presumably
# the state res algorithm isn't silly enough to give us events from different rooms.
# Still, it's easier to do it anyway.
# Check the auth events.
auth_events = await store.get_events(
event.auth_event_ids(),
redact_behaviour=EventRedactBehaviour.as_is,
allow_rejected=True,
)
room_id = event.room_id
for auth_event in auth_events:
auth_dict: MutableStateMap[str] = {}
for auth_event_id in event.auth_event_ids():
auth_event = auth_events.get(auth_event_id)

# we should have all the auth events by now, so if we do not, that suggests
# a synapse programming error
if auth_event is None:
raise RuntimeError(
f"Event {event.event_id} has unknown auth event {auth_event_id}"
)

# We need to ensure that the auth events are actually for the same room, to
# stop people from using powers they've been granted in other rooms for
# example.
if auth_event.room_id != room_id:
raise AuthError(
403,
"During auth for event %s in room %s, found event %s in the state "
"which is in room %s"
% (event.event_id, room_id, auth_event.event_id, auth_event.room_id),
% (event.event_id, room_id, auth_event_id, auth_event.room_id),
)

# We also need to check that the auth event itself is not rejected.
if auth_event.rejected_reason:
raise AuthError(
403,
"During auth for event %s: found rejected event %s in the state"
% (event.event_id, auth_event.event_id),
)

auth_dict[(auth_event.type, auth_event.state_key)] = auth_event_id

# Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules
#
# 1. If type is m.room.create:
Expand All @@ -181,16 +201,46 @@ def check_auth_rules_for_event(
"room appears to have unsupported version %s" % (room_version_prop,),
)

logger.debug("Allowing! %s", event)
return

auth_dict = {(e.type, e.state_key): e for e in auth_events}

# 3. If event does not have a m.room.create in its auth_events, reject.
creation_event = auth_dict.get((EventTypes.Create, ""), None)
if not creation_event:
raise AuthError(403, "No create event in auth events")


def check_state_dependent_auth_rules(
event: "EventBase",
auth_events: Iterable["EventBase"],
) -> None:
"""Check that an event complies with auth rules that depend on room state
Runs through the parts of the auth rules that check an event against bits of room
state.
Note:
- it's fine for use in state resolution, when we have already decided whether to
accept the event or not, and are now trying to decide whether it should make it
into the room state
- when we're doing the initial event auth, it is only suitable in combination with
a bunch of other tests (including, but not limited to, check_state_independent_auth_rules).
Args:
event: the event being checked.
auth_events: the room state to check the events against.
Raises:
AuthError if the checks fail
"""
# there are no state-dependent auth rules for create events.
if event.type == EventTypes.Create:
logger.debug("Allowing! %s", event)
return

auth_dict = {(e.type, e.state_key): e for e in auth_events}

# additional check for m.federate
creating_domain = get_domain_from_id(event.room_id)
originating_domain = get_domain_from_id(event.sender)
Expand Down
8 changes: 6 additions & 2 deletions synapse/handlers/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
)
from synapse.api.errors import AuthError, Codes, SynapseError
from synapse.api.room_versions import RoomVersion
from synapse.event_auth import check_auth_rules_for_event
from synapse.event_auth import (
check_state_dependent_auth_rules,
check_state_independent_auth_rules,
)
from synapse.events import EventBase
from synapse.events.builder import EventBuilder
from synapse.events.snapshot import EventContext
Expand Down Expand Up @@ -52,9 +55,10 @@ async def check_auth_rules_from_context(
context: EventContext,
) -> None:
"""Check an event passes the auth rules at its own auth events"""
await check_state_independent_auth_rules(self._store, event)
auth_event_ids = event.auth_event_ids()
auth_events_by_id = await self._store.get_events(auth_event_ids)
check_auth_rules_for_event(event, auth_events_by_id.values())
check_state_dependent_auth_rules(event, auth_events_by_id.values())

def compute_auth_events(
self,
Expand Down
27 changes: 17 additions & 10 deletions synapse/handlers/federation_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion, RoomVersions
from synapse.event_auth import (
auth_types_for_event,
check_auth_rules_for_event,
check_state_dependent_auth_rules,
check_state_independent_auth_rules,
validate_event_for_room_version,
)
from synapse.events import EventBase
Expand Down Expand Up @@ -1430,7 +1431,9 @@ async def _auth_and_persist_outliers_inner(
allow_rejected=True,
)

def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
events_and_contexts_to_persist: List[Tuple[EventBase, EventContext]] = []

async def prep(event: EventBase) -> None:
with nested_logging_context(suffix=event.event_id):
auth = []
for auth_event_id in event.auth_event_ids():
Expand All @@ -1444,7 +1447,7 @@ def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
event,
auth_event_id,
)
return None
return
auth.append(ae)

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

return event, context
events_and_contexts_to_persist.append((event, context))

for event in fetched_events:
await prep(event)

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

# ... and check that the event passes auth at those auth events.
try:
check_auth_rules_for_event(event, claimed_auth_events)
await check_state_independent_auth_rules(self._store, event)
check_state_dependent_auth_rules(event, claimed_auth_events)
except AuthError as e:
logger.warning(
"While checking auth of %r against auth_events: %s", event, e
Expand Down Expand Up @@ -1563,7 +1570,7 @@ async def _check_event_auth(
auth_events_for_auth = calculated_auth_event_map

try:
check_auth_rules_for_event(event, auth_events_for_auth.values())
check_state_dependent_auth_rules(event, auth_events_for_auth.values())
except AuthError as e:
logger.warning("Failed auth resolution for %r because %s", event, e)
context.rejected = RejectedReason.AUTH_ERROR
Expand Down Expand Up @@ -1663,7 +1670,7 @@ async def _check_for_soft_fail(
)

try:
check_auth_rules_for_event(event, current_auth_events)
check_state_dependent_auth_rules(event, current_auth_events)
except AuthError as e:
logger.warning(
"Soft-failing %r (from %s) because %s",
Expand Down
4 changes: 2 additions & 2 deletions synapse/state/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def _resolve_auth_events(
auth_events[(prev_event.type, prev_event.state_key)] = prev_event
try:
# The signatures have already been checked at this point
event_auth.check_auth_rules_for_event(
event_auth.check_state_dependent_auth_rules(
event,
auth_events.values(),
)
Expand All @@ -347,7 +347,7 @@ def _resolve_normal_events(
for event in _ordered_events(events):
try:
# The signatures have already been checked at this point
event_auth.check_auth_rules_for_event(
event_auth.check_state_dependent_auth_rules(
event,
auth_events.values(),
)
Expand Down
2 changes: 1 addition & 1 deletion synapse/state/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ async def _iterative_auth_checks(
auth_events[key] = event_map[ev_id]

try:
event_auth.check_auth_rules_for_event(
event_auth.check_state_dependent_auth_rules(
event,
auth_events.values(),
)
Expand Down
Loading

0 comments on commit 8ecf6be

Please sign in to comment.