8000 Structural update · snifhex/botbuilder-python@55bd161 · GitHub
[go: up one dir, main page]

Skip to content

Commit 55bd161

Browse files
committed
Structural update
1 parent 4d969c3 commit 55bd161

File tree

8 files changed

+187
-19
lines changed

8 files changed

+187
-19
lines changed

libraries/botbuilder-skills/botbuilder/skills/bot_framework_skill.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ class BotFrameworkSkill:
1010
# pylint: disable=invalid-name
1111
def __init__(self, id: str = None, app_id: str = None, skill_endpoint: str = None):
1212
self.id = id
13-
self.app_aid = app_id
13+
self.app_id = app_id
1414
self.skill_endpoint = skill_endpoint

libraries/botbuilder-skills/botbuilder/skills/skill_client.py renamed to libraries/botbuilder-skills/botbuilder/skills/bot_framework_skill_client.py

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
from abc import ABC
5-
from typing import List
4+
import base64
5+
from copy import deepcopy
6+
import json
7+
import requests
8+
from typing import Dict, List
69

7-
from botbuilder.core import Bot, BotAdapter
10+
from botbuilder.core import Bot, BotAdapter, InvokeResponse
811
from botbuilder.schema import (
912
Activity,
1013
AttachmentData,
@@ -18,33 +21,97 @@
1821
RoleTypes,
1922
Transcript,
2023
)
21-
from botframework.connector.auth import ClaimsIdentity
24+
from botframework.connector.auth import (
25+
AuthenticationConfiguration,
26+
ChannelProvider,
27+
ClaimsIdentity,
28+
CredentialProvider,
29+
GovernmentConstants,
30+
MicrosoftAppCredentials,
31+
)
2232

33+
from .bot_framework_skill import BotFrameworkSkill
2334
from .channel_api_args import ChannelApiArgs
2435
from .channel_api_methods import ChannelApiMethods
2536
from .channel_api_middleware import ChannelApiMiddleware
2637
from .skill_conversation import SkillConversation
2738

2839

29-
class SkillClient(ABC):
40+
class BotFrameworkSkillClient:
3041

3142
"""
3243
A skill host adapter implements API to forward activity to a skill and
3344
implements routing ChannelAPI calls from the Skill up through the bot/adapter.
3445
"""
3546

3647
INVOKE_ACTIVITY_NAME = "SkillEvents.ChannelApiInvoke"
48+
_BOT_IDENTITY_KEY = "BotIdentity"
3749

38-
def __init__(self, adapter: BotAdapter, logger: object = None):
39-
40-
self._bot_adapter = adapter
50+
def __init__(
51+
self,
52+
credential_provider: CredentialProvider,
53+
auth_config: AuthenticationConfiguration,
54+
channel_provider: ChannelProvider = None,
55+
logger: object = None,
56+
):
57+
if not credential_provider:
58+
raise TypeError("credential_provider can't be None")
59+
60+
if not auth_config:
61+
raise TypeError("auth_config can't be None")
62+
self._credential_provider = credential_provider
63+
self._auth_config = auth_config
64< 9E7A code class="diff-text syntax-highlighted-line addition">+
self._channel_provider = channel_provider
4165
self._logger = logger
4266

43-
if not any(
44-
isinstance(middleware, ChannelApiMiddleware)
45-
for middleware in adapter.middleware_set
46-
):
47-
adapter.middleware_set.use(ChannelApiMiddleware(self))
67+
self._app_credentials_cache: Dict[str:MicrosoftAppCredentials] = {}
68+
69+
async def forward_activity(
70+
self,
71+
bot_id: str,
72+
skill: BotFrameworkSkill,
73+
skill_host_endpoint: str,
74+
activity: Activity,
75+
) -> InvokeResponse:
76+
app_credentials = await self._get_app_credentials(bot_id, skill.app_id)
77+
78+
if not app_credentials:
79+
raise RuntimeError("Unable to get appCredentials to connect to the skill")
80+
81+
# Get token for the skill call
82+
token = app_credentials.get_access_token()
83+
activity_clone = deepcopy(activity)
84+
85+
# TODO use SkillConversation class here instead of hard coded encoding...
86+
# Encode original bot service URL and ConversationId in the new conversation ID so we can unpack it later.
87+
# var skillConversation = new SkillConversation()
88+
# { ServiceUrl = activity.ServiceUrl, ConversationId = activity.Conversation.Id };
89+
# activity.Conversation.Id = skillConversation.GetSkillConversationId()
90+
json_str = json.dumps(
91+
[activity_clone.conversation.conversation_id, activity_clone.service_url]
92+
)
93+
activity_clone.conversation.id = str(
94+
base64.b64encode(json_str.encode("utf-8")), "utf-8"
95+
)
96+
activity_clone.service_url = skill_host_endpoint
97+
activity_clone.recipient.properties["skillId"] = skill.id
98+
json_content = json.dumps(activity_clone.as_dict())
99+
with requests.Session() as session:
100+
resp = session.post(
101+
skill.skill_endpoint,
102+
data=json_content.encode("utf-8"),
103+
headers={
104+
"Authorization": f"Bearer:{token}",
105+
"Content-type": "application/json; charset=utf-8",
106+
},
107+
)
108+
resp.raise_for_status()
109+
content = resp.json
110+
111+
if content:
112+
return InvokeResponse(status=resp.status_code, body=content)
113+
114+
return None
48115

49116
async def get_conversations(
50117
self,
@@ -454,20 +521,28 @@ async def upload_attachment(
454521

455522
async def _invoke_channel_api(
456523
self,
524+
adapter: BotAdapter,
457525
bot: Bot,
458526
claims_identity: ClaimsIdentity,
459527
method: str,
460528
conversation_id: str,
461529
*args,
462530
) -> object:
531+
532+
if not any(
533+
isinstance(middleware, ChannelApiMiddleware)
534+
for middleware in adapter.middleware_set
535+
):
536+
adapter.middleware_set.use(ChannelApiMiddleware(self))
537+
463538
if self._logger:
464539
self._logger.log(f'InvokeChannelApiAsync(). Invoking method "{method}"')
465540

466541
skill_conversation = SkillConversation(conversation_id)
467542

468543
# TODO: Extension for create_invoke_activity
469544
channel_api_invoke_activity: Activity = Activity.create_invoke_activity()
470-
channel_api_invoke_activity.name = SkillClient.INVOKE_ACTIVITY_NAME
545+
channel_api_invoke_activity.name = BotFrameworkSkillClient.INVOKE_ACTIVITY_NAME
471546
channel_api_invoke_activity.channel_id = "unknown"
472547
channel_api_invoke_activity.service_url = skill_conversation.service_url
473548
channel_api_invoke_activity.conversation = ConversationAccount(
@@ -507,7 +582,7 @@ async def _invoke_channel_api(
507582
channel_api_invoke_activity.value = channel_api_args
508583

509584
# send up to the bot to process it...
510-
await self._bot_adapter.process_activity_with_claims(
585+
await adapter.process_activity_with_claims(
511586
claims_identity, channel_api_invoke_activity, bot.on_turn
512587
)
513588

@@ -516,3 +591,30 @@ async def _invoke_channel_api(
516591

517592
# Return the result that was captured in the middleware handler.
518593
return channel_api_args.result
594+
595+
async def _get_app_credentials(
596+
self, app_id: str, oauth_scope: str
597+
) -> MicrosoftAppCredentials:
598+
if not app_id:
599+
return MicrosoftAppCredentials(None, None)
600+
601+
cache_key = f"{app_id}{oauth_scope}"
602+
app_credentials = self._app_credentials_cache.get(cache_key)
603+
604+
if app_credentials:
605+
return app_credentials
606+
607+
app_password = await self._credential_provider.get_app_password(app_id)
608+
app_credentials = MicrosoftAppCredentials(
609+
app_id, app_password, oauth_scope=oauth_scope
610+
)
611+
if self._channel_provider.is_government():
612+
app_credentials.oauth_endpoint = (
613+
GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
614+
)
615+
app_credentials.oauth_scope = (
616+
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
617+
)
618+
619+
self._app_credentials_cache[cache_key] = app_credentials
620+
return app_credentials

libraries/botbuilder-skills/botbuilder/skills/channel_api_middleware.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@
88
from botbuilder.schema import Activity, ActivityTypes, ResourceResponse
99
from .channel_api_args import ChannelApiArgs
1010
from .channel_api_methods import ChannelApiMethods
11-
from .skill_client import SkillClient
11+
from .bot_framework_skill_client import BotFrameworkSkillClient
1212

1313

1414
class ChannelApiMiddleware(Middleware):
15-
def __init__(self, skill_adapter: SkillClient):
15+
def __init__(self, skill_adapter: BotFrameworkSkillClient):
1616
self._skill_adapter = skill_adapter
1717

1818
async def on_turn(
1919
self, context: TurnContext, logic: Callable[[TurnContext], Awaitable]
2020
):
2121
if (
2222
context.activity.type == ActivityTypes.invoke
23-
and context.activity.name == SkillClient.INVOKE_ACTIVITY_NAME
23+
and context.activity.name == BotFrameworkSkillClient.INVOKE_ACTIVITY_NAME
2424
):
2525
# process invoke activity TODO: (implement next 2 lines)
2626
invoke_activity = context.activity.as_invoke_activity()

libraries/botbuilder-skills/botbuilder/skills/integration/__init__.py

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from typing import Awaitable, Callable, Tuple
4+
5+
from requests import Request
6+
7+
from botbuilder.core import Bot, BotAdapter
8+
from botframework.connector.auth import ClaimsIdentity
9+
10+
from ..bot_framework_skill_client import BotFrameworkSkillClient
11+
12+
RouteAction = Callable[
13+
[BotAdapter, BotFrameworkSkillClient, Bot, ClaimsIdentity, Request, Tuple[str]],
14+
Awaitable[object],
15+
]

libraries/botframework-connector/botframework/connector/auth/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@
1818
from .jwt_token_extractor import *
1919
from .government_constants import *
2020
from .authentication_constants import *
21+
from .channel_provider import *
22+
from .simple_channel_provider import *
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import ABC, abstractmethod
5+
6+
7+
class ChannelProvider(ABC):
8+
"""
9+
ChannelProvider interface. This interface allows Bots to provide their own
10+
implementation for the configuration parameters to connect to a Bot.
11+
Framework channel service.
12+
"""
13+
14+
@abstractmethod
15+
async def get_channel_service(self) -> str:
16+
raise NotImplementedError()
17+
18+
@abstractmethod
19+
def is_government(self) -> bool:
20+
raise NotImplementedError()
21+
22+
@abstractmethod
23+
def is_public_azure(self) -> bool:
24+
raise NotImplementedError()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .channel_provider import ChannelProvider
5+
from .government_constants import GovernmentConstants
6+
7+
8+
class SimpleChannelProvider(ChannelProvider):
9+
"""
10+
ChannelProvider interface. This interface allows Bots to provide their own
11+
implementation for the configuration parameters to connect to a Bot.
12+
Framework channel service.
13+
"""
14+
15+
def __init__(self, channel_service: str = None):
16+
self.channel_service = channel_service
17+
18+
async def get_channel_service(self) -> str:
19+
return self.channel_service
20+
21+
def is_government(self) -> bool:
22+
return self.channel_service == GovernmentConstants.CHANNEL_SERVICE
23+
24+
def is_public_azure(self) -> bool:
25+
return not self.channel_service

0 commit comments

Comments
 (0)
0