diff --git a/synapse/api/auth.py b/synapse/api/auth.py index e6ca9232e..46760d49e 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -244,7 +244,7 @@ class Auth: raise MissingClientTokenError() async def validate_appservice_can_control_user_id( - self, app_service: ApplicationService, user_id: str + self, app_service: ApplicationService, user_id: str, also_allow_user: Optional[str] = None ): """Validates that the app service is allowed to control the given user. @@ -252,6 +252,7 @@ class Auth: Args: app_service: The app service that controls the user user_id: The author MXID that the app service is controlling + also_allow_user: An additional user ID that the appservice can temporarily control Raises: AuthError: If the application service is not allowed to control the user @@ -263,7 +264,7 @@ class Auth: if app_service.sender == user_id: pass # Check to make sure the app service is allowed to control the user - elif not app_service.is_interested_in_user(user_id): + elif not app_service.is_interested_in_user(user_id) and user_id != also_allow_user: raise AuthError( 403, "Application service cannot masquerade as this user (%s)." % user_id, diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 7b0381c06..658f5c38e 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -30,6 +30,8 @@ class ExperimentalConfig(Config): # MSC2716 (backfill existing history) self.msc2716_enabled: bool = experimental.get("msc2716_enabled", False) + self.msc2716_also_allow_user: bool = experimental.get("com.beeper.msc2716_also_allow_user", False) + # MSC2285 (hidden read receipts) self.msc2285_enabled: bool = experimental.get("msc2285_enabled", False) diff --git a/synapse/handlers/room_batch.py b/synapse/handlers/room_batch.py index 51dd4e755..4f71c61d2 100644 --- a/synapse/handlers/room_batch.py +++ b/synapse/handlers/room_batch.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, List, Tuple +from typing import TYPE_CHECKING, List, Tuple, Optional from synapse.api.constants import EventContentFields, EventTypes from synapse.appservice import ApplicationService @@ -103,7 +103,7 @@ class RoomBatchHandler: return insertion_event async def create_requester_for_user_id_from_app_service( - self, user_id: str, app_service: ApplicationService + self, user_id: str, app_service: ApplicationService, also_allow_user: Optional[str] = None, ) -> Requester: """Creates a new requester for the given user_id and validates that the app service is allowed to control @@ -112,12 +112,13 @@ class RoomBatchHandler: Args: user_id: The author MXID that the app service is controlling app_service: The app service that controls the user + also_allow_user: An additional user ID that the appservice can temporarily control Returns: Requester object """ - await self.auth.validate_appservice_can_control_user_id(app_service, user_id) + await self.auth.validate_appservice_can_control_user_id(app_service, user_id, also_allow_user) return create_requester(user_id, app_service=app_service) @@ -155,6 +156,7 @@ class RoomBatchHandler: room_id: str, initial_auth_event_ids: List[str], app_service_requester: Requester, + also_allow_user: Optional[str], ) -> List[str]: """Takes all `state_events_at_start` event dictionaries and creates/persists them as floating state events which don't resolve into the current room state. @@ -169,6 +171,7 @@ class RoomBatchHandler: added to the list of auth events for the next state event created. app_service_requester: The requester of an application service. + also_allow_user: An additional user ID that the appservice can temporarily control Returns: List of state event ID's we just persisted @@ -209,7 +212,8 @@ class RoomBatchHandler: membership = event_dict["content"].get("membership", None) event_id, _ = await self.room_member_handler.update_membership( await self.create_requester_for_user_id_from_app_service( - state_event["sender"], app_service_requester.app_service + state_event["sender"], app_service_requester.app_service, + also_allow_user, ), target=UserID.from_string(event_dict["state_key"]), room_id=room_id, @@ -231,7 +235,8 @@ class RoomBatchHandler: _, ) = await self.event_creation_handler.create_and_send_nonmember_event( await self.create_requester_for_user_id_from_app_service( - state_event["sender"], app_service_requester.app_service + state_event["sender"], app_service_requester.app_service, + also_allow_user, ), event_dict, outlier=True, @@ -256,6 +261,7 @@ class RoomBatchHandler: inherited_depth: int, auth_event_ids: List[str], app_service_requester: Requester, + also_allow_user: Optional[str], ) -> List[str]: """Create and persists all events provided sequentially. Handles the complexity of creating events in chronological order so they can @@ -276,6 +282,7 @@ class RoomBatchHandler: auth_event_ids: Define which events allow you to create the given event in the room. app_service_requester: The requester of an application service. + also_allow_user: An additional user ID that the appservice can temporarily control Returns: List of persisted event IDs @@ -303,7 +310,7 @@ class RoomBatchHandler: event, context = await self.event_creation_handler.create_event( await self.create_requester_for_user_id_from_app_service( - ev["sender"], app_service_requester.app_service + ev["sender"], app_service_requester.app_service, also_allow_user, ), event_dict, prev_event_ids=event_dict.get("prev_events"), @@ -335,7 +342,7 @@ class RoomBatchHandler: for (event, context) in reversed(events_to_persist): await self.event_creation_handler.handle_new_client_event( await self.create_requester_for_user_id_from_app_service( - event["sender"], app_service_requester.app_service + event["sender"], app_service_requester.app_service, also_allow_user, ), event=event, context=context, @@ -352,6 +359,7 @@ class RoomBatchHandler: inherited_depth: int, auth_event_ids: List[str], app_service_requester: Requester, + also_allow_user: Optional[str], ) -> Tuple[List[str], str]: """ Handles creating and persisting all of the historical events as well @@ -371,6 +379,7 @@ class RoomBatchHandler: auth_event_ids: Define which events allow you to create the given event in the room. app_service_requester: The requester of an application service. + also_allow_user: An additional user ID that the appservice can temporarily control Returns: Tuple containing a list of created events and the next_batch_id @@ -418,6 +427,7 @@ class RoomBatchHandler: inherited_depth=inherited_depth, auth_event_ids=auth_event_ids, app_service_requester=app_service_requester, + also_allow_user=also_allow_user, ) return event_ids, next_batch_id diff --git a/synapse/rest/client/room_batch.py b/synapse/rest/client/room_batch.py index 38ad4c244..5cb66400c 100644 --- a/synapse/rest/client/room_batch.py +++ b/synapse/rest/client/room_batch.py @@ -81,6 +81,7 @@ class RoomBatchSendEventRestServlet(RestServlet): self.auth = hs.get_auth() self.room_batch_handler = hs.get_room_batch_handler() self.txns = HttpTransactionCache(hs) + self.enable_also_allow_user = hs.config.experimental.msc2716_also_allow_user async def on_POST( self, request: SynapseRequest, room_id: str @@ -101,6 +102,8 @@ class RoomBatchSendEventRestServlet(RestServlet): request.args, "prev_event_id" ) batch_id_from_query = parse_string(request, "batch_id") + also_allow_from_query = (parse_string(request, "com.beeper.also_allow_user") + if self.enable_also_allow_user else None) if prev_event_ids_from_query is None: raise SynapseError( @@ -141,6 +144,7 @@ class RoomBatchSendEventRestServlet(RestServlet): room_id=room_id, initial_auth_event_ids=auth_event_ids, app_service_requester=requester, + also_allow_user=also_allow_from_query, ) ) # Update our ongoing auth event ID list with all of the new state we @@ -213,6 +217,7 @@ class RoomBatchSendEventRestServlet(RestServlet): inherited_depth=inherited_depth, auth_event_ids=auth_event_ids, app_service_requester=requester, + also_allow_user=also_allow_from_query, ) insertion_event_id = event_ids[0]