async def _do_appservice_login(self, login_submission: JsonDict, appservice: ApplicationService): identifier = login_submission.get("identifier") logger.info("Got appservice login request with identifier: %r", identifier) if not isinstance(identifier, dict): raise SynapseError(400, "Invalid identifier in login submission", Codes.INVALID_PARAM) # this login flow only supports identifiers of type "m.id.user". if identifier.get("type") != "m.id.user": raise SynapseError(400, "Unknown login identifier type", Codes.INVALID_PARAM) user = identifier.get("user") if not isinstance(user, str): raise SynapseError(400, "Invalid user in identifier", Codes.INVALID_PARAM) if user.startswith("@"): qualified_user_id = user else: qualified_user_id = UserID(user, self.hs.hostname).to_string() if not appservice.is_interested_in_user(qualified_user_id): raise LoginError(403, "Invalid access_token", errcode=Codes.FORBIDDEN) return await self._complete_login( qualified_user_id, login_submission, ratelimit=appservice.is_rate_limited())
async def validate_appservice_can_control_user_id( self, app_service: ApplicationService, user_id: str): """Validates that the app service is allowed to control the given user. Args: app_service: The app service that controls the user user_id: The author MXID that the app service is controlling Raises: AuthError: If the application service is not allowed to control the user (user namespace regex does not match, wrong homeserver, etc) or if the user has not been registered yet. """ # It's ok if the app service is trying to use the sender from their registration 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): raise AuthError( 403, "Application service cannot masquerade as this user (%s)." % user_id, ) # Check to make sure the user is already registered on the homeserver elif not (await self.store.get_user_by_id(user_id)): raise AuthError( 403, "Application service has not registered this user (%s)" % user_id)
async def _do_appservice_login(self, login_submission: JsonDict, appservice: ApplicationService): logger.info( "Got appservice login request with identifier: %r", login_submission.get("identifier"), ) identifier = convert_client_dict_legacy_fields_to_identifier( login_submission) qualified_user_id = self._get_qualified_user_id(identifier) if not appservice.is_interested_in_user(qualified_user_id): raise LoginError(403, "Invalid access_token", errcode=Codes.FORBIDDEN) return await self._complete_login(qualified_user_id, login_submission)
async def _is_appservice_interested_in_device_lists_of_user( self, appservice: ApplicationService, user_id: str, ) -> bool: """ Returns whether a given application service is interested in the device list updates of a given user. The application service is interested in the user's device list updates if any of the following are true: * The user is the appservice's sender localpart user. * The user is in the appservice's user namespace. * At least one member of one room that the user is a part of is in the appservice's user namespace. * The appservice is explicitly (via room ID or alias) interested in at least one room that the user is in. Args: appservice: The application service to gauge interest of. user_id: The ID of the user whose device list interest is in question. Returns: True if the application service is interested in the user's device lists, False otherwise. """ # This method checks against both the sender localpart user as well as if the # user is in the appservice's user namespace. if appservice.is_interested_in_user(user_id): return True # Determine whether any of the rooms the user is in justifies sending this # device list update to the application service. room_ids = await self.store.get_rooms_for_user(user_id) for room_id in room_ids: # This method covers checking room members for appservice interest as well as # room ID and alias checks. if await appservice.is_interested_in_room(room_id, self.store): return True return False
async def _get_to_device_messages( self, service: ApplicationService, new_token: int, users: Collection[Union[str, UserID]], ) -> List[JsonDict]: """ Given an application service, determine which events it should receive from those between the last-recorded to-device message stream token for this appservice and the given stream token. Args: service: The application service to check for which events it should receive. new_token: The latest to-device event stream token. users: The users to be notified for the new to-device messages (ie, the recipients of the messages). Returns: A list of JSON dictionaries containing data derived from the to-device events that should be sent to the given application service. """ # Get the stream token that this application service has processed up until from_key = await self.store.get_type_stream_id_for_appservice( service, "to_device") # Filter out users that this appservice is not interested in users_appservice_is_interested_in: List[str] = [] for user in users: # FIXME: We should do this farther up the call stack. We currently repeat # this operation in _handle_presence. if isinstance(user, UserID): user = user.to_string() if service.is_interested_in_user(user): users_appservice_is_interested_in.append(user) if not users_appservice_is_interested_in: # Return early if the AS was not interested in any of these users return [] # Retrieve the to-device messages for each user recipient_device_to_messages = await self.store.get_messages_for_user_devices( users_appservice_is_interested_in, from_key, new_token, ) # According to MSC2409, we'll need to add 'to_user_id' and 'to_device_id' fields # to the event JSON so that the application service will know which user/device # combination this messages was intended for. # # So we mangle this dict into a flat list of to-device messages with the relevant # user ID and device ID embedded inside each message dict. message_payload: List[JsonDict] = [] for ( user_id, device_id, ), messages in recipient_device_to_messages.items(): for message_json in messages: # Remove 'message_id' from the to-device message, as it's an internal ID message_json.pop("message_id", None) message_payload.append({ "to_user_id": user_id, "to_device_id": device_id, **message_json, }) return message_payload