async def _send_token_exchange_invoke_to_skill( self, incoming_activity: Activity, request_id: str, connection_name: str, token: str, ): activity = incoming_activity.create_reply() activity.type = ActivityTypes.invoke activity.name = SignInConstants.token_exchange_operation_name activity.value = TokenExchangeInvokeRequest( id=request_id, token=token, connection_name=connection_name, ) # route the activity to the skill skill_info = self.dialog_options.skill response = await self.dialog_options.skill_client.post_activity( self.dialog_options.bot_id, skill_info.app_id, skill_info.skill_endpoint, self.dialog_options.skill_host_endpoint, incoming_activity.conversation.id, activity, ) # Check response status: true if success, false if failure return response.is_successful_status_code()
async def _send_token_exchange_invoke_to_skill( self, turn_context: TurnContext, incoming_activity: Activity, identifier: str, token: str, ) -> bool: activity = self._create_reply(incoming_activity) activity.type = ActivityTypes.invoke activity.name = "signin/tokenExchange" activity.value = TokenExchangeInvokeRequest( id=identifier, token=token, ) # route the activity to the skill response = await self._client.post_activity( self._from_bot_id, self._to_bot_id, "http://localhost:3979/api/messages", "http://tempuri.org/whatever", incoming_activity.conversation.id, activity, ) # Check response status: true if success, false if failure is_success = int(HTTPStatus.OK) <= response.status <= 299 message = ("Skill token exchange successful" if is_success else "Skill token exchange failed") await turn_context.send_activity(MessageFactory.text(message)) return is_success
async def _send_token_exchange_invoke_to_skill( self, incoming_activity: Activity, resource_id: str, token: str, connection_name: str, target_skill: BotFrameworkSkill, ) -> bool: activity = incoming_activity.create_reply() activity.type = ActivityTypes.invoke activity.name = SignInConstants.token_exchange_operation_name activity.value = TokenExchangeInvokeRequest( id=resource_id, token=token, connection_name=connection_name ) skill_conversation_reference = await self._conversation_id_factory.get_conversation_reference( incoming_activity.conversation.id ) activity.conversation = ( skill_conversation_reference.conversation_reference.conversation ) activity.service_url = ( skill_conversation_reference.conversation_reference.service_url ) # Route the activity to the skill response = await self._skill_client.post_activity_to_skill( from_bot_id=self._bot_id, to_skill=target_skill, service_url=self._skills_config.SKILL_HOST_ENDPOINT, activity=activity, ) return 200 <= response.status <= 299
async def test_process_activity_with_identity_token_exchange_invoke_response( self): mock_credential_provider = unittest.mock.create_autospec( CredentialProvider) settings = BotFrameworkAdapterSettings( app_id="bot_id", credential_provider=mock_credential_provider, ) adapter = AdapterUnderTest(settings) identity = ClaimsIdentity( claims={ AuthenticationConstants.AUDIENCE_CLAIM: "bot_id", AuthenticationConstants.APP_ID_CLAIM: "bot_id", AuthenticationConstants.VERSION_CLAIM: "1.0", }, is_authenticated=True, ) inbound_activity = Activity( type=ActivityTypes.invoke, name=SignInConstants.token_exchange_operation_name, service_url="http://tempuri.org/whatever", delivery_mode=DeliveryModes.normal, conversation=ConversationAccount(id="conversationId"), value=TokenExchangeInvokeRequest( id="token_exchange_id", token="token", connection_name="connection_name", ), ) async def callback(context: TurnContext): activity = Activity( type=ActivityTypes.invoke_response, value=InvokeResponse( status=200, body=TokenExchangeInvokeResponse( id=context.activity.value.id, connection_name=context.activity.value.connection_name, ), ), ) await context.send_activity(activity) invoke_response = await adapter.process_activity_with_identity( inbound_activity, identity, callback, ) assert invoke_response assert invoke_response.status == 200 assert invoke_response.body["id"] == inbound_activity.value.id assert (invoke_response.body["connectionName"] == inbound_activity.value.connection_name)
async def _exchanged_token(self, turn_context: TurnContext) -> bool: token_exchange_response: TokenResponse = None aux_dict = {} if turn_context.activity.value: for prop in ["id", "connection_name", "token", "properties"]: aux_dict[prop] = turn_context.activity.value.get(prop) token_exchange_request = TokenExchangeInvokeRequest( id=aux_dict["id"], connection_name=aux_dict["connection_name"], token=aux_dict["token"], properties=aux_dict["properties"], ) try: adapter = turn_context.adapter if isinstance(turn_context.adapter, ExtendedUserTokenProvider): token_exchange_response = await adapter.exchange_token( turn_context, self._oauth_connection_name, turn_context.activity.from_property.id, TokenExchangeRequest(token=token_exchange_request.token), ) else: raise Exception( "Not supported: Token Exchange is not supported by the current adapter." ) except: traceback.print_exc() if not token_exchange_response or not token_exchange_response.token: # The token could not be exchanged (which could be due to a consent requirement) # Notify the sender that PreconditionFailed so they can respond accordingly. invoke_response = TokenExchangeInvokeResponse( id=token_exchange_request.id, connection_name=self._oauth_connection_name, failure_detail="The bot is unable to exchange token. Proceed with regular login.", ) await self._send_invoke_response( turn_context, invoke_response, HTTPStatus.PRECONDITION_FAILED ) return False return True
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())
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())