10000 Merge branch 'main' into trboehre/meetingalert · mmtrucefacts/botbuilder-python@8532759 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8532759

Browse files
authored
Merge branch 'main' into trboehre/meetingalert
2 parents 66ca863 + a30301b commit 8532759

File tree

18 files changed

+316
-54
lines changed

18 files changed

+316
-54
lines changed

libraries/botbuilder-core/botbuilder/core/activity_handler.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,36 @@ async def on_installation_update( # pylint: disable=unused-argument
374374
Override this in a derived class to provide logic specific to
375375
ActivityTypes.InstallationUpdate activities.
376376
377+
:param turn_context: The context object for this turn
378+
:type turn_context: :class:`botbuilder.core.TurnContext`
379+
:returns: A task that represents the work queued to execute
380+
"""
381+
if turn_context.activity.action == "add":
382+
return await self.on_installation_update_add(turn_context)
383+
if turn_context.activity.action == "remove":
384+
return await self.on_installation_update_remove(turn_context)
385+
return
386+
387+
async def on_installation_update_add( # pylint: disable=unused-argument
388+
self, turn_context: TurnContext
389+
):
390+
"""
391+
Override this in a derived class to provide logic specific to
392+
ActivityTypes.InstallationUpdate activities with 'action' set to 'add'.
393+
394+
:param turn_context: The context object for this turn
395+
:type turn_context: :class:`botbuilder.core.TurnContext`
396+
:returns: A task that represents the work queued to execute
397+
"""
398+
return
399+
400+
async def on_installation_update_remove( # pylint: disable=unused-argument
401+
self, turn_context: TurnContext
402+
):
403+
"""
404+
Override this in a derived class to provide logic specific to
405+
ActivityTypes.InstallationUpdate activities with 'action' set to 'remove'.
406+
377407
:param turn_context: The context object for this turn
378408
:type turn_context: :class:`botbuilder.core.TurnContext`
379409
:returns: A task that represents the work queued to execute

libraries/botbuilder-core/botbuilder/core/channel_service_handler.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ClaimsIdentity,
2222
CredentialProvider,
2323
JwtTokenValidation,
24+
SkillValidation,
2425
)
2526

2627

@@ -469,18 +470,28 @@ async def on_upload_attachment(
469470
raise BotActionNotImplementedError()
470471

471472
async def _authenticate(self, auth_header: str) -> ClaimsIdentity:
473+
"""
474+
Helper to authenticate the header.
475+
476+
This code is very similar to the code in JwtTokenValidation.authenticate_request,
477+
we should move this code somewhere in that library when we refactor auth,
478+
for now we keep it private to avoid adding more public static functions that we will need to deprecate later.
479+
"""
472480
if not auth_header:
473481
is_auth_disabled = (
474482
await self._credential_provider.is_authentication_disabled()
475483
)
476-
if is_auth_disabled:
477-
# In the scenario where Auth is disabled, we still want to have the
478-
# IsAuthenticated flag set in the ClaimsIdentity. To do this requires
479-
# adding in an empty claim.
480-
return ClaimsIdentity({}, True)
484+
if not is_auth_disabled:
485+
# No auth header. Auth is required. Request is not authorized.
486+
raise PermissionError()
481487

482-
raise PermissionError()
488+
# In the scenario where Auth is disabled, we still want to have the
489+
# IsAuthenticated flag set in the ClaimsIdentity. To do this requires
490+
# adding in an empty claim.
491+
# Since ChannelServiceHandler calls are always a skill callback call, we set the skill claim too.
492+
return SkillValidation.create_anonymous_skill_claim()
483493

494+
# Validate the header and extract claims.
484495
return await JwtTokenValidation.validate_auth_header(
485496
auth_header,
486497
self._credential_provider,

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

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
import json
33
from uuid import uuid4
44
from asyncio import Future
5-
from typing import Dict, List
5+
from typing import Dict, List, Callable
66

77
from unittest.mock import Mock, MagicMock
88
import aiounittest
99

10+
from botframework.connector.auth import (
11+
AuthenticationConfiguration,
12+
AuthenticationConstants,
13+
ClaimsIdentity,
14+
)
1015
from botbuilder.core import (
1116
TurnContext,
1217
BotActionNotImplementedError,
@@ -28,11 +33,6 @@
2833
Transcript,
2934
CallerIdConstants,
3035
)
31-
from botframework.connector.auth import (
32-
AuthenticationConfiguration,
33-
AuthenticationConstants,
34-
ClaimsIdentity,
35-
)
3636

3737

3838
class ConversationIdFactoryForTest(ConversationIdFactoryBase):
@@ -206,10 +206,30 @@ async def test_on_send_to_conversation(self):
206206
)
207207

208208
mock_adapter = Mock()
209-
mock_adapter.continue_conversation = MagicMock(return_value=Future())
210-
mock_adapter.continue_conversation.return_value.set_result(Mock())
211-
mock_adapter.send_activities = MagicMock(return_value=Future())
212-
mock_adapter.send_activities.return_value.set_result([])
209+
210+
async def continue_conversation(
211+
reference: ConversationReference,
212+
callback: Callable,
213+
bot_id: str = None,
214+
claims_identity: ClaimsIdentity = None,
215+
audience: str = None,
216+
): # pylint: disable=unused-argument
217+
await callback(
218+
TurnContext(
219+
mock_adapter,
220+
conversation_reference_extension.get_continuation_activity(
221+
self._conversation_reference
222+
),
223+
)
224+
)
225+
226+
async def send_activities(
227+
context: TurnContext, activities: List[Activity]
228+
): # pylint: disable=unused-argument
229+
return [ResourceResponse(id="resourceId")]
230+
231+
mock_adapter.continue_conversation = continue_conversation
232+
mock_adapter.send_activities = send_activities
213233

214234
sut = self.create_skill_handler_for_testing(mock_adapter)
215235

@@ -218,25 +238,12 @@ async def test_on_send_to_conversation(self):
218238

219239
assert not activity.caller_id
220240

221-
await sut.test_on_send_to_conversation(
241+
resource_response = await sut.test_on_send_to_conversation(
222242
self._claims_identity, self._conversation_id, activity
223243
)
224244

225-
args, kwargs = mock_adapter.continue_conversation.call_args_list[0]
226-
227-
assert isinstance(args[0], ConversationReference)
228-
assert callable( 97AE args[1])
229-
assert isinstance(kwargs["claims_identity"], ClaimsIdentity)
230-
231-
await args[1](
232-
TurnContext(
233-
mock_adapter,
234-
conversation_reference_extension.get_continuation_activity(
235-
self._conversation_reference
236-
),
237-
)
238-
)
239245
assert activity.caller_id is None
246+
assert resource_response.id == "resourceId"
240247

241248
async def test_forwarding_on_send_to_conversation(self):
242249
self._conversation_id = await self._test_id_factory.create_skill_conversation_id(

libraries/botbuilder-core/tests/test_activity_handler.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ async def on_installation_update(self, turn_context: TurnContext):
7777
self.record.append("on_installation_update")
7878
return await super().on_installation_update(turn_context)
7979

80+
async def on_installation_update_add(self, turn_context: TurnContext):
81+
self.record.append("on_installation_update_add")
82+
return await super().on_installation_update_add(turn_context)
83+
84+
async def on_installation_update_remove(self, turn_context: TurnContext):
85+
self.record.append("on_installation_update_remove")
86+
return await super().on_installation_update_remove(turn_context)
87+
8088
async def on_unrecognized_activity_type(self, turn_context: TurnContext):
8189
self.record.append("on_unrecognized_activity_type")
8290
return await super().on_unrecognized_activity_type(turn_context)
@@ -246,6 +254,34 @@ async def test_on_installation_update(self):
246254
assert len(bot.record) == 1
247255
assert bot.record[0] == "on_installation_update"
248256

257+
async def test_on_installation_update_add(self):
258+
activity = Activity(type=ActivityTypes.installation_update, action="add")
259+
260+
adapter = TestInvokeAdapter()
261+
turn_context = TurnContext(adapter, activity)
262+
263+
# Act
264+
bot = TestingActivityHandler()
265+
await bot.on_turn(turn_context)
266+
267+
assert len(bot.record) == 2
268+
assert bot.record[0] == "on_installation_update"
269+
assert bot.record[1] == "on_installation_update_add"
270+
271+
async def test_on_installation_update_add_remove(self):
272+
activity = Activity(type=ActivityTypes.installation_update, action="remove")
273+
274+
adapter = TestInvokeAdapter()
275+
turn_context = TurnContext(adapter, activity)
276+
277+
# Act
278+
bot = TestingActivityHandler()
279+
await bot.on_turn(turn_context)
280+
281+
assert len(bot.record) == 2
282+
assert bot.record[0] == "on_installation_update"
283+
assert bot.record[1] == "on_installation_update_remove"
284+
249285
async def test_healthcheck(self):
250286
activity = Activity(type=ActivityTypes.invoke, name="healthcheck",)
251287

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import aiounittest
5+
from botbuilder.core import ChannelServiceHandler
6+
from botframework.connector.auth import (
7+
AuthenticationConfiguration,
8+
ClaimsIdentity,
9+
SimpleCredentialProvider,
10+
JwtTokenValidation,
11+
AuthenticationConstants,
12+
)
13+
import botbuilder.schema
14+
15+
16+
class TestChannelServiceHandler(ChannelServiceHandler):
17+
def __init__(self):
18+
self.claims_identity = None
19+
ChannelServiceHandler.__init__(
20+
self, SimpleCredentialProvider("", ""), AuthenticationConfiguration()
21+
)
22+
23+
async def on_reply_to_activity(
24+
self,
25+
claims_identity: ClaimsIdentity,
26+
conversation_id: str,
27+
activity_id: str,
28+
activity: botbuilder.schema.Activity,
29+
) -> botbuilder.schema.ResourceResponse:
30+
self.claims_identity = claims_identity
31+
return botbuilder.schema.ResourceResponse()
32+
33+
34+
class ChannelServiceHandlerTests(aiounittest.AsyncTestCase):
35+
async def test_should_authenticate_anonymous_skill_claim(self):
36+
sut = TestChannelServiceHandler()
37+
await sut.handle_reply_to_activity(None, "123", "456", {})
38+
39+
assert (
40+
sut.claims_identity.authentication_type
41+
== AuthenticationConstants.ANONYMOUS_AUTH_TYPE
42+
)
43+
assert (
44+
JwtTokenValidation.get_app_id_from_claims(sut.claims_identity.claims)
45+
== AuthenticationConstants.ANONYMOUS_SKILL_APP_ID
46+
)

libraries/botbuilder-integration-aiohttp/botbuilder/integration/aiohttp/bot_framework_http_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ConversationReference,
1616
ConversationAccount,
1717
ChannelAccount,
18+
RoleTypes,
1819
)
1920
from botframework.connector.auth import (
2021
ChannelProvider,
@@ -97,7 +98,9 @@ async def post_activity(
9798
activity.conversation.id = conversation_id
9899
activity.service_url = service_url
99100
if not activity.recipient:
100-
activity.recipient = ChannelAccount()
101+
activity.recipient = ChannelAccount(role=RoleTypes.skill)
102+
else:
103+
activity.recipient.role = RoleTypes.skill
101104

102105
status, content = await self._post_content(to_url, token, activity)
103106

libraries/botbuilder-integration-aiohttp/tests/test_bot_framework_http_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from unittest.mock import Mock
22

33
import aiounittest
4-
from botbuilder.schema import ConversationAccount, ChannelAccount
4+
from botbuilder.schema import ConversationAccount, ChannelAccount, RoleTypes
55
from botbuilder.integration.aiohttp import BotFrameworkHttpClient
66
from botframework.connector.auth import CredentialProvider, Activity
77

@@ -69,3 +69,4 @@ async def _mock_post_content(
6969
)
7070

7171
assert activity.recipient.id == skill_recipient_id
72+
assert activity.recipient.role is RoleTypes.skill

libraries/botbuilder-schema/botbuilder/schema/_connector_client_enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class RoleTypes(str, Enum):
88

99
user = "user"
1010
bot = "bot"
11+
skill = "skill"
1112

1213

1314
class ActivityTypes(str, Enum):

libraries/botbuilder-schema/botbuilder/schema/_models_py3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,8 +1307,8 @@ class ConversationAccount(Model):
13071307
:param aad_object_id: This account's object ID within Azure Active
13081308
Directory (AAD)
13091309
:type aad_object_id: str
1310-
:param role: Role of the entity behind the account (Example: User, Bot,
1311-
etc.). Possible values include: 'user', 'bot'
1310+
:param role: Role of the entity behind the account (Example: User, Bot, Skill
1311+
etc.). Possible values include: 'user', 'bot', 'skill'
13121312
:type role: str or ~botframework.connector.models.RoleTypes
13131313
:param tenant_id: This conversation's tenant ID
13141314
:type tenant_id: str

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ def signed_session(self, session: requests.Session = None) -> requests.Session:
104104
def _should_authorize(
105105
self, session: requests.Session # pylint: disable=unused-argument
106106
) -> bool:
107-
return True
107+
# We don't set the token if the AppId is not set, since it means that we are in an un-authenticated scenario.
108+
return (
109+
self.microsoft_app_id != AuthenticationConstants.ANONYMOUS_SKILL_APP_ID
110+
and self.microsoft_app_id is not None
111+
)
108112

109113
def get_access_token(self, force_refresh: bool = False) -> str:
110114
"""

0 commit comments

Comments
 (0)
0