12
12
ConversationAccount ,
13
13
ConversationParameters ,
14
14
ConversationReference ,
15
- ResourceResponse ,
16
15
TokenResponse ,
16
+ ResourceResponse ,
17
17
)
18
18
from botframework .connector import Channels , EmulatorApiClient
19
19
from botframework .connector .aio import ConnectorClient
20
20
from botframework .connector .auth import (
21
+ AuthenticationConfiguration ,
21
22
AuthenticationConstants ,
22
23
ChannelValidation ,
24
+ ChannelProvider ,
25
+ ClaimsIdentity ,
23
26
GovernmentChannelValidation ,
24
27
GovernmentConstants ,
25
28
MicrosoftAppCredentials ,
26
29
JwtTokenValidation ,
27
30
SimpleCredentialProvider ,
31
+ SkillValidation ,
28
32
)
29
33
from botframework .connector .token_api import TokenApiClient
30
34
from botframework .connector .token_api .models import TokenStatus
37
41
USER_AGENT = f"Microsoft-BotFramework/3.1 (BotBuilder Python/{ __version__ } )"
38
42
OAUTH_ENDPOINT = "https://api.botframework.com"
39
43
US_GOV_OAUTH_ENDPOINT = "https://api.botframework.azure.us"
44
+ BOT_IDENTITY_KEY = "BotIdentity"
40
45
41
46
42
47
class TokenExchangeState (Model ):
@@ -72,13 +77,17 @@ def __init__(
72
77
oauth_endpoint : str = None ,
73
78
open_id_metadata : str = None ,
74
79
channel_service : str = None ,
80
+ channel_provider : ChannelProvider = None ,
81
+ auth_configuration : AuthenticationConfiguration = None ,
75
82
):
76
83
self .app_id = app_id
77
84
self .app_password = app_password
78
85
self .channel_auth_tenant = channel_auth_tenant
79
86
self .oauth_endpoint = oauth_endpoint
80
87
self .open_id_metadata = open_id_metadata
81
88
self .channel_service = channel_service
89
+ self .channel_provider = channel_provider
90
+ self .auth_configuration = auth_configuration or AuthenticationConfiguration ()
82
91
83
92
84
93
class BotFrameworkAdapter (BotAdapter , UserTokenProvider ):
@@ -90,6 +99,7 @@ def __init__(self, settings: BotFrameworkAdapterSettings):
90
99
self .settings .channel_service = self .settings .channel_service or os .environ .get (
91
100
AuthenticationConstants .CHANNEL_SERVICE
92
101
)
102
+
93
103
self .settings .open_id_metadata = (
94
104
self .settings .open_id_metadata
95
105
or os .environ .get (AuthenticationConstants .BOT_OPEN_ID_METADATA_KEY )
@@ -163,7 +173,7 @@ async def create_conversation(
163
173
164
174
# Create conversation
165
175
parameters = ConversationParameters (bot = reference .bot )
166
- client = self .create_connector_client (reference .service_url )
176
+ client = await self .create_connector_client (reference .service_url )
167
177
168
178
# Mix in the tenant ID if specified. This is required for MS Teams.
169
179
if reference .conversation is not None and reference .conversation .tenant_id :
@@ -207,8 +217,9 @@ async def process_activity(self, req, auth_header: str, logic: Callable):
207
217
activity = await self .parse_request (req )
208
218
auth_header = auth_header or ""
209
219
210
- await self .authenticate_request (activity , auth_header )
220
+ identity = await self .authenticate_request (activity , auth_header )
211
221
context = self .create_context (activity )
222
+ context .turn_state [BOT_IDENTITY_KEY ] = identity
212
223
213
224
# Fix to assign tenant_id from channelData to Conversation.tenant_id.
214
225
# MS Teams currently sends the tenant ID in channelData and the correct behavior is to expose
@@ -228,7 +239,9 @@ async def process_activity(self, req, auth_header: str, logic: Callable):
228
239
229
240
return await self .run_pipeline (context , logic )
230
241
231
- async def authenticate_request (self , request : Activity , auth_header : str ):
242
+ async def authenticate_request (
243
+ self , request : Activity , auth_header : str
244
+ ) -> ClaimsIdentity :
232
245
"""
233
246
Allows for the overriding of authentication in unit tests.
234
247
:param request:
@@ -240,11 +253,14 @@ async def authenticate_request(self, request: Activity, auth_header: str):
240
253
auth_header ,
241
254
self ._credential_provider ,
242
255
self .settings .channel_service ,
256
+ self .settings .auth_configuration ,
243
257
)
244
258
245
259
if not claims .is_authenticated :
246
260
raise Exception ("Unauthorized Access. Request is not authorized" )
247
261
262
+ return claims
263
+
248
264
def create_context (self , activity ):
249
265
"""
250
266
Allows for the overriding of the context object in unit tests and derived adapters.
@@ -306,7 +322,8 @@ async def update_activity(self, context: TurnContext, activity: Activity):
306
322
:return:
307
323
"""
308
324
try :
309
- client = self .create_connector_client (activity .service_url )
325
+ identity : ClaimsIdentity = context .turn_state .get (BOT_IDENTITY_KEY )
326
+ client = await self .create_connector_client (activity .service_url , identity )
310
327
return await client .conversations .update_activity (
311
328
activity .conversation .id , activity .id , activity
312
329
)
@@ -324,7 +341,8 @@ async def delete_activity(
324
341
:return:
325
342
"""
326
343
try :
327
- client = self .create_connector_client (reference .service_url )
344
+ identity : ClaimsIdentity = context .turn_state .get (BOT_IDENTITY_KEY )
345
+ client = await self .create_connector_client (reference .service_url , identity )
328
346
await client .conversations .delete_activity (
329
347
reference .conversation .id , reference .activity_id
330
348
)
@@ -365,7 +383,10 @@ async def send_activities(
365
383
"BotFrameworkAdapter.send_activity(): conversation.id can not be None."
366
384
)
367
385
368
- client = self .create_connector_client (activity .service_url )
386
+ identity : ClaimsIdentity = context .turn_state .get (BOT_IDENTITY_KEY )
387
+ client = await self .create_connector_client (
388
+ activity .service_url , identity
389
+ )
369
390
if activity .type == "trace" and activity .channel_id != "emulator" :
370
391
pass
371
392
elif activity .reply_to_id :
@@ -409,7 +430,8 @@ async def delete_conversation_member(
409
430
)
410
431
service_url = context .activity .service_url
411
432
conversation_id = context .activity .conversation .id
412
- client = self .create_connector_client (service_url )
433
+ identity : ClaimsIdentity = context .turn_state .get (BOT_IDENTITY_KEY )
434
+ client = await self .create_connector_client (service_url , identity )
413
435
return await client .conversations .delete_conversation_member (
414
436
conversation_id , member_id
415
437
)
@@ -446,7 +468,8 @@ async def get_activity_members(self, context: TurnContext, activity_id: str):
446
468
)
447
469
service_url = context .activity .service_url
448
470
conversation_id = context .activity .conversation .id
449
- client = self .create_connector_client (service_url )
471
+ identity : ClaimsIdentity = context .turn_state .get (BOT_IDENTITY_KEY )
472
+ client = await self .create_connector_client (service_url , identity )
450
473
return await client .conversations .get_activity_members (
451
474
conversation_id , activity_id
452
475
)
@@ -474,7 +497,8 @@ async def get_conversation_members(self, context: TurnContext):
474
497
)
475
498
service_url = context .activity .service_url
476
499
conversation_id = context .activity .conversation .id
477
- client = self .create_connector_client (service_url )
500
+ identity : ClaimsIdentity = context .turn_state .get (BOT_IDENTITY_KEY )
501
+ client = await self .create_connector_client (service_url , identity )
478
502
return await client .conversations .get_conversation_members (conversation_id )
479
503
except Exception as error :
480
504
raise error
@@ -488,7 +512,7 @@ async def get_conversations(self, service_url: str, continuation_token: str = No
488
512
:param continuation_token:
489
513
:return:
490
514
"""
491
- client = self .create_connector_client (service_url )
515
+ client = await self .create_connector_client (service_url )
492
516
return await client .conversations .get_conversations (continuation_token )
493
517
494
518
async def get_user_token (
@@ -595,13 +619,44 @@ async def get_aad_tokens(
595
619
user_id , connection_name , context .activity .channel_id , resource_urls
596
620
)
597
621
598
- def create_connector_client (self , service_url : str ) -> ConnectorClient :
622
+ async def create_connector_client (
623
+ self , service_url : str , identity : ClaimsIdentity = None
624
+ ) -> ConnectorClient :
599
625
"""
600
626
Allows for mocking of the connector client in unit tests.
601
627
:param service_url:
628
+ :param identity:
602
629
:return:
603
630
"""
604
- client = ConnectorClient (self ._credentials , base_url = service_url )
631
+ if identity :
632
+ bot_app_id_claim = identity .claims .get (
633
+ AuthenticationConstants .AUDIENCE_CLAIM
634
+ ) or identity .claims .get (AuthenticationConstants .APP_ID_CLAIM )
635
+
636
+ credentials = None
637
+ if bot_app_id_claim and SkillValidation .is_skill_claim (identity .claims ):
638
+ scope = JwtTokenValidation .get_app_id_from_claims (identity .claims )
639
+
640
+ password = await self ._credential_provider .get_app_password (
641
+ bot_app_id_claim
642
+ )
643
+ credentials = MicrosoftAppCredentials (
644
+ bot_app_id_claim , password , oauth_scope = scope
645
+ )
646
+ if (
647
+ self .settings .channel_provider
648
+ and self .settings .channel_provider .is_government ()
649
+ ):
650
+ credentials .oauth_endpoint = (
651
+ GovernmentConstants .TO_CHANNEL_FROM_BOT_LOGIN_URL
652
+ )
653
+ credentials .oauth_scope = (
654
+ GovernmentConstants .TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
655
+ )
656
+ else :
657
+ credentials = self ._credentials
658
+
659
+ client = ConnectorClient (credentials , base_url = service_url )
605
660
client .config .add_user_agent (USER_AGENT )
606
661
return client
607
662
0 commit comments