Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit be246d7

Browse files
authored
Merge branch 'master' into trboehre-readme-update
2 parents bc8e433 + 9a5e20a commit be246d7

30 files changed

+1572
-1177
lines changed

ci-pr-pipeline.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,14 @@ jobs:
4141
pip install -e ./libraries/botbuilder-dialogs
4242
pip install -e ./libraries/botbuilder-azure
4343
pip install -e ./libraries/botbuilder-testing
44+
pip install -e ./libraries/botbuilder-integration-applicationinsights-aiohttp
4445
pip install -r ./libraries/botframework-connector/tests/requirements.txt
4546
pip install -r ./libraries/botbuilder-core/tests/requirements.txt
4647
pip install coveralls
4748
pip install pylint
4849
pip install black
4950
displayName: 'Install dependencies'
5051
51-
- script: 'pip install requests_mock'
52-
displayName: 'Install requests mock (REMOVE AFTER MERGING INSPECTION)'
53-
enabled: false
54-
5552
- script: |
5653
pip install pytest
5754
pip install pytest-cov

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

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77

88

99
class ActivityHandler:
10+
"""
11+
Handles activities and should be subclassed.
12+
13+
.. remarks::
14+
Derive from this class to handle particular activity types.
15+
Yon can add pre and post processing of activities by calling the base class
16+
in the derived class.
17+
"""
18+
1019
async def on_turn(self, turn_context: TurnContext):
1120
"""
1221
Called by the adapter (for example, :class:`BotFrameworkAdapter`) at runtime
@@ -22,8 +31,8 @@ async def on_turn(self, turn_context: TurnContext):
2231
process, which allows a derived class to provide type-specific logic in a controlled way.
2332
In a derived class, override this method to add logic that applies to all activity types.
2433
Also
25-
- Add logic to apply before the type-specific logic and before calling :meth:`ActivityHandler.on_turn()`.
26-
- Add logic to apply after the type-specific logic after calling :meth:`ActivityHandler.on_turn()`.
34+
- Add logic to apply before the type-specific logic and before calling :meth:`on_turn()`.
35+
- Add logic to apply after the type-specific logic after calling :meth:`on_turn()`.
2736
"""
2837
if turn_context is None:
2938
raise TypeError("ActivityHandler.on_turn(): turn_context cannot be None.")
@@ -71,21 +80,22 @@ async def on_message_activity( # pylint: disable=unused-argument
7180
async def on_conversation_update_activity(self, turn_context: TurnContext):
7281
"""
7382
Invoked when a conversation update activity is received from the channel when the base behavior of
74-
:meth:`ActivityHandler.on_turn()` is used.
83+
:meth:`on_turn()` is used.
7584
7685
:param turn_context: The context object for this turn
7786
:type turn_context: :class:`botbuilder.core.TurnContext`
7887
7988
:returns: A task that represents the work queued to execute
8089
8190
.. remarks::
82-
When the :meth:'ActivityHandler.on_turn()` method receives a conversation update activity, it calls this
91+
When the :meth:`on_turn()` method receives a conversation update activity, it calls this
8392
method.
84-
If the conversation update activity indicates that members other than the bot joined the conversation,
85-
it calls the :meth:`ActivityHandler.on_members_added_activity()` method.
86-
If the conversation update activity indicates that members other than the bot left the conversation,
87-
it calls the :meth:`ActivityHandler.on_members_removed_activity()` method.
88-
In a derived class, override this method to add logic that applies to all conversation update activities.
93+
Also
94+
- If the conversation update activity indicates that members other than the bot joined the conversation,
95+
it calls the :meth:`on_members_added_activity()` method.
96+
- If the conversation update activity indicates that members other than the bot left the conversation,
97+
it calls the :meth:`on_members_removed_activity()` method.
98+
- In a derived class, override this method to add logic that applies to all conversation update activities.
8999
Add logic to apply before the member added or removed logic before the call to this base class method.
90100
"""
91101
if (
@@ -120,7 +130,7 @@ async def on_members_added_activity(
120130
:returns: A task that represents the work queued to execute
121131
122132
.. remarks::
123-
When the :meth:'ActivityHandler.on_conversation_update_activity()` method receives a conversation
133+
When the :meth:`on_conversation_update_activity()` method receives a conversation
124134
update activity that indicates
125135
one or more users other than the bot are joining the conversation, it calls this method.
126136
"""
@@ -142,7 +152,7 @@ async def on_members_removed_activity(
142152
:returns: A task that represents the work queued to execute
143153
144154
.. remarks::
145-
When the :meth:'ActivityHandler.on_conversation_update_activity()` method receives a conversation
155+
When the :meth:`on_conversation_update_activity()` method receives a conversation
146156
update activity that indicates one or more users other than the bot are leaving the conversation,
147157
it calls this method.
148158
"""
@@ -152,7 +162,7 @@ async def on_members_removed_activity(
152162
async def on_message_reaction_activity(self, turn_context: TurnContext):
153163
"""
154164
Invoked when an event activity is received from the connector when the base behavior of
155-
:meth:'ActivityHandler.on_turn()` is used.
165+
:meth:`on_turn()` is used.
156166
157167
:param turn_context: The context object for this turn
158168
:type turn_context: :class:`botbuilder.core.TurnContext`
@@ -162,15 +172,18 @@ async def on_message_reaction_activity(self, turn_context: TurnContext):
162172
.. remarks::
163173
Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a previously
164174
sent activity.
175+
165176
Message reactions are only supported by a few channels. The activity that the message reaction corresponds
166177
to is indicated in the reply to Id property. The value of this property is the activity id of a previously
167178
sent activity given back to the bot as the response from a send call.
168-
When the :meth:'ActivityHandler.on_turn()` method receives a message reaction activity, it calls this
179+
When the :meth:`on_turn()` method receives a message reaction activity, it calls this
169180
method.
170-
If the message reaction indicates that reactions were added to a message, it calls
171-
:meth:'ActivityHandler.on_reaction_added().
172-
If the message reaction indicates that reactions were removed from a message, it calls
173-
:meth:'ActivityHandler.on_reaction_removed().
181+
182+
- If the message reaction indicates that reactions were added to a message, it calls
183+
:meth:`on_reaction_added()`.
184+
- If the message reaction indicates that reactions were removed from a message, it calls
185+
:meth:`on_reaction_removed()`.
186+
174187
In a derived class, override this method to add logic that applies to all message reaction activities.
175188
Add logic to apply before the reactions added or removed logic before the call to the this base class
176189
method.
@@ -202,8 +215,9 @@ async def on_reactions_added( # pylint: disable=unused-argument
202215
203216
.. remarks::
204217
Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji)
205-
to a previously sent message on the conversation. Message reactions are supported by only a few channels.
206-
The activity that the message is in reaction to is identified by the activity's reply to Id property.
218+
to a previously sent message on the conversation.
219+
Message reactions are supported by only a few channels.
220+
The activity that the message is in reaction to is identified by the activity's reply to ID property.
207221
The value of this property is the activity ID of a previously sent activity. When the bot sends an activity,
208222
the channel assigns an ID to it, which is available in the resource response Id of the result.
209223
"""
@@ -235,17 +249,17 @@ async def on_reactions_removed( # pylint: disable=unused-argument
235249
async def on_event_activity(self, turn_context: TurnContext):
236250
"""
237251
Invoked when an event activity is received from the connector when the base behavior of
238-
:meth:'ActivityHandler.on_turn()` is used.
252+
:meth:`on_turn()` is used.
239253
240254
:param turn_context: The context object for this turn
241255
:type turn_context: :class:`botbuilder.core.TurnContext`
242256
243257
:returns: A task that represents the work queued to execute
244258
245259
.. remarks::
246-
When the :meth:'ActivityHandler.on_turn()` method receives an event activity, it calls this method.
247-
If the activity name is `tokens/response`, it calls :meth:'ActivityHandler.on_token_response_event()`;
248-
otherwise, it calls :meth:'ActivityHandler.on_event()`.
260+
When the :meth:`on_turn()` method receives an event activity, it calls this method.
261+
If the activity name is `tokens/response`, it calls :meth:`on_token_response_event()`;
262+
otherwise, it calls :meth:`on_event()`.
249263
250264
In a derived class, override this method to add logic that applies to all event activities.
251265
Add logic to apply before the specific event-handling logic before the call to this base class method.
@@ -265,7 +279,7 @@ async def on_token_response_event( # pylint: disable=unused-argument
265279
):
266280
"""
267281
Invoked when a `tokens/response` event is received when the base behavior of
268-
:meth:'ActivityHandler.on_event_activity()` is used.
282+
:meth:`on_event_activity()` is used.
269283
If using an `oauth_prompt`, override this method to forward this activity to the current dialog.
270284
271285
:param turn_context: The context object for this turn
@@ -274,7 +288,7 @@ async def on_token_response_event( # pylint: disable=unused-argument
274288
:returns: A task that represents the work queued to execute
275289
276290
.. remarks::
277-
When the :meth:'ActivityHandler.on_event()` method receives an event with an activity name of
291+
When the :meth:`on_event()` method receives an event with an activity name of
278292
`tokens/response`, it calls this method. If your bot uses an `oauth_prompt`, forward the incoming
279293
activity to the current dialog.
280294
"""
@@ -285,7 +299,7 @@ async def on_event( # pylint: disable=unused-argument
285299
):
286300
"""
287301
Invoked when an event other than `tokens/response` is received when the base behavior of
288-
:meth:'ActivityHandler.on_event_activity()` is used.
302+
:meth:`on_event_activity()` is used.
289303
290304
291305
:param turn_context: The context object for this turn
@@ -294,7 +308,7 @@ async def on_event( # pylint: disable=unused-argument
294308
:returns: A task that represents the work queued to execute
295309
296310
.. remarks::
297-
When the :meth:'ActivityHandler.on_event_activity()` is used method receives an event with an
311+
When the :meth:`on_event_activity()` is used method receives an event with an
298312
activity name other than `tokens/response`, it calls this method.
299313
This method could optionally be overridden if the bot is meant to handle miscellaneous events.
300314
"""
@@ -317,7 +331,7 @@ async def on_unrecognized_activity_type( # pylint: disable=unused-argument
317331
):
318332
"""
319333
Invoked when an activity other than a message, conversation update, or event is received when the base
320-
behavior of :meth:`ActivityHandler.on_turn()` is used.
334+
behavior of :meth:`on_turn()` is used.
321335
If overridden, this method could potentially respond to any of the other activity types.
322336
323337
:param turn_context: The context object for this turn
@@ -326,7 +340,7 @@ async def on_unrecognized_activity_type( # pylint: disable=unused-argument
326340
:returns: A task that represents the work queued to execute
327341
328342
.. remarks::
329-
When the :meth:`ActivityHandler.on_turn()` method receives an activity that is not a message,
343+
When the :meth:`on_turn()` method receives an activity that is not a message,
330344
conversation update, message reaction, or event activity, it calls this method.
331345
"""
332346
return

libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py

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

4+
# pylint: disable=too-many-lines
5+
46
import asyncio
57
import base64
68
import json
@@ -22,6 +24,7 @@
2224
JwtTokenValidation,
2325
SimpleCredentialProvider,
2426
SkillValidation,
27+
CertificateAppCredentials,
2528
)
2629
from botframework.connector.token_api import TokenApiClient
2730
from botframework.connector.token_api.models import TokenStatus
@@ -76,13 +79,15 @@ class BotFrameworkAdapterSettings:
7679
def __init__(
7780
self,
7881
app_id: str,
79-
app_password: str,
82+
app_password: str = None,
8083
channel_auth_tenant: str = None,
8184
oauth_endpoint: str = None,
8285
open_id_metadata: str = None,
8386
channel_service: str = None,
8487
channel_provider: ChannelProvider = None,
8588
auth_configuration: AuthenticationConfiguration = None,
89+
certificate_thumbprint: str = None,
90+
certificate_private_key: str = None,
8691
):
8792
"""
8893
Contains the settings used to initialize a :class:`BotFrameworkAdapter` instance.
@@ -104,6 +109,15 @@ def __init__(
104109
:type channel_provider: :class:`botframework.connector.auth.ChannelProvider`
105110
:param auth_configuration:
106111
:type auth_configuration: :class:`botframework.connector.auth.AuthenticationConfiguration`
112+
:param certificate_thumbprint: X509 thumbprint
113+
:type certificate_thumbprint: str
114+
:param certificate_private_key: X509 private key
115+
:type certificate_private_key: str
116+
117+
.. remarks::
118+
For credentials authorization, both app_id and app_password are required.
119+
For certificate authorization, app_id, certificate_thumbprint, and certificate_private_key are required.
120+
107121
"""
108122
self.app_id = app_id
109123
self.app_password = app_password
@@ -113,6 +127,8 @@ def __init__(
113127
self.channel_service = channel_service
114128
self.channel_provider = channel_provider
115129
self.auth_configuration = auth_configuration or AuthenticationConfiguration()
130+
self.certificate_thumbprint = certificate_thumbprint
131+
self.certificate_private_key = certificate_private_key
116132

117133

118134
class BotFrameworkAdapter(BotAdapter, UserTokenProvider):
@@ -141,23 +157,42 @@ def __init__(self, settings: BotFrameworkAdapterSettings):
141157
"""
142158
super(BotFrameworkAdapter, self).__init__()
143159
self.settings = settings or BotFrameworkAdapterSettings("", "")
160+
161+
# If settings.certificate_thumbprint & settings.certificate_private_key are provided,
162+
# use CertificateAppCredentials.
163+
if self.settings.certificate_thumbprint and settings.certificate_private_key:
164+
self._credentials = CertificateAppCredentials(
165+
self.settings.app_id,
166+
self.settings.certificate_thumbprint,
167+
self.settings.certificate_private_key,
168+
self.settings.channel_auth_tenant,
169+
)
170+
self._credential_provider = SimpleCredentialProvider(
171+
self.settings.app_id, ""
172+
)
173+
else:
174+
self._credentials = MicrosoftAppCredentials(
175+
self.settings.app_id,
176+
self.settings.app_password,
177+
self.settings.channel_auth_tenant,
178+
)
179+
self._credential_provider = SimpleCredentialProvider(
180+
self.settings.app_id, self.settings.app_password
181+
)
182+
183+
self._is_emulating_oauth_cards = False
184+
185+
# If no channel_service or open_id_metadata values were passed in the settings, check the
186+
# process' Environment Variables for values.
187+
# These values may be set when a bot is provisioned on Azure and if so are required for
188+
# the bot to properly work in Public Azure or a National Cloud.
144189
self.settings.channel_service = self.settings.channel_service or os.environ.get(
145190
AuthenticationConstants.CHANNEL_SERVICE
146191
)
147-
148192
self.settings.open_id_metadata = (
149193
self.settings.open_id_metadata
150194
or os.environ.get(AuthenticationConstants.BOT_OPEN_ID_METADATA_KEY)
151195
)
152-
self._credentials = MicrosoftAppCredentials(
153-
self.settings.app_id,
154-
self.settings.app_password,
155-
self.settings.channel_auth_tenant,
156-
)
157-
self._credential_provider = SimpleCredentialProvider(
158-
self.settings.app_id, self.settings.app_password
159-
)
160-
self._is_emulating_oauth_cards = False
161196

162197
if self.settings.open_id_metadata:
163198
ChannelValidation.open_id_metadata_endpoint = self.settings.open_id_metadata
@@ -878,35 +913,39 @@ async def create_connector_client(
878913
879914
:return: An instance of the :class:`ConnectorClient` class
880915
"""
916+
917+
# Anonymous claims and non-skill claims should fall through without modifying the scope.
918+
credentials = self._credentials
919+
881920
if identity:
882921
bot_app_id_claim = identity.claims.get(
883922
AuthenticationConstants.AUDIENCE_CLAIM
884923
) or identity.claims.get(AuthenticationConstants.APP_ID_CLAIM)
885924

886-
credentials = None
887925
if bot_app_id_claim and SkillValidation.is_skill_claim(identity.claims):
888926
scope = JwtTokenValidation.get_app_id_from_claims(identity.claims)
889927

890-
password = await self._credential_provider.get_app_password(
891-
bot_app_id_claim
892-
)
893-
credentials = MicrosoftAppCredentials(
894-
bot_app_id_claim, password, oauth_scope=scope
895-
)
896-
if (
897-
self.settings.channel_provider
898-
and self.settings.channel_provider.is_government()
899-
):
900-
credentials.oauth_endpoint = (
901-
GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
928+
# Do nothing, if the current credentials and its scope are valid for the skill.
929+
# i.e. the adapter instance is pre-configured to talk with one skill.
930+
# Otherwise we will create a new instance of the AppCredentials
931+
# so self._credentials.oauth_scope isn't overridden.
932+
if self._credentials.oauth_scope != scope:
933+
password = await self._credential_provider.get_app_password(
934+
bot_app_id_claim
902935
)
903-
credentials.oauth_scope = (
904-
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
936+
credentials = MicrosoftAppCredentials(
937+
bot_app_id_claim, password, oauth_scope=scope
905938
)
906-
else:
907-
credentials = self._credentials
908-
else:
909-
credentials = self._credentials
939+
if (
940+
self.settings.channel_provider
941+
and self.settings.channel_provider.is_government()
942+
):
943+
credentials.oauth_endpoint = (
944+
GovernmentConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL
945+
)
946+
credentials.oauth_scope = (
947+
GovernmentConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE
948+
)
910949

911950
client_key = (
912951
f"{service_url}{credentials.microsoft_app_id if credentials else ''}"

0 commit comments

Comments
 (0)