Add policy server checks on membership events too

This commit is contained in:
Travis Ralston
2026-03-20 11:17:06 -06:00
parent 40d699b1d4
commit 12e3894a64
4 changed files with 68 additions and 10 deletions

View File

@@ -139,6 +139,9 @@ class FederationClient(FederationBase):
self.server_name = hs.hostname
self.signing_key = hs.signing_key
# Avoid a cyclic dependency between this and the room policy handler itself
self._get_room_policy_handler = hs.get_room_policy_handler
# Cache mapping `event_id` to a tuple of the event itself and the `pull_origin`
# (which server we pulled the event from)
self._get_pdu_cache: ExpiringCache[str, tuple[EventBase, str]] = ExpiringCache(
@@ -1317,6 +1320,11 @@ class FederationClient(FederationBase):
pdu = event_from_pdu_json(pdu_dict, room_version)
# Verify the policy server has allowed the event, raising a SynapseError if not.
# We use `raise_if_not_allowed` instead of asking for a signature because the
# event we received might already have a policy server signature.
await self._get_room_policy_handler().raise_if_not_allowed(pdu)
# Check signatures are correct.
try:
pdu = await self._check_sigs_and_hash(room_version, pdu)

View File

@@ -151,6 +151,7 @@ class FederationServer(FederationBase):
self._state_storage_controller = hs.get_storage_controllers().state
self.device_handler = hs.get_device_handler()
self._room_policy_handler = hs.get_room_policy_handler()
# Ensure the following handlers are loaded since they register callbacks
# with FederationHandlerRegistry.
@@ -741,6 +742,12 @@ class FederationServer(FederationBase):
pdu.event_id,
)
raise SynapseError(403, Codes.FORBIDDEN)
# Verify the policy server has allowed the event, raising a SynapseError if not.
# We use `raise_if_not_allowed` instead of asking for a signature because the
# event we received might already have a policy server signature.
await self._room_policy_handler.raise_if_not_allowed(pdu)
try:
pdu = await self._check_sigs_and_hash(room_version, pdu)
except InvalidEventSignatureError as e:
@@ -1019,6 +1026,11 @@ class FederationServer(FederationBase):
)
)
# Verify the policy server has allowed the event, raising a SynapseError if not.
# We use `raise_if_not_allowed` instead of asking for a signature because the
# event we received might already have a policy server signature.
await self._room_policy_handler.raise_if_not_allowed(event)
try:
event = await self._check_sigs_and_hash(room_version, event)
except InvalidEventSignatureError as e:

View File

@@ -107,6 +107,7 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
self.account_data_handler = hs.get_account_data_handler()
self.event_auth_handler = hs.get_event_auth_handler()
self._worker_lock_handler = hs.get_worker_locks_handler()
self._room_policy_handler = hs.get_room_policy_handler()
self._membership_types_to_include_profile_data_in = {
Membership.JOIN,
@@ -496,6 +497,16 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
outlier=outlier,
delay_id=delay_id,
)
# Before we persist the event, run the event through the policy server
# to ensure the membership action can happen. This will raise a
# SynapseError if the policy server refused it, and otherwise append a
# signature to the event.
await self._room_policy_handler.ask_policy_server_to_sign_event(
event,
verify=True,
)
context = await unpersisted_context.persist(event)
prev_state_ids = await context.get_prev_state_ids(
StateFilter.from_types([(EventTypes.Member, user_id)])

View File

@@ -151,8 +151,40 @@ class RoomPolicyHandler:
Returns:
bool: True if the event is allowed in the room, False otherwise.
"""
try:
await self.raise_if_not_allowed(event)
except Exception as ex:
# We probably caught either a refusal to sign, an invalid signature, or
# some other transient or network error. These are all rejection cases.
logger.warning("Failed to get a signature from the policy server: %s", ex)
return False
return True
async def raise_if_not_allowed(self, event: EventBase) -> None:
"""
Check if the given event is allowed in the room by the policy server, and raise
a SynapseError if not.
Note: This will *not* raise errors if the room's policy server is Synapse itself.
This is because Synapse can't be a policy server (currently).
If no policy server is configured in the room, this does not raise. Similarly, if
the policy server is invalid in any way (not joined, not a server, etc), this
does not raise.
If a valid and contactable policy server is configured in the room, this raises
a SynapseError if that server suggests the event is spammy, and returns nothing
otherwise.
Args:
event: The event to check. This should be a fully-formed PDU.
Raises:
SynapseError: If the event is spammy according to the policy server.
"""
if self._is_policy_server_state_event(event):
return True # always allow policy server change events
return # always allow policy server change events
policy_server = await self._get_policy_server(event.room_id)
if policy_server is None:
@@ -165,18 +197,13 @@ class RoomPolicyHandler:
event, policy_server.server_name, policy_server.public_key
)
if valid:
return True # valid signature == allow
return # valid signature == allow
# We couldn't save the HTTP hit, so do that hit.
try:
await self.ask_policy_server_to_sign_event(event, verify=True)
except Exception as ex:
# We probably caught either a refusal to sign, an invalid signature, or
# some other transient or network error. These are all rejection cases.
logger.warning("Failed to get a signature from the policy server: %s", ex)
return False
# This raises the SynapseError we mentioned in the docstring
await self.ask_policy_server_to_sign_event(event, verify=True)
return True # passed all verifications and checks, so allow
return # passed all verifications and checks, so allow
async def _verify_policy_server_signature(
self, event: EventBase, policy_server: str, public_key: str