diff --git a/synapse/federation/federation_client.py b/synapse/federation/federation_client.py index 55151ca549..090652961d 100644 --- a/synapse/federation/federation_client.py +++ b/synapse/federation/federation_client.py @@ -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) diff --git a/synapse/federation/federation_server.py b/synapse/federation/federation_server.py index eff6d63789..1bd3b890c7 100644 --- a/synapse/federation/federation_server.py +++ b/synapse/federation/federation_server.py @@ -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: diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py index b2e678e90e..ae6f4d79e5 100644 --- a/synapse/handlers/room_member.py +++ b/synapse/handlers/room_member.py @@ -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)]) diff --git a/synapse/handlers/room_policy.py b/synapse/handlers/room_policy.py index 57163c4344..998a4a24cd 100644 --- a/synapse/handlers/room_policy.py +++ b/synapse/handlers/room_policy.py @@ -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