diff --git a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py index 930f29d44..19b44f2c7 100644 --- a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py @@ -36,6 +36,7 @@ TokenStatus, TokenExchangeRequest, SignInUrlResponse, + TokenResponse as ConnectorTokenResponse, ) from botbuilder.schema import ( Activity, @@ -509,7 +510,10 @@ async def process_activity_with_identity( ) if invoke_response is None: return InvokeResponse(status=int(HTTPStatus.NOT_IMPLEMENTED)) - return invoke_response.value + return InvokeResponse( + status=invoke_response.value.status, + body=invoke_response.value.body.serialize(), + ) return None @@ -1267,8 +1271,13 @@ async def exchange_token_from_credentials( exchange_request.token, ) - if isinstance(result, TokenResponse): - return result + if isinstance(result, ConnectorTokenResponse): + return TokenResponse( + channel_id=result.channel_id, + connection_name=result.connection_name, + token=result.token, + expiration=result.expiration, + ) raise TypeError(f"exchange_async returned improper result: {type(result)}") @staticmethod diff --git a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py index fe4f55e3f..1fed80986 100644 --- a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py +++ b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py @@ -12,6 +12,7 @@ BotFrameworkAdapterSettings, TurnContext, ) +from botbuilder.core.invoke_response import InvokeResponse from botbuilder.schema import ( Activity, ActivityTypes, @@ -22,6 +23,13 @@ DeliveryModes, ExpectedReplies, CallerIdConstants, + SignInConstants, + TokenExchangeInvokeRequest, + TokenExchangeInvokeResponse, +) +from botframework.connector.token_api.models import ( + TokenExchangeRequest, + TokenResponse as ConnectorTokenResponse, ) from botframework.connector.aio import ConnectorClient from botframework.connector.auth import ( @@ -189,6 +197,31 @@ async def mock_create_conversation(parameters): return self.connector_client_mock + async def _create_token_api_client( + self, context: TurnContext, oauth_app_credentials: AppCredentials = None + ): + client = await super()._create_token_api_client(context, oauth_app_credentials) + + def mock_exchange_async( + user_id, # pylint: disable=unused-argument + connection_name, + channel_id, + uri=None, # pylint: disable=unused-argument + token=None, + custom_headers=None, # pylint: disable=unused-argument + raw=False, # pylint: disable=unused-argument + **operation_config, # pylint: disable=unused-argument + ): + return ConnectorTokenResponse( + channel_id=channel_id, + connection_name=connection_name, + token=token, + expiration=None, + ) + + client.user_token.exchange_async = mock_exchange_async + return client + async def process_activity( channel_id: str, channel_data_tenant_id: str, conversation_tenant_id: str @@ -731,3 +764,124 @@ async def callback(context: TurnContext): adapter.connector_client_mock.conversations.send_to_conversation.call_count == 3 ) + + 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 test_exchange_token_from_credentials(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", + conversation=ConversationAccount(id="conversationId"), + value=TokenExchangeInvokeRequest( + id="token_exchange_id", + token="token", + connection_name="connection_name", + ), + ) + + async def callback(context): + result = await adapter.exchange_token_from_credentials( + turn_context=context, + oauth_app_credentials=None, + connection_name=context.activity.value.connection_name, + exchange_request=TokenExchangeRequest( + token=context.activity.value.token, uri=context.activity.service_url + ), + user_id="user_id", + ) + + activity = Activity( + type=ActivityTypes.invoke_response, + value=InvokeResponse( + status=200, + body=TokenExchangeInvokeResponse( + id=context.activity.value.id, + connection_name=result.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 + )