async def _recognize_token(self, context: TurnContext) -> PromptRecognizerResult: token = None if OAuthPrompt._is_token_response_event(context): token = context.activity.value elif OAuthPrompt._is_teams_verification_invoke(context): code = context.activity.value["state"] try: token = await self.get_user_token(context, code) if token is not None: await context.send_activity( Activity(type="invokeResponse", value=InvokeResponse(200))) else: await context.send_activity( Activity(type="invokeResponse", value=InvokeResponse(404))) except Exception: context.send_activity( Activity(type="invokeResponse", value=InvokeResponse(500))) elif context.activity.type == ActivityTypes.message and context.activity.text: match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text) if match: token = await self.get_user_token(context, match[0]) return (PromptRecognizerResult(True, token) if token is not None else PromptRecognizerResult())
async def post_return(): nonlocal sequence if sequence == 0: result = InvokeResponse(body=first_response, status=HTTPStatus.OK) else: result = InvokeResponse(status=HTTPStatus.CONFLICT) sequence += 1 return result
async def post_activity( self, from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ) -> InvokeResponse: app_credentials = await self._get_app_credentials( from_bot_id, to_bot_id) if not app_credentials: raise KeyError( "Unable to get appCredentials to connect to the skill") # Get token for the skill call token = (app_credentials.get_access_token() if app_credentials.microsoft_app_id else None) # Capture current activity settings before changing them. original_conversation_id = activity.conversation.id original_service_url = activity.service_url original_relates_to = activity.relates_to original_recipient = activity.recipient try: activity.relates_to = ConversationReference( service_url=activity.service_url, activity_id=activity.id, channel_id=activity.channel_id, conversation=ConversationAccount( id=activity.conversation.id, name=activity.conversation.name, conversation_type=activity.conversation.conversation_type, aad_object_id=activity.conversation.aad_object_id, is_group=activity.conversation.is_group, role=activity.conversation.role, tenant_id=activity.conversation.tenant_id, properties=activity.conversation.properties, ), bot=None, ) activity.conversation.id = conversation_id activity.service_url = service_url if not activity.recipient: activity.recipient = ChannelAccount(role=RoleTypes.skill) else: activity.recipient.role = RoleTypes.skill status, content = await self._post_content(to_url, token, activity) return InvokeResponse(status=status, body=content) finally: # Restore activity properties. activity.conversation.id = original_conversation_id activity.service_url = original_service_url activity.relates_to = original_relates_to activity.recipient = original_recipient
async def post_activity( self, from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ) -> InvokeResponse: app_credentials = await self._get_app_credentials( from_bot_id, to_bot_id) if not app_credentials: raise KeyError( "Unable to get appCredentials to connect to the skill") # Get token for the skill call token = (app_credentials.get_access_token() if app_credentials.microsoft_app_id else None) # Capture current activity settings before changing them. # TODO: DO we need to set the activity ID? (events that are created manually don't have it). original_conversation_id = activity.conversation.id original_service_url = activity.service_url original_caller_id = activity.caller_id try: activity.conversation.id = conversation_id activity.service_url = service_url activity.caller_id = from_bot_id headers_dict = { "Content-type": "application/json; charset=utf-8", } if token: headers_dict.update({ "Authorization": f"Bearer {token}", }) json_content = json.dumps(activity.serialize()) resp = await self._session.post( to_url, data=json_content.encode("utf-8"), headers=headers_dict, ) resp.raise_for_status() data = (await resp.read()).decode() content = json.loads(data) if data else None if content: return InvokeResponse(status=resp.status_code, body=content) finally: # Restore activity properties. activity.conversation.id = original_conversation_id activity.service_url = original_service_url activity.caller_id = original_caller_id
async def _send_invoke_response( self, turn_context: TurnContext, body: object = None, http_status_code=HTTPStatus.OK, ): await turn_context.send_activity( Activity( type=ActivityTypes.invoke_response, value=InvokeResponse(status=http_status_code, body=body), ) )
def _get_token_exchange_invoke_response( self, status: int, failure_detail: str, identifier: str = None ) -> Activity: return Activity( type=ActivityTypes.invoke_response, value=InvokeResponse( status=status, body=TokenExchangeInvokeResponse( id=identifier, connection_name=self._settings.connection_name, failure_detail=failure_detail, ), ), )
async def mock_post_activity( from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ): nonlocal callback, return_status if callback: await callback( from_bot_id, to_bot_id, to_url, service_url, conversation_id, activity, ) return InvokeResponse(status=return_status)
async def process_streaming_activity( self, activity: Activity, bot_callback_handler: Callable[[TurnContext], Awaitable], ) -> InvokeResponse: if not activity: raise TypeError( f"'activity: {activity.__class__.__name__}' argument can't be None" ) """ If a conversation has moved from one connection to another for the same Channel or Skill and hasn't been forgotten by the previous StreamingRequestHandler. The last requestHandler the conversation has been associated with should always be the active connection. """ request_handler = [ handler for handler in self.request_handlers if handler.service_url == activity.service_url and handler.has_conversation(activity.conversation.id) ] request_handler = request_handler[-1] if request_handler else None context = TurnContext(self, activity) if self.claims_identity: context.turn_state[self.BOT_IDENTITY_KEY] = self.claims_identity connector_client = self._create_streaming_connector_client( activity, request_handler) context.turn_state[self.BOT_CONNECTOR_CLIENT_KEY] = connector_client await self.run_pipeline(context, bot_callback_handler) if activity.type == ActivityTypes.invoke: activity_invoke_response = context.turn_state.get( self._INVOKE_RESPONSE_KEY) if not activity_invoke_response: return InvokeResponse(status=HTTPStatus.NOT_IMPLEMENTED) return activity_invoke_response.value return None
async def mock_post_activity( from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ): nonlocal callback, return_status if callback: await callback( from_bot_id, to_bot_id, to_url, service_url, conversation_id, activity, ) if isinstance(return_status, Callable): return await return_status() return InvokeResponse(status=return_status, body=activity_list)
async def _recognize_token( self, dialog_context: DialogContext) -> PromptRecognizerResult: context = dialog_context.context token = None if OAuthPrompt._is_token_response_event(context): token = context.activity.value # fixup the turnContext's state context if this was received from a skill host caller state: CallerInfo = dialog_context.active_dialog.state[ OAuthPrompt.PERSISTED_CALLER] if state: # set the ServiceUrl to the skill host's Url dialog_context.context.activity.service_url = state.caller_service_url # recreate a ConnectorClient and set it in TurnState so replies use the correct one if not isinstance(context.adapter, ConnectorClientBuilder): raise TypeError( "OAuthPrompt: IConnectorClientProvider interface not implemented by the current adapter" ) connector_client_builder: ConnectorClientBuilder = context.adapter claims_identity = context.turn_state.get( BotAdapter.BOT_IDENTITY_KEY) connector_client = await connector_client_builder.create_connector_client( dialog_context.context.activity.service_url, claims_identity, state.scope, ) context.turn_state[ BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = connector_client elif OAuthPrompt._is_teams_verification_invoke(context): code = context.activity.value["state"] try: token = await self.get_user_token(context, code) if token is not None: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.OK)), )) else: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.NOT_FOUND)), )) except Exception: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse( int(HTTPStatus.INTERNAL_SERVER_ERROR)), )) elif self._is_token_exchange_request_invoke(context): if isinstance(context.activity.value, dict): context.activity.value = TokenExchangeInvokeRequest( ).from_dict(context.activity.value) if not (context.activity.value and self._is_token_exchange_request( context.activity.value)): # Received activity is not a token exchange request. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value." " This is required to be sent with the InvokeActivity.", )) elif (context.activity.value.connection_name != self._settings.connection_name): # Connection name on activity does not match that of setting. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a" " ConnectionName that does not match the ConnectionName expected by the bots active" " OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid" " ConnectionName in the TokenExchangeInvokeRequest", )) elif not getattr(context.adapter, "exchange_token"): # Token Exchange not supported in the adapter. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_GATEWAY), "The bot's BotAdapter does not support token exchange operations." " Ensure the bot's Adapter supports the ExtendedUserTokenProvider interface.", )) raise AttributeError( "OAuthPrompt._recognize_token(): not supported by the current adapter." ) else: # No errors. Proceed with token exchange. extended_user_token_provider: ExtendedUserTokenProvider = context.adapter token_exchange_response = None try: token_exchange_response = await extended_user_token_provider.exchange_token_from_credentials( context, self._settings.oath_app_credentials, self._settings.connection_name, context.activity.from_property.id, TokenExchangeRequest( token=context.activity.value.token), ) except: # Ignore Exceptions # If token exchange failed for any reason, tokenExchangeResponse above stays null, and # hence we send back a failure invoke response to the caller. pass if not token_exchange_response or not token_exchange_response.token: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.PRECONDITION_FAILED), "The bot is unable to exchange token. Proceed with regular login.", )) else: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.OK), None, context.activity.value.id)) token = TokenResponse( channel_id=token_exchange_response.channel_id, connection_name=token_exchange_response. connection_name, token=token_exchange_response.token, expiration=None, ) elif context.activity.type == ActivityTypes.message and context.activity.text: match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text) if match: token = await self.get_user_token(context, match[0]) return (PromptRecognizerResult(True, token) if token is not None else PromptRecognizerResult())
def create_invoke_response(self) -> InvokeResponse: return InvokeResponse(status=int(self._status_code), body=self._body)
def _create_invoke_response(body: object = None) -> InvokeResponse: return InvokeResponse(status=int(HTTPStatus.OK), body=body)
async def post_activity( self, from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, conversation_id: str, activity: Activity, ) -> InvokeResponse: app_credentials = await self._get_app_credentials( from_bot_id, to_bot_id) if not app_credentials: raise KeyError( "Unable to get appCredentials to connect to the skill") # Get token for the skill call token = (app_credentials.get_access_token() if app_credentials.microsoft_app_id else None) # Capture current activity settings before changing them. # TODO: DO we need to set the activity ID? (events that are created manually don't have it). original_conversation_id = activity.conversation.id original_service_url = activity.service_url original_caller_id = activity.caller_id original_relates_to = activity.relates_to try: # TODO: The relato has to be ported to the adapter in the new integration library when # resolving conflicts in merge activity.relates_to = ConversationReference( service_url=activity.service_url, activity_id=activity.id, channel_id=activity.channel_id, conversation=ConversationAccount( id=activity.conversation.id, name=activity.conversation.name, conversation_type=activity.conversation.conversation_type, aad_object_id=activity.conversation.aad_object_id, is_group=activity.conversation.is_group, role=activity.conversation.role, tenant_id=activity.conversation.tenant_id, properties=activity.conversation.properties, ), bot=None, ) activity.conversation.id = conversation_id activity.service_url = service_url activity.caller_id = f"urn:botframework:aadappid:{from_bot_id}" headers_dict = { "Content-type": "application/json; charset=utf-8", } if token: headers_dict.update({ "Authorization": f"Bearer {token}", }) json_content = json.dumps(activity.serialize()) resp = await self._session.post( to_url, data=json_content.encode("utf-8"), headers=headers_dict, ) resp.raise_for_status() data = (await resp.read()).decode() content = json.loads(data) if data else None return InvokeResponse(status=resp.status, body=content) finally: # Restore activity properties. activity.conversation.id = original_conversation_id activity.service_url = original_service_url activity.caller_id = original_caller_id activity.relates_to = original_relates_to
def _create_invoke_response(body: object = None) -> InvokeResponse: return InvokeResponse(status=int(HTTPStatus.OK), body=serializer_helper(body))
async def _recognize_token(self, context: TurnContext) -> PromptRecognizerResult: token = None if OAuthPrompt._is_token_response_event(context): token = context.activity.value elif OAuthPrompt._is_teams_verification_invoke(context): code = context.activity.value["state"] try: token = await self.get_user_token(context, code) if token is not None: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.OK)), )) else: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.NOT_FOUND)), )) except Exception: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse( int(HTTPStatus.INTERNAL_SERVER_ERROR)), )) elif self._is_token_exchange_request_invoke(context): if isinstance(context.activity.value, dict): context.activity.value = TokenExchangeInvokeRequest( ).from_dict(context.activity.value) if not (context.activity.value and self._is_token_exchange_request( context.activity.value)): # Received activity is not a token exchange request. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value." " This is required to be sent with the InvokeActivity.", )) elif (context.activity.value.connection_name != self._settings.connection_name): # Connection name on activity does not match that of setting. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a" " ConnectionName that does not match the ConnectionName expected by the bots active" " OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid" " ConnectionName in the TokenExchangeInvokeRequest", )) elif not getattr(context.adapter, "exchange_token"): # Token Exchange not supported in the adapter. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_GATEWAY), "The bot's BotAdapter does not support token exchange operations." " Ensure the bot's Adapter supports the ITokenExchangeProvider interface.", )) raise AttributeError( "OAuthPrompt.recognize(): not supported by the current adapter." ) else: # No errors. Proceed with token exchange. extended_user_token_provider: ExtendedUserTokenProvider = context.adapter token_exchange_response = await extended_user_token_provider.exchange_token_from_credentials( context, self._settings.oath_app_credentials, self._settings.connection_name, context.activity.from_property.id, TokenExchangeRequest(token=context.activity.value.token), ) if not token_exchange_response or not token_exchange_response.token: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.CONFLICT), "The bot is unable to exchange token. Proceed with regular login.", )) else: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.OK), None, context.activity.value.id)) token = TokenResponse( channel_id=token_exchange_response.channel_id, connection_name=token_exchange_response. connection_name, token=token_exchange_response.token, expiration=None, ) elif context.activity.type == ActivityTypes.message and context.activity.text: match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text) if match: token = await self.get_user_token(context, match[0]) return (PromptRecognizerResult(True, token) if token is not None else PromptRecognizerResult())