Skip to content

Commit 8e46411

Browse files
[#1631][BotFrameworkAdapter] process_activity returns HTTP 412 error when exchanging a token (#1632)
* Apply fix for exchanging token * Add unit tests for the token exchange fix Co-authored-by: sw-joelmut <joel.mut@southworks.com>
1 parent fe6e900 commit 8e46411

File tree

2 files changed

+166
-3
lines changed

2 files changed

+166
-3
lines changed

libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
TokenStatus,
3737
TokenExchangeRequest,
3838
SignInUrlResponse,
39+
TokenResponse as ConnectorTokenResponse,
3940
)
4041
from botbuilder.schema import (
4142
Activity,
@@ -509,7 +510,10 @@ async def process_activity_with_identity(
509510
)
510511
if invoke_response is None:
511512
return InvokeResponse(status=int(HTTPStatus.NOT_IMPLEMENTED))
512-
return invoke_response.value
513+
return InvokeResponse(
514+
status=invoke_response.value.status,
515+
body=invoke_response.value.body.serialize(),
516+
)
513517

514518
return None
515519

@@ -1267,8 +1271,13 @@ async def exchange_token_from_credentials(
12671271
exchange_request.token,
12681272
)
12691273

1270-
if isinstance(result, TokenResponse):
1271-
return result
1274+
if isinstance(result, ConnectorTokenResponse):
1275+
return TokenResponse(
1276+
channel_id=result.channel_id,
1277+
connection_name=result.connection_name,
1278+
token=result.token,
1279+
expiration=result.expiration,
1280+
)
12721281
raise TypeError(f"exchange_async returned improper result: {type(result)}")
12731282

12741283
@staticmethod

libraries/botbuilder-core/tests/test_bot_framework_adapter.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
BotFrameworkAdapterSettings,
1313
TurnContext,
1414
)
15+
from botbuilder.core.invoke_response import InvokeResponse
1516
from botbuilder.schema import (
1617
Activity,
1718
ActivityTypes,
@@ -22,6 +23,13 @@
2223
DeliveryModes,
2324
ExpectedReplies,
2425
CallerIdConstants,
26+
SignInConstants,
27+
TokenExchangeInvokeRequest,
28+
TokenExchangeInvokeResponse,
29+
)
30+
from botframework.connector.token_api.models import (
31+
TokenExchangeRequest,
32+
TokenResponse as ConnectorTokenResponse,
2533
)
2634
from botframework.connector.aio import ConnectorClient
2735
from botframework.connector.auth import (
@@ -189,6 +197,31 @@ async def mock_create_conversation(parameters):
189197

190198
return self.connector_client_mock
191199

200+
async def _create_token_api_client(
201+
self, context: TurnContext, oauth_app_credentials: AppCredentials = None
202+
):
203+
client = await super()._create_token_api_client(context, oauth_app_credentials)
204+
205+
def mock_exchange_async(
206+
user_id, # pylint: disable=unused-argument
207+
connection_name,
208+
channel_id,
209+
uri=None, # pylint: disable=unused-argument
210+
token=None,
211+
custom_headers=None, # pylint: disable=unused-argument
212+
raw=False, # pylint: disable=unused-argument
213+
**operation_config, # pylint: disable=unused-argument
214+
):
215+
return ConnectorTokenResponse(
216+
channel_id=channel_id,
217+
connection_name=connection_name,
218+
token=token,
219+
expiration=None,
220+
)
221+
222+
client.user_token.exchange_async = mock_exchange_async
223+
return client
224+
192225

193226
async def process_activity(
194227
channel_id: str, channel_data_tenant_id: str, conversation_tenant_id: str
@@ -731,3 +764,124 @@ async def callback(context: TurnContext):
731764
adapter.connector_client_mock.conversations.send_to_conversation.call_count
732765
== 3
733766
)
767+
768+
async def test_process_activity_with_identity_token_exchange_invoke_response(self):
769+
mock_credential_provider = unittest.mock.create_autospec(CredentialProvider)
770+
771+
settings = BotFrameworkAdapterSettings(
772+
app_id="bot_id", credential_provider=mock_credential_provider,
773+
)
774+
adapter = AdapterUnderTest(settings)
775+
776+
identity = ClaimsIdentity(
777+
claims={
778+
AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
779+
AuthenticationConstants.APP_ID_CLAIM: "bot_id",
780+
AuthenticationConstants.VERSION_CLAIM: "1.0",
781+
},
782+
is_authenticated=True,
783+
)
784+
785+
inbound_activity = Activity(
786+
type=ActivityTypes.invoke,
787+
name=SignInConstants.token_exchange_operation_name,
788+
service_url="http://tempuri.org/whatever",
789+
delivery_mode=DeliveryModes.normal,
790+
conversation=ConversationAccount(id="conversationId"),
791+
value=TokenExchangeInvokeRequest(
792+
id="token_exchange_id",
793+
token="token",
794+
connection_name="connection_name",
795+
),
796+
)
797+
798+
async def callback(context: TurnContext):
799+
activity = Activity(
800+
type=ActivityTypes.invoke_response,
801+
value=InvokeResponse(
802+
status=200,
803+
body=TokenExchangeInvokeResponse(
804+
id=context.activity.value.id,
805+
connection_name=context.activity.value.connection_name,
806+
),
807+
),
808+
)
809+
810+
await context.send_activity(activity)
811+
812+
invoke_response = await adapter.process_activity_with_identity(
813+
inbound_activity, identity, callback,
814+
)
815+
816+
assert invoke_response
817+
assert invoke_response.status == 200
818+
assert invoke_response.body["id"] == inbound_activity.value.id
819+
assert (
820+
invoke_response.body["connectionName"]
821+
== inbound_activity.value.connection_name
822+
)
823+
824+
async def test_exchange_token_from_credentials(self):
825+
mock_credential_provider = unittest.mock.create_autospec(CredentialProvider)
826+
827+
settings = BotFrameworkAdapterSettings(
828+
app_id="bot_id", credential_provider=mock_credential_provider,
829+
)
830+
adapter = AdapterUnderTest(settings)
831+
832+
identity = ClaimsIdentity(
833+
claims={
834+
AuthenticationConstants.AUDIENCE_CLAIM: "bot_id",
835+
AuthenticationConstants.APP_ID_CLAIM: "bot_id",
836+
AuthenticationConstants.VERSION_CLAIM: "1.0",
837+
},
838+
is_authenticated=True,
839+
)
840+
841+
inbound_activity = Activity(
842+
type=ActivityTypes.invoke,
843+
name=SignInConstants.token_exchange_operation_name,
844+
service_url="http://tempuri.org/whatever",
845+
conversation=ConversationAccount(id="conversationId"),
846+
value=TokenExchangeInvokeRequest(
847+
id="token_exchange_id",
848+
token="token",
849+
connection_name="connection_name",
850+
),
851+
)
852+
853+
async def callback(context):
854+
result = await adapter.exchange_token_from_credentials(
855+
turn_context=context,
856+
oauth_app_credentials=None,
857+
connection_name=context.activity.value.connection_name,
858+
exchange_request=TokenExchangeRequest(
859+
token=context.activity.value.token, uri=context.activity.service_url
860+
),
861+
user_id="user_id",
862+
)
863+
864+
activity = Activity(
865+
type=ActivityTypes.invoke_response,
866+
value=InvokeResponse(
867+
status=200,
868+
body=TokenExchangeInvokeResponse(
869+
id=context.activity.value.id,
870+
connection_name=result.connection_name,
871+
),
872+
),
873+
)
874+
875+
await context.send_activity(activity)
876+
877+
invoke_response = await adapter.process_activity_with_identity(
878+
inbound_activity, identity, callback,
879+
)
880+
881+
assert invoke_response
882+
assert invoke_response.status == 200
883+
assert invoke_response.body["id"] == inbound_activity.value.id
884+
assert (
885+
invoke_response.body["connectionName"]
886+
== inbound_activity.value.connection_name
887+
)

0 commit comments

Comments
 (0)