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

Add a cache around server ACL checking #16360

Merged
merged 13 commits into from
Sep 26, 2023
2 changes: 1 addition & 1 deletion synapse/events/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
CANONICALJSON_MIN_INT,
validate_canonicaljson,
)
from synapse.federation.federation_server import server_acl_evaluator_from_event
from synapse.http.servlet import validate_json_object
from synapse.rest.models import RequestBodyModel
from synapse.storage.controllers.state import server_acl_evaluator_from_event
from synapse.types import EventID, JsonDict, RoomID, StrCollection, UserID


Expand Down
52 changes: 3 additions & 49 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1324,57 +1324,11 @@ async def check_server_matches_acl(self, server_name: str, room_id: str) -> None
Raises:
AuthError if the server does not match the ACL
"""
acl_event = await self._storage_controllers.state.get_current_state_event(
room_id, EventTypes.ServerACL, ""
)
if acl_event:
server_acl_evaluator = await self._get_server_acl_evaluator(
acl_event.event_id, acl_event
)
if not server_acl_evaluator.server_matches_acl_event(server_name):
server_acl_evaluator = await self._storage_controllers.state.get_server_acl_for_room(
room_id)
if server_acl_evaluator and not server_acl_evaluator.server_matches_acl_event(server_name):
raise AuthError(code=403, msg="Server is banned from room")

@cached(uncached_args=("acl_event",))
def _get_server_acl_evaluator(
self, event_id: str, acl_event: EventBase
) -> ServerAclEvaluator:
"""Create a ServerAclEvaluator from an event, but cached on the event ID."""
return server_acl_evaluator_from_event(acl_event)


def server_acl_evaluator_from_event(acl_event: EventBase) -> "ServerAclEvaluator":
"""
Create a ServerAclEvaluator from a m.room.server_acl event's content.

This does up-front parsing of the content to ignore bad data and pre-compile
regular expressions.
"""

# first of all, check if literal IPs are blocked, and if so, whether the
# server name is a literal IP
allow_ip_literals = acl_event.content.get("allow_ip_literals", True)
if not isinstance(allow_ip_literals, bool):
logger.warning("Ignoring non-bool allow_ip_literals flag")
allow_ip_literals = True

# next, check the deny list
deny = acl_event.content.get("deny", [])
if not isinstance(deny, (list, tuple)):
logger.warning("Ignoring non-list deny ACL %s", deny)
deny = []
else:
deny = [s for s in deny if isinstance(s, str)]

# then the allow list.
allow = acl_event.content.get("allow", [])
if not isinstance(allow, (list, tuple)):
logger.warning("Ignoring non-list allow ACL %s", allow)
allow = []
else:
allow = [s for s in allow if isinstance(s, str)]

return ServerAclEvaluator(allow_ip_literals, allow, deny)


class FederationHandlerRegistry:
"""Allows classes to register themselves as handlers for a given EDU or
Expand Down
58 changes: 58 additions & 0 deletions synapse/storage/controllers/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
PartialCurrentStateTracker,
PartialStateEventsTracker,
)
from synapse.synapse_rust.acl import ServerAclEvaluator
from synapse.types import MutableStateMap, StateMap, get_domain_from_id
from synapse.types.state import StateFilter
from synapse.util.async_helpers import Linearizer
Expand Down Expand Up @@ -501,6 +502,29 @@ async def get_canonical_alias_for_room(self, room_id: str) -> Optional[str]:

return event.content.get("alias")

@cached()
async def get_server_acl_for_room(self, room_id: str) -> Optional[ServerAclEvaluator]:
"""Get the server ACL evaluator for room, if any

This does up-front parsing of the content to ignore bad data and pre-compile
regular expressions.

Args:
room_id: The room ID

Returns:
The server ACL evaluator, if any
"""

acl_event = await self.get_current_state_event(
room_id, EventTypes.ServerACL, ""
)
clokep marked this conversation as resolved.
Show resolved Hide resolved

if not acl_event:
return None

return server_acl_evaluator_from_event(acl_event)

@trace
@tag_args
async def get_current_state_deltas(
Expand Down Expand Up @@ -760,3 +784,37 @@ async def _get_joined_hosts(
cache.state_group = object()

return frozenset(cache.hosts_to_joined_users)


def server_acl_evaluator_from_event(acl_event: EventBase) -> "ServerAclEvaluator":
"""
Create a ServerAclEvaluator from a m.room.server_acl event's content.

This does up-front parsing of the content to ignore bad data and pre-compile
regular expressions.
"""

# first of all, check if literal IPs are blocked, and if so, whether the
# server name is a literal IP
allow_ip_literals = acl_event.content.get("allow_ip_literals", True)
if not isinstance(allow_ip_literals, bool):
logger.warning("Ignoring non-bool allow_ip_literals flag")
allow_ip_literals = True

# next, check the deny list
deny = acl_event.content.get("deny", [])
if not isinstance(deny, (list, tuple)):
logger.warning("Ignoring non-list deny ACL %s", deny)
deny = []
else:
deny = [s for s in deny if isinstance(s, str)]

# then the allow list.
allow = acl_event.content.get("allow", [])
if not isinstance(allow, (list, tuple)):
logger.warning("Ignoring non-list allow ACL %s", allow)
allow = []
else:
allow = [s for s in allow if isinstance(s, str)]

return ServerAclEvaluator(allow_ip_literals, allow, deny)
5 changes: 5 additions & 0 deletions synapse/storage/databases/main/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ def _invalidate_caches_for_event(
"get_forgotten_rooms_for_user", (state_key,)
)

if etype == EventTypes.ServerACL:
self.hs.get_storage_controllers().state.get_server_acl_for_room.invalidate((room_id,))
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved

if relates_to:
self._attempt_to_invalidate_cache("get_relations_for_event", (relates_to,))
self._attempt_to_invalidate_cache("get_references_for_event", (relates_to,))
Expand Down Expand Up @@ -383,6 +386,8 @@ def _invalidate_caches_for_room_events(self, room_id: str) -> None:
self._attempt_to_invalidate_cache("get_thread_participated", None)
self._attempt_to_invalidate_cache("get_threads", (room_id,))

self.hs.get_storage_controllers().state.get_server_acl_for_room.invalidate((room_id,))

self._attempt_to_invalidate_cache("_get_state_group_for_event", None)

self._attempt_to_invalidate_cache("get_event_ordering", None)
Expand Down
2 changes: 1 addition & 1 deletion tests/federation/test_federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.config.server import DEFAULT_ROOM_VERSION
from synapse.events import EventBase, make_event_from_dict
from synapse.federation.federation_server import server_acl_evaluator_from_event
from synapse.rest import admin
from synapse.rest.client import login, room
from synapse.server import HomeServer
from synapse.storage.controllers.state import server_acl_evaluator_from_event
from synapse.types import JsonDict
from synapse.util import Clock

Expand Down