Repository: microsoft/botbuilder-python

Issue:
[PORT] Fixes Unauthorized error when calling ContinueConversation
> Port this change from botbuilder-dotnet/master branch:
https://github.com/microsoft/botbuilder-dotnet/pull/4348

Fixes #4347

Fixes Unauthorized error when ContinueConversation is called on a bot app service instance that didn't originate the skill request.
- Updated code in BotFrameworkAdapter.ContinueConversation() so it always adds the activity.ServiceUrl to TrustedHostNames if the request has been authorized and it has an app ID (this will work for skills and other proactive messages)
- Made TrustedHostNames in AppCredentials internal and added InternalsVisibleTo directive to Microsoft.BotConnector so we can access it and assert the state of the state in tests.
- Cleaned up some async warnings in BotFrameworkAdapterTests
- Added additional logging to AppCredentials to help troubleshoot this issue in the future.


# Changed projects
* Microsoft.Bot.Builder
* Microsoft.Bot.Connector
* Microsoft.Bot.Builder.Tests






Discussion:
Hi @axelsrz, can you validate if the related PR applies to Python? 

Resolving patch:
diff --git a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py
--- a/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py
+++ b/libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py
@@ -279,10 +279,18 @@ async def continue_conversation(
         context.turn_state[BotAdapter.BOT_CALLBACK_HANDLER_KEY] = callback
         context.turn_state[BotAdapter.BOT_OAUTH_SCOPE_KEY] = audience
 
-        # Add the channel service URL to the trusted services list so we can send messages back.
-        # the service URL for skills is trusted because it is applied by the SkillHandler based
-        # on the original request received by the root bot
-        AppCredentials.trust_service_url(reference.service_url)
+        # If we receive a valid app id in the incoming token claims, add the channel service URL to the
+        # trusted services list so we can send messages back.
+        # The service URL for skills is trusted because it is applied by the SkillHandler based on the original
+        # request received by the root bot
+        app_id_from_claims = JwtTokenValidation.get_app_id_from_claims(
+            claims_identity.claims
+        )
+        if app_id_from_claims:
+            if SkillValidation.is_skill_claim(
+                claims_identity.claims
+            ) or await self._credential_provider.is_valid_appid(app_id_from_claims):
+                AppCredentials.trust_service_url(reference.service_url)
 
         client = await self.create_connector_client(
             reference.service_url, claims_identity, audience


Test patch:
diff --git a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py
--- a/libraries/botbuilder-core/tests/test_bot_framework_adapter.py
+++ b/libraries/botbuilder-core/tests/test_bot_framework_adapter.py
@@ -571,8 +571,14 @@ async def callback(context: TurnContext):
             scope = context.turn_state[BotFrameworkAdapter.BOT_OAUTH_SCOPE_KEY]
             assert AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE == scope
 
+            # Ensure the serviceUrl was added to the trusted hosts
+            assert AppCredentials.is_trusted_service(channel_service_url)
+
         refs = ConversationReference(service_url=channel_service_url)
 
+        # Ensure the serviceUrl is NOT in the trusted hosts
+        assert not AppCredentials.is_trusted_service(channel_service_url)
+
         await adapter.continue_conversation(
             refs, callback, claims_identity=skills_identity
         )
@@ -629,8 +635,14 @@ async def callback(context: TurnContext):
             scope = context.turn_state[BotFrameworkAdapter.BOT_OAUTH_SCOPE_KEY]
             assert skill_2_app_id == scope
 
+            # Ensure the serviceUrl was added to the trusted hosts
+            assert AppCredentials.is_trusted_service(skill_2_service_url)
+
         refs = ConversationReference(service_url=skill_2_service_url)
 
+        # Ensure the serviceUrl is NOT in the trusted hosts
+        assert not AppCredentials.is_trusted_service(skill_2_service_url)
+
         await adapter.continue_conversation(
             refs, callback, claims_identity=skills_identity, audience=skill_2_app_id
         )