1818from botframework .connector import Channels , EmulatorApiClient
1919from botframework .connector .aio import ConnectorClient
2020from botframework .connector .auth import (
21+ AuthenticationConfiguration ,
2122 AuthenticationConstants ,
2223 ChannelValidation ,
24+ ChannelProvider ,
25+ ClaimsIdentity ,
2326 GovernmentChannelValidation ,
2427 GovernmentConstants ,
2528 MicrosoftAppCredentials ,
2629 JwtTokenValidation ,
2730 SimpleCredentialProvider ,
31+ SkillValidation ,
2832)
2933from botframework .connector .token_api import TokenApiClient
3034from botframework .connector .token_api .models import TokenStatus
3741USER_AGENT = f"Microsoft-BotFramework/3.1 (BotBuilder Python/{ __version__ } )"
3842OAUTH_ENDPOINT = "https://api.botframework.com"
3943US_GOV_OAUTH_ENDPOINT = "https://api.botframework.azure.us"
44+ BOT_IDENTITY_KEY = "BotIdentity"
4045
4146
4247class TokenExchangeState (Model ):
@@ -72,13 +77,17 @@ def __init__(
7277 oauth_endpoint : str = None ,
7378 open_id_metadata : str = None ,
7479 channel_service : str = None ,
80+ channel_provider : ChannelProvider = None ,
81+ auth_configuration : AuthenticationConfiguration = None ,
7582 ):
7683 self .app_id = app_id
7784 self .app_password = app_password
7885 self .channel_auth_tenant = channel_auth_tenant
7986 self .oauth_endpoint = oauth_endpoint
8087 self .open_id_metadata = open_id_metadata
8188 self .channel_service = channel_service
89+ self .channel_provider = channel_provider
90+ self .auth_configuration = auth_configuration or AuthenticationConfiguration ()
8291
8392
8493class BotFrameworkAdapter (BotAdapter , UserTokenProvider ):
@@ -90,6 +99,7 @@ def __init__(self, settings: BotFrameworkAdapterSettings):
9099 self .settings .channel_service = self .settings .channel_service or os .environ .get (
91100 AuthenticationConstants .CHANNEL_SERVICE
92101 )
102+
93103 self .settings .open_id_metadata = (
94104 self .settings .open_id_metadata
95105 or os .environ .get (AuthenticationConstants .BOT_OPEN_ID_METADATA_KEY )
@@ -163,7 +173,7 @@ async def create_conversation(
163173
164174 # Create conversation
165175 parameters = ConversationParameters (bot = reference .bot )
166- client = self .create_connector_client (reference .service_url )
176+ client = await self .create_connector_client (reference .service_url )
167177
168178 # Mix in the tenant ID if specified. This is required for MS Teams.
169179 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):
207217 activity = await self .parse_request (req )
208218 auth_header = auth_header or ""
209219
210- await self .authenticate_request (activity , auth_header )
220+ identity = await self .authenticate_request (activity , auth_header )
211221 context = self .create_context (activity )
222+ context .turn_state [BOT_IDENTITY_KEY ] = identity
212223
213224 # Fix to assign tenant_id from channelData to Conversation.tenant_id.
214225 # 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):
228239
229240 return await self .run_pipeline (context , logic )
230241
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 :
232245 """
233246 Allows for the overriding of authentication in unit tests.
234247 :param request:
@@ -240,11 +253,14 @@ async def authenticate_request(self, request: Activity, auth_header: str):
240253 auth_header ,
241254 self ._credential_provider ,
242255 self .settings .channel_service ,
256+ self .settings .auth_configuration ,
243257 )
244258
245259 if not claims .is_authenticated :
246260 raise Exception ("Unauthorized Access. Request is not authorized" )
247261
262+ return claims
263+
248264 def create_context (self , activity ):
249265 """
250266 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):
306322 :return:
307323 """
308324 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 )
310327 return await client .conversations .update_activity (
311328 activity .conversation .id , activity .id , activity
312329 )
@@ -324,7 +341,8 @@ async def delete_activity(
324341 :return:
325342 """
326343 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 )
328346 await client .conversations .delete_activity (
329347 reference .conversation .id , reference .activity_id
330348 )
@@ -365,7 +383,10 @@ async def send_activities(
365383 "BotFrameworkAdapter.send_activity(): conversation.id can not be None."
366384 )
367385
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+ )
369390 if activity .type == "trace" and activity .channel_id != "emulator" :
370391 pass
371392 elif activity .reply_to_id :
@@ -409,7 +430,8 @@ async def delete_conversation_member(
409430 )
410431 service_url = context .activity .service_url
411432 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 )
413435 return await client .conversations .delete_conversation_member (
414436 conversation_id , member_id
415437 )
@@ -446,7 +468,8 @@ async def get_activity_members(self, context: TurnContext, activity_id: str):
446468 )
447469 service_url = context .activity .service_url
448470 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 )
450473 return await client .conversations .get_activity_members (
451474 conversation_id , activity_id
452475 )
@@ -474,7 +497,8 @@ async def get_conversation_members(self, context: TurnContext):
474497 )
475498 service_url = context .activity .service_url
476499 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 )
478502 return await client .conversations .get_conversation_members (conversation_id )
479503 except Exception as error :
480504 raise error
@@ -488,7 +512,7 @@ async def get_conversations(self, service_url: str, continuation_token: str = No
488512 :param continuation_token:
489513 :return:
490514 """
491- client = self .create_connector_client (service_url )
515+ client = await self .create_connector_client (service_url )
492516 return await client .conversations .get_conversations (continuation_token )
493517
494518 async def get_user_token (
@@ -595,13 +619,44 @@ async def get_aad_tokens(
595619 user_id , connection_name , context .activity .channel_id , resource_urls
596620 )
597621
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 :
599625 """
600626 Allows for mocking of the connector client in unit tests.
601627 :param service_url:
628+ :param identity:
602629 :return:
603630 """
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 )
605660 client .config .add_user_agent (USER_AGENT )
606661 return client
607662
0 commit comments