Merge branch 'rav/simplify_event_auth_interface' into develop

This commit is contained in:
Richard van der Hoff 2022-06-13 11:34:59 +01:00
commit f68b5e5773
11 changed files with 239 additions and 228 deletions

1
changelog.d/13017.misc Normal file
View file

@ -0,0 +1 @@
Remove redundant `room_version` parameters from event auth functions.

View file

@ -45,9 +45,7 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def validate_event_for_room_version( def validate_event_for_room_version(event: "EventBase") -> None:
room_version_obj: RoomVersion, event: "EventBase"
) -> None:
"""Ensure that the event complies with the limits, and has the right signatures """Ensure that the event complies with the limits, and has the right signatures
NB: does not *validate* the signatures - it assumes that any signatures present NB: does not *validate* the signatures - it assumes that any signatures present
@ -60,12 +58,10 @@ def validate_event_for_room_version(
NB: This is used to check events that have been received over federation. As such, NB: This is used to check events that have been received over federation. As such,
it can only enforce the checks specified in the relevant room version, to avoid it can only enforce the checks specified in the relevant room version, to avoid
a split-brain situation where some servers accept such events, and others reject a split-brain situation where some servers accept such events, and others reject
them. them. See also EventValidator, which contains extra checks which are applied only to
locally-generated events.
TODO: consider moving this into EventValidator
Args: Args:
room_version_obj: the version of the room which contains this event
event: the event to be checked event: the event to be checked
Raises: Raises:
@ -103,7 +99,7 @@ def validate_event_for_room_version(
raise AuthError(403, "Event not signed by sending server") raise AuthError(403, "Event not signed by sending server")
is_invite_via_allow_rule = ( is_invite_via_allow_rule = (
room_version_obj.msc3083_join_rules event.room_version.msc3083_join_rules
and event.type == EventTypes.Member and event.type == EventTypes.Member
and event.membership == Membership.JOIN and event.membership == Membership.JOIN
and EventContentFields.AUTHORISING_USER in event.content and EventContentFields.AUTHORISING_USER in event.content
@ -117,7 +113,6 @@ def validate_event_for_room_version(
def check_auth_rules_for_event( def check_auth_rules_for_event(
room_version_obj: RoomVersion,
event: "EventBase", event: "EventBase",
auth_events: Iterable["EventBase"], auth_events: Iterable["EventBase"],
) -> None: ) -> None:
@ -136,7 +131,6 @@ def check_auth_rules_for_event(
a bunch of other tests. a bunch of other tests.
Args: Args:
room_version_obj: the version of the room
event: the event being checked. event: the event being checked.
auth_events: the room state to check the events against. auth_events: the room state to check the events against.
@ -205,7 +199,10 @@ def check_auth_rules_for_event(
raise AuthError(403, "This room has been marked as unfederatable.") raise AuthError(403, "This room has been marked as unfederatable.")
# 4. If type is m.room.aliases # 4. If type is m.room.aliases
if event.type == EventTypes.Aliases and room_version_obj.special_case_aliases_auth: if (
event.type == EventTypes.Aliases
and event.room_version.special_case_aliases_auth
):
# 4a. If event has no state_key, reject # 4a. If event has no state_key, reject
if not event.is_state(): if not event.is_state():
raise AuthError(403, "Alias event must be a state event") raise AuthError(403, "Alias event must be a state event")
@ -225,7 +222,7 @@ def check_auth_rules_for_event(
# 5. If type is m.room.membership # 5. If type is m.room.membership
if event.type == EventTypes.Member: if event.type == EventTypes.Member:
_is_membership_change_allowed(room_version_obj, event, auth_dict) _is_membership_change_allowed(event.room_version, event, auth_dict)
logger.debug("Allowing! %s", event) logger.debug("Allowing! %s", event)
return return
@ -247,17 +244,17 @@ def check_auth_rules_for_event(
_can_send_event(event, auth_dict) _can_send_event(event, auth_dict)
if event.type == EventTypes.PowerLevels: if event.type == EventTypes.PowerLevels:
_check_power_levels(room_version_obj, event, auth_dict) _check_power_levels(event.room_version, event, auth_dict)
if event.type == EventTypes.Redaction: if event.type == EventTypes.Redaction:
check_redaction(room_version_obj, event, auth_dict) check_redaction(event.room_version, event, auth_dict)
if ( if (
event.type == EventTypes.MSC2716_INSERTION event.type == EventTypes.MSC2716_INSERTION
or event.type == EventTypes.MSC2716_BATCH or event.type == EventTypes.MSC2716_BATCH
or event.type == EventTypes.MSC2716_MARKER or event.type == EventTypes.MSC2716_MARKER
): ):
check_historical(room_version_obj, event, auth_dict) check_historical(event.room_version, event, auth_dict)
logger.debug("Allowing! %s", event) logger.debug("Allowing! %s", event)

View file

@ -35,6 +35,10 @@ class EventValidator:
def validate_new(self, event: EventBase, config: HomeServerConfig) -> None: def validate_new(self, event: EventBase, config: HomeServerConfig) -> None:
"""Validates the event has roughly the right format """Validates the event has roughly the right format
Suitable for checking a locally-created event. It has stricter checks than
is appropriate for an event received over federation (for which, see
event_auth.validate_event_for_room_version)
Args: Args:
event: The event to validate. event: The event to validate.
config: The homeserver's configuration. config: The homeserver's configuration.

View file

@ -48,14 +48,13 @@ class EventAuthHandler:
async def check_auth_rules_from_context( async def check_auth_rules_from_context(
self, self,
room_version_obj: RoomVersion,
event: EventBase, event: EventBase,
context: EventContext, context: EventContext,
) -> None: ) -> None:
"""Check an event passes the auth rules at its own auth events""" """Check an event passes the auth rules at its own auth events"""
auth_event_ids = event.auth_event_ids() auth_event_ids = event.auth_event_ids()
auth_events_by_id = await self._store.get_events(auth_event_ids) auth_events_by_id = await self._store.get_events(auth_event_ids)
check_auth_rules_for_event(room_version_obj, event, auth_events_by_id.values()) check_auth_rules_for_event(event, auth_events_by_id.values())
def compute_auth_events( def compute_auth_events(
self, self,

View file

@ -800,9 +800,7 @@ class FederationHandler:
# The remote hasn't signed it yet, obviously. We'll do the full checks # The remote hasn't signed it yet, obviously. We'll do the full checks
# when we get the event back in `on_send_join_request` # when we get the event back in `on_send_join_request`
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(event, context)
room_version, event, context
)
return event return event
async def on_invite_request( async def on_invite_request(
@ -973,9 +971,7 @@ class FederationHandler:
try: try:
# The remote hasn't signed it yet, obviously. We'll do the full checks # The remote hasn't signed it yet, obviously. We'll do the full checks
# when we get the event back in `on_send_leave_request` # when we get the event back in `on_send_leave_request`
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(event, context)
room_version_obj, event, context
)
except AuthError as e: except AuthError as e:
logger.warning("Failed to create new leave %r because %s", event, e) logger.warning("Failed to create new leave %r because %s", event, e)
raise e raise e
@ -1034,9 +1030,7 @@ class FederationHandler:
try: try:
# The remote hasn't signed it yet, obviously. We'll do the full checks # The remote hasn't signed it yet, obviously. We'll do the full checks
# when we get the event back in `on_send_knock_request` # when we get the event back in `on_send_knock_request`
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(event, context)
room_version_obj, event, context
)
except AuthError as e: except AuthError as e:
logger.warning("Failed to create new knock %r because %s", event, e) logger.warning("Failed to create new knock %r because %s", event, e)
raise e raise e
@ -1207,9 +1201,9 @@ class FederationHandler:
event.internal_metadata.send_on_behalf_of = self.hs.hostname event.internal_metadata.send_on_behalf_of = self.hs.hostname
try: try:
validate_event_for_room_version(room_version_obj, event) validate_event_for_room_version(event)
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(
room_version_obj, event, context event, context
) )
except AuthError as e: except AuthError as e:
logger.warning("Denying new third party invite %r because %s", event, e) logger.warning("Denying new third party invite %r because %s", event, e)
@ -1259,10 +1253,8 @@ class FederationHandler:
) )
try: try:
validate_event_for_room_version(room_version_obj, event) validate_event_for_room_version(event)
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(event, context)
room_version_obj, event, context
)
except AuthError as e: except AuthError as e:
logger.warning("Denying third party invite %r because %s", event, e) logger.warning("Denying third party invite %r because %s", event, e)
raise e raise e

View file

@ -1430,9 +1430,6 @@ class FederationEventHandler:
allow_rejected=True, allow_rejected=True,
) )
room_version = await self._store.get_room_version_id(room_id)
room_version_obj = KNOWN_ROOM_VERSIONS[room_version]
def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]: def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
with nested_logging_context(suffix=event.event_id): with nested_logging_context(suffix=event.event_id):
auth = [] auth = []
@ -1455,8 +1452,8 @@ class FederationEventHandler:
context = EventContext.for_outlier(self._storage_controllers) context = EventContext.for_outlier(self._storage_controllers)
try: try:
validate_event_for_room_version(room_version_obj, event) validate_event_for_room_version(event)
check_auth_rules_for_event(room_version_obj, event, auth) check_auth_rules_for_event(event, auth)
except AuthError as e: except AuthError as e:
logger.warning("Rejecting %r because %s", event, e) logger.warning("Rejecting %r because %s", event, e)
context.rejected = RejectedReason.AUTH_ERROR context.rejected = RejectedReason.AUTH_ERROR
@ -1499,11 +1496,8 @@ class FederationEventHandler:
assert not event.internal_metadata.outlier assert not event.internal_metadata.outlier
# first of all, check that the event itself is valid. # first of all, check that the event itself is valid.
room_version = await self._store.get_room_version_id(event.room_id)
room_version_obj = KNOWN_ROOM_VERSIONS[room_version]
try: try:
validate_event_for_room_version(room_version_obj, event) validate_event_for_room_version(event)
except AuthError as e: except AuthError as e:
logger.warning("While validating received event %r: %s", event, e) logger.warning("While validating received event %r: %s", event, e)
# TODO: use a different rejected reason here? # TODO: use a different rejected reason here?
@ -1521,7 +1515,7 @@ class FederationEventHandler:
# ... and check that the event passes auth at those auth events. # ... and check that the event passes auth at those auth events.
try: try:
check_auth_rules_for_event(room_version_obj, event, claimed_auth_events) check_auth_rules_for_event(event, claimed_auth_events)
except AuthError as e: except AuthError as e:
logger.warning( logger.warning(
"While checking auth of %r against auth_events: %s", event, e "While checking auth of %r against auth_events: %s", event, e
@ -1569,9 +1563,7 @@ class FederationEventHandler:
auth_events_for_auth = calculated_auth_event_map auth_events_for_auth = calculated_auth_event_map
try: try:
check_auth_rules_for_event( check_auth_rules_for_event(event, auth_events_for_auth.values())
room_version_obj, event, auth_events_for_auth.values()
)
except AuthError as e: except AuthError as e:
logger.warning("Failed auth resolution for %r because %s", event, e) logger.warning("Failed auth resolution for %r because %s", event, e)
context.rejected = RejectedReason.AUTH_ERROR context.rejected = RejectedReason.AUTH_ERROR
@ -1671,7 +1663,7 @@ class FederationEventHandler:
) )
try: try:
check_auth_rules_for_event(room_version_obj, event, current_auth_events) check_auth_rules_for_event(event, current_auth_events)
except AuthError as e: except AuthError as e:
logger.warning( logger.warning(
"Soft-failing %r (from %s) because %s", "Soft-failing %r (from %s) because %s",

View file

@ -42,7 +42,7 @@ from synapse.api.errors import (
SynapseError, SynapseError,
UnsupportedRoomVersionError, UnsupportedRoomVersionError,
) )
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.api.urls import ConsentURIBuilder from synapse.api.urls import ConsentURIBuilder
from synapse.event_auth import validate_event_for_room_version from synapse.event_auth import validate_event_for_room_version
from synapse.events import EventBase, relation_from_event from synapse.events import EventBase, relation_from_event
@ -1274,23 +1274,6 @@ class EventCreationHandler:
) )
return prev_event return prev_event
if event.is_state() and (event.type, event.state_key) == (
EventTypes.Create,
"",
):
room_version_id = event.content.get(
"room_version", RoomVersions.V1.identifier
)
maybe_room_version_obj = KNOWN_ROOM_VERSIONS.get(room_version_id)
if not maybe_room_version_obj:
raise UnsupportedRoomVersionError(
"Attempt to create a room with unsupported room version %s"
% (room_version_id,)
)
room_version_obj = maybe_room_version_obj
else:
room_version_obj = await self.store.get_room_version(event.room_id)
if event.internal_metadata.is_out_of_band_membership(): if event.internal_metadata.is_out_of_band_membership():
# the only sort of out-of-band-membership events we expect to see here are # the only sort of out-of-band-membership events we expect to see here are
# invite rejections and rescinded knocks that we have generated ourselves. # invite rejections and rescinded knocks that we have generated ourselves.
@ -1298,9 +1281,9 @@ class EventCreationHandler:
assert event.content["membership"] == Membership.LEAVE assert event.content["membership"] == Membership.LEAVE
else: else:
try: try:
validate_event_for_room_version(room_version_obj, event) validate_event_for_room_version(event)
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(
room_version_obj, event, context event, context
) )
except AuthError as err: except AuthError as err:
logger.warning("Denying new event %r because %s", event, err) logger.warning("Denying new event %r because %s", event, err)

View file

@ -226,10 +226,9 @@ class RoomCreationHandler:
}, },
}, },
) )
old_room_version = await self.store.get_room_version(old_room_id) validate_event_for_room_version(tombstone_event)
validate_event_for_room_version(old_room_version, tombstone_event)
await self._event_auth_handler.check_auth_rules_from_context( await self._event_auth_handler.check_auth_rules_from_context(
old_room_version, tombstone_event, tombstone_context tombstone_event, tombstone_context
) )
# Upgrade the room # Upgrade the room

View file

@ -30,7 +30,7 @@ from typing import (
from synapse import event_auth from synapse import event_auth
from synapse.api.constants import EventTypes from synapse.api.constants import EventTypes
from synapse.api.errors import AuthError from synapse.api.errors import AuthError
from synapse.api.room_versions import RoomVersion, RoomVersions from synapse.api.room_versions import RoomVersion
from synapse.events import EventBase from synapse.events import EventBase
from synapse.types import MutableStateMap, StateMap from synapse.types import MutableStateMap, StateMap
@ -331,7 +331,6 @@ def _resolve_auth_events(
try: try:
# The signatures have already been checked at this point # The signatures have already been checked at this point
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1,
event, event,
auth_events.values(), auth_events.values(),
) )
@ -349,7 +348,6 @@ def _resolve_normal_events(
try: try:
# The signatures have already been checked at this point # The signatures have already been checked at this point
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1,
event, event,
auth_events.values(), auth_events.values(),
) )

View file

@ -574,7 +574,6 @@ async def _iterative_auth_checks(
try: try:
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
room_version,
event, event,
auth_events.values(), auth_events.values(),
) )

View file

@ -15,10 +15,12 @@
import unittest import unittest
from typing import Optional from typing import Optional
from parameterized import parameterized
from synapse import event_auth from synapse import event_auth
from synapse.api.constants import EventContentFields from synapse.api.constants import EventContentFields
from synapse.api.errors import AuthError from synapse.api.errors import AuthError
from synapse.api.room_versions import RoomVersions from synapse.api.room_versions import EventFormatVersions, RoomVersion, RoomVersions
from synapse.events import EventBase, make_event_from_dict from synapse.events import EventBase, make_event_from_dict
from synapse.types import JsonDict, get_domain_from_id from synapse.types import JsonDict, get_domain_from_id
@ -30,38 +32,39 @@ class EventAuthTestCase(unittest.TestCase):
""" """
creator = "@creator:example.com" creator = "@creator:example.com"
auth_events = [ auth_events = [
_create_event(creator), _create_event(RoomVersions.V9, creator),
_join_event(creator), _join_event(RoomVersions.V9, creator),
] ]
# creator should be able to send state # creator should be able to send state
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V9, _random_state_event(RoomVersions.V9, creator),
_random_state_event(creator),
auth_events, auth_events,
) )
# ... but a rejected join_rules event should cause it to be rejected # ... but a rejected join_rules event should cause it to be rejected
rejected_join_rules = _join_rules_event(creator, "public") rejected_join_rules = _join_rules_event(
RoomVersions.V9,
creator,
"public",
)
rejected_join_rules.rejected_reason = "stinky" rejected_join_rules.rejected_reason = "stinky"
auth_events.append(rejected_join_rules) auth_events.append(rejected_join_rules)
self.assertRaises( self.assertRaises(
AuthError, AuthError,
event_auth.check_auth_rules_for_event, event_auth.check_auth_rules_for_event,
RoomVersions.V9, _random_state_event(RoomVersions.V9, creator),
_random_state_event(creator),
auth_events, auth_events,
) )
# ... even if there is *also* a good join rules # ... even if there is *also* a good join rules
auth_events.append(_join_rules_event(creator, "public")) auth_events.append(_join_rules_event(RoomVersions.V9, creator, "public"))
self.assertRaises( self.assertRaises(
AuthError, AuthError,
event_auth.check_auth_rules_for_event, event_auth.check_auth_rules_for_event,
RoomVersions.V9, _random_state_event(RoomVersions.V9, creator),
_random_state_event(creator),
auth_events, auth_events,
) )
@ -73,15 +76,14 @@ class EventAuthTestCase(unittest.TestCase):
creator = "@creator:example.com" creator = "@creator:example.com"
joiner = "@joiner:example.com" joiner = "@joiner:example.com"
auth_events = [ auth_events = [
_create_event(creator), _create_event(RoomVersions.V1, creator),
_join_event(creator), _join_event(RoomVersions.V1, creator),
_join_event(joiner), _join_event(RoomVersions.V1, joiner),
] ]
# creator should be able to send state # creator should be able to send state
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1, _random_state_event(RoomVersions.V1, creator),
_random_state_event(creator),
auth_events, auth_events,
) )
@ -89,8 +91,7 @@ class EventAuthTestCase(unittest.TestCase):
self.assertRaises( self.assertRaises(
AuthError, AuthError,
event_auth.check_auth_rules_for_event, event_auth.check_auth_rules_for_event,
RoomVersions.V1, _random_state_event(RoomVersions.V1, joiner),
_random_state_event(joiner),
auth_events, auth_events,
) )
@ -104,28 +105,28 @@ class EventAuthTestCase(unittest.TestCase):
king = "@joiner2:example.com" king = "@joiner2:example.com"
auth_events = [ auth_events = [
_create_event(creator), _create_event(RoomVersions.V1, creator),
_join_event(creator), _join_event(RoomVersions.V1, creator),
_power_levels_event( _power_levels_event(
creator, {"state_default": "30", "users": {pleb: "29", king: "30"}} RoomVersions.V1,
creator,
{"state_default": "30", "users": {pleb: "29", king: "30"}},
), ),
_join_event(pleb), _join_event(RoomVersions.V1, pleb),
_join_event(king), _join_event(RoomVersions.V1, king),
] ]
# pleb should not be able to send state # pleb should not be able to send state
self.assertRaises( self.assertRaises(
AuthError, AuthError,
event_auth.check_auth_rules_for_event, event_auth.check_auth_rules_for_event,
RoomVersions.V1, _random_state_event(RoomVersions.V1, pleb),
_random_state_event(pleb),
auth_events, auth_events,
), ),
# king should be able to send state # king should be able to send state
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1, _random_state_event(RoomVersions.V1, king),
_random_state_event(king),
auth_events, auth_events,
) )
@ -134,37 +135,33 @@ class EventAuthTestCase(unittest.TestCase):
creator = "@creator:example.com" creator = "@creator:example.com"
other = "@other:example.com" other = "@other:example.com"
auth_events = [ auth_events = [
_create_event(creator), _create_event(RoomVersions.V1, creator),
_join_event(creator), _join_event(RoomVersions.V1, creator),
] ]
# creator should be able to send aliases # creator should be able to send aliases
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1, _alias_event(RoomVersions.V1, creator),
_alias_event(creator),
auth_events, auth_events,
) )
# Reject an event with no state key. # Reject an event with no state key.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1, _alias_event(RoomVersions.V1, creator, state_key=""),
_alias_event(creator, state_key=""),
auth_events, auth_events,
) )
# If the domain of the sender does not match the state key, reject. # If the domain of the sender does not match the state key, reject.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1, _alias_event(RoomVersions.V1, creator, state_key="test.com"),
_alias_event(creator, state_key="test.com"),
auth_events, auth_events,
) )
# Note that the member does *not* need to be in the room. # Note that the member does *not* need to be in the room.
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V1, _alias_event(RoomVersions.V1, other),
_alias_event(other),
auth_events, auth_events,
) )
@ -173,38 +170,35 @@ class EventAuthTestCase(unittest.TestCase):
creator = "@creator:example.com" creator = "@creator:example.com"
other = "@other:example.com" other = "@other:example.com"
auth_events = [ auth_events = [
_create_event(creator), _create_event(RoomVersions.V6, creator),
_join_event(creator), _join_event(RoomVersions.V6, creator),
] ]
# creator should be able to send aliases # creator should be able to send aliases
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _alias_event(RoomVersions.V6, creator),
_alias_event(creator),
auth_events, auth_events,
) )
# No particular checks are done on the state key. # No particular checks are done on the state key.
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _alias_event(RoomVersions.V6, creator, state_key=""),
_alias_event(creator, state_key=""),
auth_events, auth_events,
) )
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _alias_event(RoomVersions.V6, creator, state_key="test.com"),
_alias_event(creator, state_key="test.com"),
auth_events, auth_events,
) )
# Per standard auth rules, the member must be in the room. # Per standard auth rules, the member must be in the room.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _alias_event(RoomVersions.V6, other),
_alias_event(other),
auth_events, auth_events,
) )
def test_msc2209(self): @parameterized.expand([(RoomVersions.V1, True), (RoomVersions.V6, False)])
def test_notifications(self, room_version: RoomVersion, allow_modification: bool):
""" """
Notifications power levels get checked due to MSC2209. Notifications power levels get checked due to MSC2209.
""" """
@ -212,28 +206,26 @@ class EventAuthTestCase(unittest.TestCase):
pleb = "@joiner:example.com" pleb = "@joiner:example.com"
auth_events = [ auth_events = [
_create_event(creator), _create_event(room_version, creator),
_join_event(creator), _join_event(room_version, creator),
_power_levels_event( _power_levels_event(
creator, {"state_default": "30", "users": {pleb: "30"}} room_version, creator, {"state_default": "30", "users": {pleb: "30"}}
), ),
_join_event(pleb), _join_event(room_version, pleb),
] ]
# pleb should be able to modify the notifications power level. pl_event = _power_levels_event(
event_auth.check_auth_rules_for_event( room_version, pleb, {"notifications": {"room": 100}}
RoomVersions.V1,
_power_levels_event(pleb, {"notifications": {"room": 100}}),
auth_events,
) )
# But an MSC2209 room rejects this change. # on room V1, pleb should be able to modify the notifications power level.
with self.assertRaises(AuthError): if allow_modification:
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(pl_event, auth_events)
RoomVersions.V6,
_power_levels_event(pleb, {"notifications": {"room": 100}}), else:
auth_events, # But an MSC2209 room rejects this change.
) with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event(pl_event, auth_events)
def test_join_rules_public(self): def test_join_rules_public(self):
""" """
@ -243,58 +235,60 @@ class EventAuthTestCase(unittest.TestCase):
pleb = "@joiner:example.com" pleb = "@joiner:example.com"
auth_events = { auth_events = {
("m.room.create", ""): _create_event(creator), ("m.room.create", ""): _create_event(RoomVersions.V6, creator),
("m.room.member", creator): _join_event(creator), ("m.room.member", creator): _join_event(RoomVersions.V6, creator),
("m.room.join_rules", ""): _join_rules_event(creator, "public"), ("m.room.join_rules", ""): _join_rules_event(
RoomVersions.V6, creator, "public"
),
} }
# Check join. # Check join.
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user cannot be force-joined to a room. # A user cannot be force-joined to a room.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _member_event(RoomVersions.V6, pleb, "join", sender=creator),
_member_event(pleb, "join", sender=creator),
auth_events.values(), auth_events.values(),
) )
# Banned should be rejected. # Banned should be rejected.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "ban") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V6, pleb, "ban"
)
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user who left can re-join. # A user who left can re-join.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "leave") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V6, pleb, "leave"
)
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user can send a join if they're in the room. # A user can send a join if they're in the room.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "join") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V6, pleb, "join"
)
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user can accept an invite. # A user can accept an invite.
auth_events[("m.room.member", pleb)] = _member_event( auth_events[("m.room.member", pleb)] = _member_event(
pleb, "invite", sender=creator RoomVersions.V6, pleb, "invite", sender=creator
) )
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
@ -306,64 +300,88 @@ class EventAuthTestCase(unittest.TestCase):
pleb = "@joiner:example.com" pleb = "@joiner:example.com"
auth_events = { auth_events = {
("m.room.create", ""): _create_event(creator), ("m.room.create", ""): _create_event(RoomVersions.V6, creator),
("m.room.member", creator): _join_event(creator), ("m.room.member", creator): _join_event(RoomVersions.V6, creator),
("m.room.join_rules", ""): _join_rules_event(creator, "invite"), ("m.room.join_rules", ""): _join_rules_event(
RoomVersions.V6, creator, "invite"
),
} }
# A join without an invite is rejected. # A join without an invite is rejected.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user cannot be force-joined to a room. # A user cannot be force-joined to a room.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _member_event(RoomVersions.V6, pleb, "join", sender=creator),
_member_event(pleb, "join", sender=creator),
auth_events.values(), auth_events.values(),
) )
# Banned should be rejected. # Banned should be rejected.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "ban") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V6, pleb, "ban"
)
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user who left cannot re-join. # A user who left cannot re-join.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "leave") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V6, pleb, "leave"
)
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user can send a join if they're in the room. # A user can send a join if they're in the room.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "join") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V6, pleb, "join"
)
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user can accept an invite. # A user can accept an invite.
auth_events[("m.room.member", pleb)] = _member_event( auth_events[("m.room.member", pleb)] = _member_event(
pleb, "invite", sender=creator RoomVersions.V6, pleb, "invite", sender=creator
) )
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V6, _join_event(RoomVersions.V6, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
def test_join_rules_msc3083_restricted(self): def test_join_rules_restricted_old_room(self) -> None:
"""Old room versions should reject joins to restricted rooms"""
creator = "@creator:example.com"
pleb = "@joiner:example.com"
auth_events = {
("m.room.create", ""): _create_event(RoomVersions.V6, creator),
("m.room.member", creator): _join_event(RoomVersions.V6, creator),
("m.room.power_levels", ""): _power_levels_event(
RoomVersions.V6, creator, {"invite": 0}
),
("m.room.join_rules", ""): _join_rules_event(
RoomVersions.V6, creator, "restricted"
),
}
with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event(
_join_event(RoomVersions.V6, pleb),
auth_events.values(),
)
def test_join_rules_msc3083_restricted(self) -> None:
""" """
Test joining a restricted room from MSC3083. Test joining a restricted room from MSC3083.
@ -377,29 +395,25 @@ class EventAuthTestCase(unittest.TestCase):
pleb = "@joiner:example.com" pleb = "@joiner:example.com"
auth_events = { auth_events = {
("m.room.create", ""): _create_event(creator), ("m.room.create", ""): _create_event(RoomVersions.V8, creator),
("m.room.member", creator): _join_event(creator), ("m.room.member", creator): _join_event(RoomVersions.V8, creator),
("m.room.power_levels", ""): _power_levels_event(creator, {"invite": 0}), ("m.room.power_levels", ""): _power_levels_event(
("m.room.join_rules", ""): _join_rules_event(creator, "restricted"), RoomVersions.V8, creator, {"invite": 0}
),
("m.room.join_rules", ""): _join_rules_event(
RoomVersions.V8, creator, "restricted"
),
} }
# Older room versions don't understand this join rule
with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event(
RoomVersions.V6,
_join_event(pleb),
auth_events.values(),
)
# A properly formatted join event should work. # A properly formatted join event should work.
authorised_join_event = _join_event( authorised_join_event = _join_event(
RoomVersions.V8,
pleb, pleb,
additional_content={ additional_content={
EventContentFields.AUTHORISING_USER: "@creator:example.com" EventContentFields.AUTHORISING_USER: "@creator:example.com"
}, },
) )
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8,
authorised_join_event, authorised_join_event,
auth_events.values(), auth_events.values(),
) )
@ -408,14 +422,16 @@ class EventAuthTestCase(unittest.TestCase):
# are done properly). # are done properly).
pl_auth_events = auth_events.copy() pl_auth_events = auth_events.copy()
pl_auth_events[("m.room.power_levels", "")] = _power_levels_event( pl_auth_events[("m.room.power_levels", "")] = _power_levels_event(
creator, {"invite": 100, "users": {"@inviter:foo.test": 150}} RoomVersions.V8,
creator,
{"invite": 100, "users": {"@inviter:foo.test": 150}},
) )
pl_auth_events[("m.room.member", "@inviter:foo.test")] = _join_event( pl_auth_events[("m.room.member", "@inviter:foo.test")] = _join_event(
"@inviter:foo.test" RoomVersions.V8, "@inviter:foo.test"
) )
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8,
_join_event( _join_event(
RoomVersions.V8,
pleb, pleb,
additional_content={ additional_content={
EventContentFields.AUTHORISING_USER: "@inviter:foo.test" EventContentFields.AUTHORISING_USER: "@inviter:foo.test"
@ -427,20 +443,21 @@ class EventAuthTestCase(unittest.TestCase):
# A join which is missing an authorised server is rejected. # A join which is missing an authorised server is rejected.
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8, _join_event(RoomVersions.V8, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# An join authorised by a user who is not in the room is rejected. # An join authorised by a user who is not in the room is rejected.
pl_auth_events = auth_events.copy() pl_auth_events = auth_events.copy()
pl_auth_events[("m.room.power_levels", "")] = _power_levels_event( pl_auth_events[("m.room.power_levels", "")] = _power_levels_event(
creator, {"invite": 100, "users": {"@other:example.com": 150}} RoomVersions.V8,
creator,
{"invite": 100, "users": {"@other:example.com": 150}},
) )
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8,
_join_event( _join_event(
RoomVersions.V8,
pleb, pleb,
additional_content={ additional_content={
EventContentFields.AUTHORISING_USER: "@other:example.com" EventContentFields.AUTHORISING_USER: "@other:example.com"
@ -453,8 +470,8 @@ class EventAuthTestCase(unittest.TestCase):
# *would* be valid, but is sent be a different user.) # *would* be valid, but is sent be a different user.)
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8,
_member_event( _member_event(
RoomVersions.V8,
pleb, pleb,
"join", "join",
sender=creator, sender=creator,
@ -466,39 +483,41 @@ class EventAuthTestCase(unittest.TestCase):
) )
# Banned should be rejected. # Banned should be rejected.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "ban") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V8, pleb, "ban"
)
with self.assertRaises(AuthError): with self.assertRaises(AuthError):
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8,
authorised_join_event, authorised_join_event,
auth_events.values(), auth_events.values(),
) )
# A user who left can re-join. # A user who left can re-join.
auth_events[("m.room.member", pleb)] = _member_event(pleb, "leave") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V8, pleb, "leave"
)
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8,
authorised_join_event, authorised_join_event,
auth_events.values(), auth_events.values(),
) )
# A user can send a join if they're in the room. (This doesn't need to # A user can send a join if they're in the room. (This doesn't need to
# be authorised since the user is already joined.) # be authorised since the user is already joined.)
auth_events[("m.room.member", pleb)] = _member_event(pleb, "join") auth_events[("m.room.member", pleb)] = _member_event(
RoomVersions.V8, pleb, "join"
)
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8, _join_event(RoomVersions.V8, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
# A user can accept an invite. (This doesn't need to be authorised since # A user can accept an invite. (This doesn't need to be authorised since
# the user was invited.) # the user was invited.)
auth_events[("m.room.member", pleb)] = _member_event( auth_events[("m.room.member", pleb)] = _member_event(
pleb, "invite", sender=creator RoomVersions.V8, pleb, "invite", sender=creator
) )
event_auth.check_auth_rules_for_event( event_auth.check_auth_rules_for_event(
RoomVersions.V8, _join_event(RoomVersions.V8, pleb),
_join_event(pleb),
auth_events.values(), auth_events.values(),
) )
@ -508,20 +527,25 @@ class EventAuthTestCase(unittest.TestCase):
TEST_ROOM_ID = "!test:room" TEST_ROOM_ID = "!test:room"
def _create_event(user_id: str) -> EventBase: def _create_event(
room_version: RoomVersion,
user_id: str,
) -> EventBase:
return make_event_from_dict( return make_event_from_dict(
{ {
"room_id": TEST_ROOM_ID, "room_id": TEST_ROOM_ID,
"event_id": _get_event_id(), **_maybe_get_event_id_dict_for_room_version(room_version),
"type": "m.room.create", "type": "m.room.create",
"state_key": "", "state_key": "",
"sender": user_id, "sender": user_id,
"content": {"creator": user_id}, "content": {"creator": user_id},
} },
room_version=room_version,
) )
def _member_event( def _member_event(
room_version: RoomVersion,
user_id: str, user_id: str,
membership: str, membership: str,
sender: Optional[str] = None, sender: Optional[str] = None,
@ -530,79 +554,102 @@ def _member_event(
return make_event_from_dict( return make_event_from_dict(
{ {
"room_id": TEST_ROOM_ID, "room_id": TEST_ROOM_ID,
"event_id": _get_event_id(), **_maybe_get_event_id_dict_for_room_version(room_version),
"type": "m.room.member", "type": "m.room.member",
"sender": sender or user_id, "sender": sender or user_id,
"state_key": user_id, "state_key": user_id,
"content": {"membership": membership, **(additional_content or {})}, "content": {"membership": membership, **(additional_content or {})},
"prev_events": [], "prev_events": [],
} },
room_version=room_version,
) )
def _join_event(user_id: str, additional_content: Optional[dict] = None) -> EventBase: def _join_event(
return _member_event(user_id, "join", additional_content=additional_content) room_version: RoomVersion,
user_id: str,
additional_content: Optional[dict] = None,
) -> EventBase:
return _member_event(
room_version,
user_id,
"join",
additional_content=additional_content,
)
def _power_levels_event(sender: str, content: JsonDict) -> EventBase: def _power_levels_event(
room_version: RoomVersion,
sender: str,
content: JsonDict,
) -> EventBase:
return make_event_from_dict( return make_event_from_dict(
{ {
"room_id": TEST_ROOM_ID, "room_id": TEST_ROOM_ID,
"event_id": _get_event_id(), **_maybe_get_event_id_dict_for_room_version(room_version),
"type": "m.room.power_levels", "type": "m.room.power_levels",
"sender": sender, "sender": sender,
"state_key": "", "state_key": "",
"content": content, "content": content,
} },
room_version=room_version,
) )
def _alias_event(sender: str, **kwargs) -> EventBase: def _alias_event(room_version: RoomVersion, sender: str, **kwargs) -> EventBase:
data = { data = {
"room_id": TEST_ROOM_ID, "room_id": TEST_ROOM_ID,
"event_id": _get_event_id(), **_maybe_get_event_id_dict_for_room_version(room_version),
"type": "m.room.aliases", "type": "m.room.aliases",
"sender": sender, "sender": sender,
"state_key": get_domain_from_id(sender), "state_key": get_domain_from_id(sender),
"content": {"aliases": []}, "content": {"aliases": []},
} }
data.update(**kwargs) data.update(**kwargs)
return make_event_from_dict(data) return make_event_from_dict(data, room_version=room_version)
def _random_state_event(sender: str) -> EventBase: def _random_state_event(room_version: RoomVersion, sender: str) -> EventBase:
return make_event_from_dict( return make_event_from_dict(
{ {
"room_id": TEST_ROOM_ID, "room_id": TEST_ROOM_ID,
"event_id": _get_event_id(), **_maybe_get_event_id_dict_for_room_version(room_version),
"type": "test.state", "type": "test.state",
"sender": sender, "sender": sender,
"state_key": "", "state_key": "",
"content": {"membership": "join"}, "content": {"membership": "join"},
} },
room_version=room_version,
) )
def _join_rules_event(sender: str, join_rule: str) -> EventBase: def _join_rules_event(
room_version: RoomVersion, sender: str, join_rule: str
) -> EventBase:
return make_event_from_dict( return make_event_from_dict(
{ {
"room_id": TEST_ROOM_ID, "room_id": TEST_ROOM_ID,
"event_id": _get_event_id(), **_maybe_get_event_id_dict_for_room_version(room_version),
"type": "m.room.join_rules", "type": "m.room.join_rules",
"sender": sender, "sender": sender,
"state_key": "", "state_key": "",
"content": { "content": {
"join_rule": join_rule, "join_rule": join_rule,
}, },
} },
room_version=room_version,
) )
event_count = 0 event_count = 0
def _get_event_id() -> str: def _maybe_get_event_id_dict_for_room_version(room_version: RoomVersion) -> dict:
"""If this room version needs it, generate an event id"""
if room_version.event_format != EventFormatVersions.V1:
return {}
global event_count global event_count
c = event_count c = event_count
event_count += 1 event_count += 1
return "!%i:example.com" % (c,) return {"event_id": "!%i:example.com" % (c,)}