8000 Merge pull request #821 from microsoft/eric/addAudienceToSkills · LucaSavio/botbuilder-python@e141d08 · GitHub
[go: up one dir, main page]

Skip to content

Commit e141d08

Browse files
authored
Merge pull request microsoft#821 from microsoft/eric/addAudienceToSkills
Support originating audience for skills conversations
2 parents 07692fc + 77053b3 commit e141d08

8 files changed

+212
-66
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
from .bot_framework_skill import BotFrameworkSkill
99
from .conversation_id_factory import ConversationIdFactoryBase
10-
from .skill_conversation_id_factory import SkillConversationIdFactory
1110
from .skill_handler import SkillHandler
11+
from .skill_conversation_id_factory_options import SkillConversationIdFactoryOptions
12+
from .skill_conversation_reference import SkillConversationReference
1213

1314
__all__ = [
1415
"BotFrameworkSkill",
1516
"ConversationIdFactoryBase",
16-
"SkillConversationIdFactory",
17+
"SkillConversationIdFactoryOptions",
18+
"SkillConversationReference",
1719
"SkillHandler",
1820
]
Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,66 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3+
34
from abc import ABC, abstractmethod
5+
from typing import Union
46
from botbuilder.schema import ConversationReference
7+
from .skill_conversation_id_factory_options import SkillConversationIdFactoryOptions
8+
from .skill_conversation_reference import SkillConversationReference
59

610

711
class ConversationIdFactoryBase(ABC):
12+
"""
13+
Handles creating conversation ids for skill and should be subclassed.
14+
15+
.. remarks::
16+
Derive from this class to handle creation of conversation ids, retrieval of
17+
SkillConversationReferences and deletion.
18+
"""
19+
820
@abstractmethod
921
async def create_skill_conversation_id(
10-
self, conversation_reference: ConversationReference
22+
self,
23+
options_or_conversation_reference: Union[
24+
SkillConversationIdFactoryOptions, ConversationReference
25+
],
1126
) -> str:
27+
"""
28+
Using the options passed in, creates a conversation id and
29+
SkillConversationReference, storing them for future use.
30+
31+
:param options_or_conversation_reference: The options contain properties useful
32+
for generating a SkillConversationReference and conversation id.
33+
:type options_or_conversation_reference: :class:
34+
`Union[SkillConversationIdFactoryOptions, ConversationReference]`
35+
36+
:returns: A skill conversation id.
37+
38+
.. note::
39+
SkillConversationIdFactoryOptions is the preferred parameter type, while ConversationReference
40+
type is provided for backwards compatability.
41+
"""
1242
raise NotImplementedError()
1343

1444
@abstractmethod
1545
async def get_conversation_reference(
1646
self, skill_conversation_id: str
17-
) -> ConversationReference:
47+
) -> Union[SkillConversationReference, ConversationReference]:
48+
"""
49+
Retrieves a SkillConversationReference using a conversation id passed in.
50+
51+
:param skill_conversation_id: The conversation id for which to retrieve
52+
the SkillConversationReference.
53+
:type skill_conversation_id: str
54+
55+
.. note::
56+
SkillConversationReference is the preferred return type, while ConversationReference
57+
type is provided for backwards compatability.
58+
"""
1859
raise NotImplementedError()
1960

2061
@abstractmethod
2162
async def delete_conversation_reference(self, skill_conversation_id: str):
63+
"""
64+
Removes any reference to objects keyed on the conversation id passed in.
65+
"""
2266
raise NotImplementedError()

libraries/botbuilder-core/botbuilder/core/skills/skill_conversation_id_factory.py

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.schema import Activity
5+
from .bot_framework_skill import BotFrameworkSkill
6+
7+
8+
class SkillConversationIdFactoryOptions:
9+
def __init__(
10+
self,
11+
from_bot_oauth_scope: str,
12+
from_bot_id: str,
13+
activity: Activity,
14+
bot_framework_skill: BotFrameworkSkill,
15+
):
16+
if from_bot_oauth_scope is None:
17+
raise TypeError(
18+
"SkillConversationIdFactoryOptions(): from_bot_oauth_scope cannot be None."
19+
)
20+
21+
if from_bot_id is None:
22+
raise TypeError(
23+
"SkillConversationIdFactoryOptions(): from_bot_id cannot be None."
24+
)
25+
26+
if activity is None:
27+
raise TypeError(
28+
"SkillConversationIdFactoryOptions(): activity cannot be None."
29+
)
30+
31+
if bot_framework_skill is None:
32+
raise TypeError(
33+
"SkillConversationIdFactoryOptions(): bot_framework_skill cannot be None."
34+
)
35+
36+
self.from_bot_oauth_scope = from_bot_oauth_scope
37+
self.from_bot_id = from_bot_id
38+
self.activity = activity
39+
self.bot_framework_skill = bot_framework_skill
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from botbuilder.schema import ConversationReference
4+
5+
6+
class SkillConversationReference:
7+
"""
8+
ConversationReference implementation for Skills ConversationIdFactory.
9+
"""
10+
11+
def __init__(self, conversation_reference: ConversationReference, oauth_scope: str):
12+
if conversation_reference is None:
13+
raise TypeError(
14+
"SkillConversationReference(): conversation_reference cannot be None."
15+
)
16+
17+
if oauth_scope is None:
18+
raise TypeError("SkillConversationReference(): oauth_scope cannot be None.")
19+
20+
self.conversation_reference = conversation_reference
21+
self.oauth_scope = oauth_scope

libraries/botbuilder-core/botbuilder/core/skills/skill_handler.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
)
1313
from botframework.connector.auth import (
1414
AuthenticationConfiguration,
15+
AuthenticationConstants,
1516
ChannelProvider,
1617
ClaimsIdentity,
1718
CredentialProvider,
19+
GovernmentConstants,
1820
)
19-
20-
from .skill_conversation_id_factory import SkillConversationIdFactory
21+
from .skill_conversation_reference import SkillConversationReference
22+
from .conversation_id_factory import ConversationIdFactoryBase
2123

2224

2325
class SkillHandler(ChannelServiceHandler):
@@ -30,7 +32,7 @@ def __init__(
3032
self,
3133
adapter: BotAdapter,
3234
bot: Bot,
33-
conversation_id_factory: SkillConversationIdFactory,
35+
conversation_id_factory: ConversationIdFactoryBase,
3436
credential_provider: CredentialProvider,
3537
auth_configuration: AuthenticationConfiguration,
3638
channel_provider: ChannelProvider = None,
@@ -118,14 +120,29 @@ async def _process_activity(
118120
reply_to_activity_id: str,
119121
activity: Activity,
120122
) -> ResourceResponse:
121-
conversation_reference = await self._conversation_id_factory.get_conversation_reference(
123+
conversation_reference_result = await self._conversation_id_factory.get_conversation_reference(
122124
conversation_id
123125
)
124126

127+
oauth_scope = None
128+
conversation_reference = None
129+
if isinstance(conversation_reference_result, SkillConversationReference):
130+
oauth_scope = conversation_reference_result.oauth_scope
131+
conversation_reference = (
132+
conversation_reference_result.conversation_reference
133+
)
134+
else:
135+
conversation_reference = conversation_reference_result
136+
oauth_scope = (
137+
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
138+
if self._channel_provider and self._channel_provider.is_government()
139+
else AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
140+
)
141+
125142
if not conversation_reference:
126143
raise KeyError("ConversationReference not found")
127144

128-
skill_conversation_reference = ConversationReference(
145+
activity_conversation_reference = ConversationReference(
129146
activity_id=activity.id,
130147
user=activity.from_property,
131148
bot=activity.recipient,
@@ -137,7 +154,7 @@ async def _process_activity(
137154
async def callback(context: TurnContext):
138155
context.turn_state[
139156
SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY
140-
] = skill_conversation_reference
157+
] = activity_conversation_reference
141158
TurnContext.apply_conversation_reference(activity, conversation_reference)
142159
context.activity.id = reply_to_activity_id
143160

@@ -154,7 +171,10 @@ async def callback(context: TurnContext):
154171
await context.send_activity(activity)
155172

156173
await self._adapter.continue_conversation(
157-
conversation_reference, callback, claims_identity=claims_identity
174+
conversation_reference,
175+
callback,
176+
claims_identity=claims_identity,
177+
audience=oauth_scope,
158178
)
159179
return ResourceResponse(id=str(uuid4()))
160180

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.core import (
5+
BotFrameworkHttpClient,
6+
InvokeResponse,
7+
)
8+
from botbuilder.core.skills import (
9+
ConversationIdFactoryBase,
10+
SkillConversationIdFactoryOptions,
11+
BotFrameworkSkill,
12+
)
13+
from botbuilder.schema import Activity
14+
from botframework.connector.auth import (
15+
AuthenticationConstants,
16+
ChannelProvider,
17+
GovernmentConstants,
18+
SimpleCredentialProvider,
19+
)
20+
21+
22+
class SkillHttpClient(BotFrameworkHttpClient):
23+
def __init__(
24+
self,
25+
credential_provider: SimpleCredentialProvider,
26+
skill_conversation_id_factory: ConversationIdFactoryBase,
27+
channel_provider: ChannelProvider = None,
28+
):
29+
if not skill_conversation_id_factory:
30+
raise TypeError(
31+
"SkillHttpClient(): skill_conversation_id_factory can't be None"
32+
)
33+
34+
super().__init__(credential_provider)
35+
36+
self._skill_conversation_id_factory = skill_conversation_id_factory
37+ 10000
self._channel_provider = channel_provider
38+
39+
async def post_activity_to_skill(
40+
self,
41+
from_bot_id: str,
42+
to_skill: BotFrameworkSkill,
43+
service_url: str,
44+
activity: Activity,
45+
originating_audience: str = None,
46+
) -> InvokeResponse:
47+
48+
if originating_audience is None:
49+
originating_audience = (
50+
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
51+
if self._channel_provider is not None
52+
and self._channel_provider.IsGovernment()
53+
else AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
54+
)
55+
56+
options = SkillConversationIdFactoryOptions(
57+
from_bot_oauth_scope=originating_audience,
58+
from_bot_id=from_bot_id,
59+
activity=activity,
60+
bot_framework_skill=to_skill,
61+
)
62+
63+
skill_conversation_id = await self._skill_conversation_id_factory.create_skill_conversation_id(
64+
options
65+
)
66+
67+
return await super().post_activity(
68+
from_bot_id,
69+
to_skill.app_id,
70+
to_skill.skill_endpoint,
71+
service_url,
72+
skill_conversation_id,
73+
activity,
74+
)

libraries/botbuilder-core/tests/skills/test_skill_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class ConversationIdFactoryForTest(ConversationIdFactoryBase):
3434
def __init__(self):
3535
self._conversation_refs: Dict[str, str] = {}
3636

37-
async def create_skill_conversation_id(
37+
async def create_skill_conversation_id( # pylint: disable=W0221
3838
self, conversation_reference: ConversationReference
3939
) -> str:
4040
cr_json = json.dumps(conversation_reference.serialize())

0 commit comments

Comments
 (0)
0